From 87d36c5eb6bc767370c35696d0c996bfda201d3c Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Tue, 29 Oct 2024 14:52:51 +0100 Subject: [PATCH 01/13] Use sourcegen bytecode generator to generate internal metadata --- core-processor/build.gradle.kts | 1 + .../micronaut/aop/writer/AopProxyWriter.java | 1682 ++--- .../EvaluatedExpressionWriter.java | 111 +- .../AnnotationMetadataGenUtils.java | 600 ++ .../annotation/AnnotationMetadataWriter.java | 813 +-- .../inject/ast/ReflectClassElement.java | 15 + .../inject/beans/visitor/AptClassWriter.java | 60 - .../visitor/BeanIntrospectionWriter.java | 1492 ++-- .../IntrospectedTypeElementVisitor.java | 8 +- .../AbstractAnnotationMetadataWriter.java | 245 - .../writer/AbstractClassFileWriter.java | 1684 ----- .../inject/writer/ArgumentExpUtils.java | 631 ++ .../writer/ArrayAwareSignatureWriter.java | 238 - .../writer/BeanConfigurationWriter.java | 110 +- .../inject/writer/BeanDefinitionVisitor.java | 4 +- .../inject/writer/BeanDefinitionWriter.java | 6210 ++++++++--------- .../inject/writer/DispatchWriter.java | 959 +-- .../writer/EvaluatedExpressionProcessor.java | 20 +- .../ExecutableMethodsDefinitionWriter.java | 491 +- .../inject/writer/ExpressionsUtils.java | 171 + .../inject/writer/MethodGenUtils.java | 296 + .../inject/writer/StringSwitchWriter.java | 123 - .../micronaut/inject/writer/WriterUtils.java | 323 - .../core/reflect/ReflectionUtils.java | 22 + .../GraalReflectionMetadataWriter.java | 101 +- .../reflect/GraalTypeElementVisitor.java | 3 +- gradle/libs.versions.toml | 3 + .../test/AbstractBeanDefinitionSpec.groovy | 10 +- .../ast/groovy/InjectTransform.groovy | 3 +- .../test/AbstractTypeElementSpec.groovy | 11 +- .../PackageConfigurationInjectProcessor.java | 3 +- .../AnnotationMetadataWriterSpec.groovy | 2 + .../AbstractInitializableBeanDefinition.java | 6 +- ...bstractInitializableBeanIntrospection.java | 30 + 34 files changed, 7332 insertions(+), 9149 deletions(-) create mode 100644 core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/beans/visitor/AptClassWriter.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/AbstractAnnotationMetadataWriter.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/AbstractClassFileWriter.java create mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/ArrayAwareSignatureWriter.java create mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java create mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/StringSwitchWriter.java delete mode 100644 core-processor/src/main/java/io/micronaut/inject/writer/WriterUtils.java diff --git a/core-processor/build.gradle.kts b/core-processor/build.gradle.kts index bb130f6632a..3947e6320d9 100644 --- a/core-processor/build.gradle.kts +++ b/core-processor/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { exclude(group = "com.google.guava", module = "guava") } implementation(projects.micronautCoreReactive) + api(libs.sourcegen.bytecode.generator) compileOnly(libs.managed.kotlin.stdlib.jdk8) } diff --git a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java index adcc7348b9f..96e3b564e79 100644 --- a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java +++ b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java @@ -35,12 +35,12 @@ import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.AnnotationUtil; import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArrayUtils; -import io.micronaut.core.util.Toggleable; import io.micronaut.core.value.OptionalValues; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.ExecutableMethod; @@ -56,31 +56,37 @@ import io.micronaut.inject.ast.TypedElement; import io.micronaut.inject.configuration.ConfigurationMetadataBuilder; import io.micronaut.inject.processing.JavaModelUtils; +import io.micronaut.inject.qualifiers.Qualified; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.inject.writer.AbstractClassFileWriter; +import io.micronaut.inject.writer.ArgumentExpUtils; import io.micronaut.inject.writer.BeanDefinitionWriter; +import io.micronaut.inject.writer.ClassOutputWriter; import io.micronaut.inject.writer.ClassWriterOutputVisitor; import io.micronaut.inject.writer.ExecutableMethodsDefinitionWriter; import io.micronaut.inject.writer.OriginatingElements; import io.micronaut.inject.writer.ProxyingBeanDefinitionVisitor; -import io.micronaut.inject.writer.WriterUtils; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; +import io.micronaut.inject.writer.MethodGenUtils; +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; +import io.micronaut.sourcegen.model.VariableDef; import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; +import javax.lang.model.element.Modifier; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -91,6 +97,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static io.micronaut.core.annotation.AnnotationUtil.ZERO_ANNOTATION_VALUES; import static io.micronaut.inject.ast.ParameterElement.ZERO_PARAMETER_ELEMENTS; @@ -102,93 +109,111 @@ * @since 1.0 */ @Internal -public class AopProxyWriter extends AbstractClassFileWriter implements ProxyingBeanDefinitionVisitor, Toggleable { +public class AopProxyWriter implements ProxyingBeanDefinitionVisitor, ClassOutputWriter { public static final int ADDITIONAL_PARAMETERS_COUNT = 5; - private static final int MAX_LOCALS = 3; - - private static final Method METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT = Method.getMethod(ReflectionUtils.getRequiredInternalMethod( - DefaultBeanContext.class, - "getProxyTargetBean", - BeanResolutionContext.class, - BeanDefinition.class, - Argument.class, - Qualifier.class - )); - - private static final Method METHOD_GET_PROXY_BEAN_DEFINITION = Method.getMethod(ReflectionUtils.getRequiredInternalMethod( - BeanDefinitionRegistry.class, - "getProxyTargetBeanDefinition", - Argument.class, - Qualifier.class - )); - - private static final Method METHOD_HAS_CACHED_INTERCEPTED_METHOD = Method.getMethod(ReflectionUtils.getRequiredInternalMethod( - InterceptedProxy.class, - "hasCachedInterceptedTarget" - )); - - private static final Method METHOD_BEAN_DEFINITION_GET_REQUIRED_METHOD = Method.getMethod(ReflectionUtils.getRequiredInternalMethod( + private static final Method METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT = ReflectionUtils.getRequiredInternalMethod( + DefaultBeanContext.class, + "getProxyTargetBean", + BeanResolutionContext.class, + BeanDefinition.class, + Argument.class, + Qualifier.class + ); + + private static final Method METHOD_GET_PROXY_BEAN_DEFINITION = ReflectionUtils.getRequiredInternalMethod( + BeanDefinitionRegistry.class, + "getProxyTargetBeanDefinition", + Argument.class, + Qualifier.class + ); + + private static final Method METHOD_INTERCEPTED_TARGET = ReflectionUtils.getRequiredInternalMethod( + InterceptedProxy.class, + "interceptedTarget" + ); + + private static final Method METHOD_HAS_CACHED_INTERCEPTED_METHOD = ReflectionUtils.getRequiredInternalMethod( + InterceptedProxy.class, + "hasCachedInterceptedTarget" + ); + + private static final Method METHOD_BEAN_DEFINITION_GET_REQUIRED_METHOD = ReflectionUtils.getRequiredInternalMethod( BeanDefinition.class, "getRequiredMethod", String.class, Class[].class - )); + ); + + private static final Method GET_READ_LOCK_METHOD = ReflectionUtils.getRequiredInternalMethod( + ReadWriteLock.class, + "readLock" + ); + + private static final Method GET_WRITE_LOCK_METHOD = ReflectionUtils.getRequiredInternalMethod( + ReadWriteLock.class, + "writeLock" + ); + + private static final Method LOCK_METHOD = ReflectionUtils.getRequiredInternalMethod( + Lock.class, + "lock" + ); + + private static final Method UNLOCK_METHOD = ReflectionUtils.getRequiredInternalMethod( + Lock.class, + "unlock" + ); + + private static final Method SWAP_METHOD = ReflectionUtils.getRequiredInternalMethod( + HotSwappableInterceptedProxy.class, + "swap", + Object.class + ); + + private static final Method WITH_QUALIFIER_METHOD = ReflectionUtils.getRequiredInternalMethod( + Qualified.class, + "$withBeanQualifier", + Qualifier.class + ); - private static final Type FIELD_TYPE_INTERCEPTORS = Type.getType(Interceptor[][].class); - private static final Type TYPE_INTERCEPTOR_CHAIN = Type.getType(InterceptorChain.class); - private static final Type TYPE_METHOD_INTERCEPTOR_CHAIN = Type.getType(MethodInterceptorChain.class); private static final String FIELD_TARGET = "$target"; private static final String FIELD_BEAN_RESOLUTION_CONTEXT = "$beanResolutionContext"; private static final String FIELD_READ_WRITE_LOCK = "$target_rwl"; - private static final Type TYPE_READ_WRITE_LOCK = Type.getType(ReentrantReadWriteLock.class); private static final String FIELD_READ_LOCK = "$target_rl"; private static final String FIELD_WRITE_LOCK = "$target_wl"; - private static final Type TYPE_LOCK = Type.getType(Lock.class); - private static final Type TYPE_BEAN_DEFINITION = Type.getType(BeanDefinition.class); - private static final Type TYPE_BEAN_LOCATOR = Type.getType(BeanLocator.class); - private static final Type TYPE_DEFAULT_BEAN_CONTEXT = Type.getType(DefaultBeanContext.class); - private static final Type TYPE_BEAN_DEFINITION_REGISTRY = Type.getType(BeanDefinitionRegistry.class); - - private static final Method METHOD_PROXY_TARGET_TYPE = Method.getMethod(ReflectionUtils.getRequiredInternalMethod(ProxyBeanDefinition.class, "getTargetDefinitionType")); - private static final Method METHOD_PROXY_TARGET_CLASS = Method.getMethod(ReflectionUtils.getRequiredInternalMethod(ProxyBeanDefinition.class, "getTargetType")); + private static final Method RESOLVE_INTRODUCTION_INTERCEPTORS_METHOD = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "resolveIntroductionInterceptors", InterceptorRegistry.class, ExecutableMethod.class, List.class); - private static final java.lang.reflect.Method RESOLVE_INTRODUCTION_INTERCEPTORS_METHOD = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "resolveIntroductionInterceptors", InterceptorRegistry.class, ExecutableMethod.class, List.class); + private static final Method RESOLVE_AROUND_INTERCEPTORS_METHOD = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "resolveAroundInterceptors", InterceptorRegistry.class, ExecutableMethod.class, List.class); - private static final java.lang.reflect.Method RESOLVE_AROUND_INTERCEPTORS_METHOD = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "resolveAroundInterceptors", InterceptorRegistry.class, ExecutableMethod.class, List.class); - - private static final Constructor CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN = ReflectionUtils.findConstructor(MethodInterceptorChain.class, Interceptor[].class, Object.class, ExecutableMethod.class, Object[].class).orElseThrow(() -> - new IllegalStateException("new MethodInterceptorChain(..) constructor not found. Incompatible version of Micronaut?") + private static final Constructor CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN = ReflectionUtils.findConstructor(MethodInterceptorChain.class, Interceptor[].class, Object.class, ExecutableMethod.class, Object[].class).orElseThrow(() -> + new IllegalStateException("new MethodInterceptorChain(..) constructor not found. Incompatible version of Micronaut?") ); - private static final Constructor CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS = ReflectionUtils.findConstructor(MethodInterceptorChain.class, Interceptor[].class, Object.class, ExecutableMethod.class).orElseThrow(() -> - new IllegalStateException("new MethodInterceptorChain(..) constructor not found. Incompatible version of Micronaut?") + private static final Constructor CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS = ReflectionUtils.findConstructor(MethodInterceptorChain.class, Interceptor[].class, Object.class, ExecutableMethod.class).orElseThrow(() -> + new IllegalStateException("new MethodInterceptorChain(..) constructor not found. Incompatible version of Micronaut?") ); private static final String INTERCEPTORS_PARAMETER = "$interceptors"; - private static final java.lang.reflect.Method METHOD_PROCEED = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "proceed"); + private static final Method METHOD_PROCEED = ReflectionUtils.getRequiredInternalMethod(InterceptorChain.class, "proceed"); + + private static final Method COPY_BEAN_CONTEXT_METHOD = ReflectionUtils.getRequiredMethod(BeanResolutionContext.class, "copy"); private static final String FIELD_INTERCEPTORS = "$interceptors"; private static final String FIELD_BEAN_LOCATOR = "$beanLocator"; private static final String FIELD_BEAN_QUALIFIER = "$beanQualifier"; private static final String FIELD_PROXY_METHODS = "$proxyMethods"; private static final String FIELD_PROXY_BEAN_DEFINITION = "$proxyBeanDefinition"; - private static final Type FIELD_TYPE_PROXY_METHODS = Type.getType(ExecutableMethod[].class); - private static final Type EXECUTABLE_METHOD_TYPE = Type.getType(ExecutableMethod.class); - private static final Type INTERCEPTOR_ARRAY_TYPE = Type.getType(Interceptor[].class); private final String packageName; private final String targetClassShortName; - private final ClassWriter classWriter; private final String targetClassFullName; private final String proxyFullName; private final BeanDefinitionWriter proxyBeanDefinitionWriter; - private final String proxyInternalName; private final Set> interceptorBinding; private final Set interfaceTypes; - private final Type proxyType; private final boolean hotswap; private final boolean lazy; private final boolean cacheLazyTarget; @@ -196,14 +221,12 @@ public class AopProxyWriter extends AbstractClassFileWriter implements ProxyingB private final BeanDefinitionWriter parentWriter; private final boolean isIntroduction; private final boolean implementInterface; - private boolean isProxyTarget; + private final boolean isProxyTarget; - private MethodVisitor constructorWriter; private final List proxiedMethods = new ArrayList<>(); private final Set proxiedMethodsRefSet = new HashSet<>(); private final List proxyTargetMethods = new ArrayList<>(); private int proxyMethodCount = 0; - private GeneratorAdapter constructorGenerator; private int interceptorsListArgumentIndex; private int beanResolutionContextArgumentIndex = -1; private int beanContextArgumentIndex = -1; @@ -213,17 +236,25 @@ public class AopProxyWriter extends AbstractClassFileWriter implements ProxyingB private boolean constructorRequiresReflection; private MethodElement declaredConstructor; private MethodElement newConstructor; - private String newConstructorSignature; + private MethodElement realConstructor; private List> superConstructorParametersBinding; private ParameterElement qualifierParameter; private ParameterElement interceptorsListParameter; private VisitorContext visitorContext; + private final OriginatingElements originatingElements; + + private final ClassDef.ClassDefBuilder proxyBuilder; + private final FieldDef interceptorsField; + private final FieldDef proxyMethodsField; + private FieldDef targetField; + /** *

Constructs a new {@link AopProxyWriter} for the given parent {@link BeanDefinitionWriter} and starting interceptors types.

* *

Additional {@link Interceptor} types can be added downstream with {@link #visitInterceptorBinding(AnnotationValue[])} .

- * @param parent The parent {@link BeanDefinitionWriter} + * + * @param parent The parent {@link BeanDefinitionWriter} * @param settings optional setting * @param visitorContext The visitor context * @param interceptorBinding The interceptor binding of the {@link Interceptor} instances to be injected @@ -232,7 +263,9 @@ public AopProxyWriter(BeanDefinitionWriter parent, OptionalValues settings, VisitorContext visitorContext, AnnotationValue... interceptorBinding) { - super(parent.getOriginatingElements()); + + this.originatingElements = OriginatingElements.of(parent.getOriginatingElements()); + this.isIntroduction = false; this.implementInterface = true; this.parentWriter = parent; @@ -245,27 +278,48 @@ public AopProxyWriter(BeanDefinitionWriter parent, this.packageName = parent.getPackageName(); this.targetClassShortName = parent.getBeanSimpleName(); this.targetClassFullName = packageName + '.' + targetClassShortName; - this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); this.proxyFullName = parent.getBeanDefinitionName() + PROXY_SUFFIX; - this.proxyInternalName = getInternalName(this.proxyFullName); - this.proxyType = getTypeReferenceForName(proxyFullName); this.interceptorBinding = toInterceptorBindingMap(interceptorBinding); this.interfaceTypes = Collections.emptySet(); final ClassElement aopElement = ClassElement.of(proxyFullName, isInterface, parent.getAnnotationMetadata()); this.proxyBeanDefinitionWriter = new BeanDefinitionWriter( - aopElement, - parent, - visitorContext + aopElement, + parent, + visitorContext ); proxyBeanDefinitionWriter.setRequiresMethodProcessing(parent.requiresMethodProcessing()); - startClass(classWriter, getInternalName(proxyFullName), getTypeReferenceForName(targetClassFullName)); proxyBeanDefinitionWriter.setInterceptedType(targetClassFullName); + + proxyBuilder = ClassDef.builder(proxyFullName); + + interceptorsField = FieldDef.builder(FIELD_INTERCEPTORS, Interceptor[][].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + proxyBuilder.addField(interceptorsField); + + proxyMethodsField = FieldDef.builder(FIELD_PROXY_METHODS, ExecutableMethod[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + proxyBuilder.addField(proxyMethodsField); + + if (cacheLazyTarget || hotswap) { + targetField = FieldDef.builder(FIELD_TARGET, ClassTypeDef.of(targetClassFullName)).addModifiers(Modifier.PRIVATE).build(); + proxyBuilder.addField(targetField); + } else if (!lazy) { + targetField = FieldDef.builder(FIELD_TARGET, ClassTypeDef.of(targetClassFullName)).addModifiers(Modifier.PRIVATE, Modifier.FINAL).build(); + proxyBuilder.addField(targetField); + } + + this.visitorContext = visitorContext; } /** * Constructs a new {@link AopProxyWriter} for the purposes of writing {@link io.micronaut.aop.Introduction} advise. - * @param packageName The package name + * + * @param packageName The package name * @param className The class name * @param isInterface Is the target of the advice an interface * @param originatingElement The originating element @@ -287,7 +341,8 @@ public AopProxyWriter(String packageName, /** * Constructs a new {@link AopProxyWriter} for the purposes of writing {@link io.micronaut.aop.Introduction} advise. - * @param packageName The package name + * + * @param packageName The package name * @param className The class name * @param isInterface Is the target of the advice an interface * @param implementInterface Whether the interface should be implemented. If false the {@code interfaceTypes} argument should contain at least one entry @@ -306,7 +361,7 @@ public AopProxyWriter(String packageName, ClassElement[] interfaceTypes, VisitorContext visitorContext, AnnotationValue... interceptorBinding) { - super(OriginatingElements.of(originatingElement)); + this.originatingElements = OriginatingElements.of(originatingElement); this.isIntroduction = true; this.implementInterface = implementInterface; @@ -316,6 +371,7 @@ public AopProxyWriter(String packageName, this.packageName = packageName; this.isInterface = isInterface; + this.isProxyTarget = false; this.hotswap = false; this.lazy = false; this.cacheLazyTarget = false; @@ -323,20 +379,17 @@ public AopProxyWriter(String packageName, this.targetClassFullName = packageName + '.' + targetClassShortName; this.parentWriter = null; this.proxyFullName = targetClassFullName + PROXY_SUFFIX; - this.proxyInternalName = getInternalName(this.proxyFullName); - this.proxyType = getTypeReferenceForName(proxyFullName); this.interceptorBinding = toInterceptorBindingMap(interceptorBinding); this.interfaceTypes = interfaceTypes != null ? new LinkedHashSet<>(Arrays.asList(interfaceTypes)) : Collections.emptySet(); - this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); ClassElement aopElement = ClassElement.of( - proxyFullName, - isInterface, - annotationMetadata + proxyFullName, + isInterface, + annotationMetadata ); this.proxyBeanDefinitionWriter = new BeanDefinitionWriter( - aopElement, - this, - visitorContext + aopElement, + this, + visitorContext ); if (isInterface) { if (implementInterface) { @@ -345,11 +398,27 @@ public AopProxyWriter(String packageName, } else { proxyBeanDefinitionWriter.setInterceptedType(targetClassFullName); } - startClass(classWriter, proxyInternalName, getTypeReferenceForName(targetClassFullName)); + + proxyBuilder = ClassDef.builder(proxyFullName); + + interceptorsField = FieldDef.builder(FIELD_INTERCEPTORS, Interceptor[][].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + proxyBuilder.addField(interceptorsField); + + proxyMethodsField = FieldDef.builder(FIELD_PROXY_METHODS, ExecutableMethod[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + proxyBuilder.addField(proxyMethodsField); + + this.visitorContext = visitorContext; } /** * Find the interceptors list constructor parameter index. + * * @param parameters The constructor parameters * @return the index */ @@ -373,18 +442,8 @@ public boolean isProxyTarget() { } @Override - protected void startClass(ClassVisitor classWriter, String className, Type superType) { - String[] interfaces = getImplementedInterfaceInternalNames(); - classWriter.visit(V17, ACC_SYNTHETIC, className, null, !isInterface ? superType.getInternalName() : null, interfaces); - - classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - - classWriter.visitField(ACC_FINAL | ACC_PRIVATE, FIELD_INTERCEPTORS, FIELD_TYPE_INTERCEPTORS.getDescriptor(), null, null); - classWriter.visitField(ACC_FINAL | ACC_PRIVATE, FIELD_PROXY_METHODS, FIELD_TYPE_PROXY_METHODS.getDescriptor(), null, null); - } - - private String[] getImplementedInterfaceInternalNames() { - return interfaceTypes.stream().map(o -> JavaModelUtils.getTypeReference(o).getInternalName()).toArray(String[]::new); + public Element getOriginatingElement() { + return originatingElements.getOriginatingElements()[0]; } @Override @@ -461,40 +520,41 @@ public String getBeanDefinitionName() { */ @Override public void visitBeanDefinitionConstructor( - MethodElement constructor, - boolean requiresReflection, - VisitorContext visitorContext) { + MethodElement constructor, + boolean requiresReflection, + VisitorContext visitorContext) { this.constructorRequiresReflection = requiresReflection; this.declaredConstructor = constructor; this.visitorContext = visitorContext; - io.micronaut.core.annotation.AnnotationValue[] interceptorTypes = - InterceptedMethodUtil.resolveInterceptorBinding(constructor.getAnnotationMetadata(), InterceptorKind.AROUND_CONSTRUCT); + AnnotationValue[] interceptorTypes = + InterceptedMethodUtil.resolveInterceptorBinding(constructor.getAnnotationMetadata(), InterceptorKind.AROUND_CONSTRUCT); visitInterceptorBinding(interceptorTypes); } @Override public void visitDefaultConstructor(AnnotationMetadata annotationMetadata, VisitorContext visitorContext) { this.constructorRequiresReflection = false; - ClassElement classElement = ClassElement.of(proxyType.getClassName()); + this.visitorContext = visitorContext; + ClassElement classElement = ClassElement.of(proxyFullName); this.declaredConstructor = MethodElement.of( - classElement, - annotationMetadata, - classElement, - classElement, - "" + classElement, + annotationMetadata, + classElement, + classElement, + "" ); } private void initConstructor(MethodElement constructor) { final ClassElement interceptorList = ClassElement.of(List.class, AnnotationMetadata.EMPTY_METADATA, Collections.singletonMap( - "E", ClassElement.of(BeanRegistration.class, AnnotationMetadata.EMPTY_METADATA, Collections.singletonMap( - "T", ClassElement.of(Interceptor.class) - )) + "E", ClassElement.of(BeanRegistration.class, AnnotationMetadata.EMPTY_METADATA, Collections.singletonMap( + "T", ClassElement.of(Interceptor.class) + )) )); this.qualifierParameter = ParameterElement.of(Qualifier.class, "$qualifier"); this.interceptorsListParameter = ParameterElement.of(interceptorList, INTERCEPTORS_PARAMETER); ParameterElement interceptorRegistryParameter = ParameterElement.of(ClassElement.of(InterceptorRegistry.class), "$interceptorRegistry"); - ClassElement proxyClass = ClassElement.of(proxyType.getClassName()); + ClassElement proxyClass = ClassElement.of(proxyFullName); superConstructorParametersBinding = new ArrayList<>(); ParameterElement[] constructorParameters = constructor.getParameters(); List newConstructorParameters = new ArrayList<>(constructorParameters.length + 5); @@ -512,9 +572,9 @@ private void initConstructor(MethodElement constructor) { newConstructorParameters.add(interceptorsListParameter); newConstructorParameters.add(interceptorRegistryParameter); superConstructorParameterIndex += 5; // Skip internal parameters - if (WriterUtils.hasKotlinDefaultsParameters(List.of(constructorParameters))) { + if (MethodGenUtils.hasKotlinDefaultsParameters(List.of(constructorParameters))) { List realNewConstructorParameters = new ArrayList<>(newConstructorParameters); - int count = WriterUtils.calculateNumberOfKotlinDefaultsMasks(List.of(constructorParameters)); + int count = MethodGenUtils.calculateNumberOfKotlinDefaultsMasks(List.of(constructorParameters)); for (int j = 0; j < count; j++) { ParameterElement mask = ParameterElement.of(PrimitiveElement.INT, "mask" + j); realNewConstructorParameters.add(mask); @@ -523,18 +583,28 @@ private void initConstructor(MethodElement constructor) { ParameterElement marker = ParameterElement.of(ClassElement.of("kotlin.jvm.internal.DefaultConstructorMarker"), "marker"); realNewConstructorParameters.add(marker); superConstructorParametersBinding.add(Map.entry(marker, superConstructorParameterIndex)); - this.newConstructorSignature = getConstructorDescriptor(realNewConstructorParameters); - } else { - this.newConstructorSignature = getConstructorDescriptor(newConstructorParameters); - } - this.newConstructor = MethodElement.of( + + this.realConstructor = MethodElement.of( proxyClass, constructor.getAnnotationMetadata(), proxyClass, proxyClass, "", - newConstructorParameters.toArray(ZERO_PARAMETER_ELEMENTS) + realNewConstructorParameters.toArray(ZERO_PARAMETER_ELEMENTS) + ); + } + this.newConstructor = MethodElement.of( + proxyClass, + constructor.getAnnotationMetadata(), + proxyClass, + proxyClass, + "", + newConstructorParameters.toArray(ZERO_PARAMETER_ELEMENTS) ); + if (realConstructor == null) { + realConstructor = newConstructor; + } + this.beanResolutionContextArgumentIndex = newConstructorParameters.indexOf(beanResolutionContext); this.beanContextArgumentIndex = newConstructorParameters.indexOf(beanContext); this.qualifierIndex = newConstructorParameters.indexOf(qualifierParameter); @@ -557,8 +627,8 @@ public String getBeanDefinitionReferenceClassName() { public void visitIntroductionMethod(TypedElement declaringBean, MethodElement methodElement) { visitAroundMethod( - declaringBean, - methodElement + declaringBean, + methodElement ); } @@ -577,25 +647,27 @@ public void visitAroundMethod(TypedElement beanType, boolean isVoidReturn = isPrimitive && returnTypeObject.equals(Type.VOID_TYPE); final Optional overridden = methodElement.getOwningType() - .getEnclosedElement(ElementQuery.ALL_METHODS - .onlyInstance() - .filter(el -> el.getName().equals(methodElement.getName()) && el.overrides(methodElement))); + .getEnclosedElement(ElementQuery.ALL_METHODS + .onlyInstance() + .filter(el -> el.getName().equals(methodElement.getName()) && el.overrides(methodElement))); if (overridden.isPresent()) { MethodElement overriddenBy = overridden.get(); String methodElementKey = methodElement.getName() + - Arrays.stream(methodElement.getSuspendParameters()) - .map(p -> toTypeString(p.getType())) - .collect(Collectors.joining(",")); + Arrays.stream(methodElement.getSuspendParameters()) + .map(p -> toTypeString(p.getType())) + .collect(Collectors.joining(",")); String overriddenByKey = overriddenBy.getName() + - Arrays.stream(methodElement.getSuspendParameters()) - .map(p -> toTypeString(p.getGenericType())) - .collect(Collectors.joining(",")); + Arrays.stream(methodElement.getSuspendParameters()) + .map(p -> toTypeString(p.getGenericType())) + .collect(Collectors.joining(",")); if (!methodElementKey.equals(overriddenByKey)) { - buildMethodDelegate(methodElement, overriddenBy, isVoidReturn); + proxyBuilder.addMethod(MethodDef.override(methodElement) + .build((aThis, methodParameters) -> aThis.superRef().invoke(overriddenBy, methodParameters).returning()) + ); return; } } @@ -603,7 +675,6 @@ public void visitAroundMethod(TypedElement beanType, String methodName = methodElement.getName(); List argumentTypeList = Arrays.asList(methodElement.getSuspendParameters()); int argumentCount = argumentTypeList.size(); - final Type declaringTypeReference = JavaModelUtils.getTypeReference(beanType); MethodRef methodKey = new MethodRef(methodName, argumentTypeList, returnTypeObject); if (!proxiedMethodsRefSet.contains(methodKey)) { @@ -618,29 +689,25 @@ public void visitAroundMethod(TypedElement beanType, interceptedProxyClassName = proxyFullName; interceptedProxyBridgeMethodName = "$$access$$" + methodName; - String bridgeDesc = getMethodDescriptor(returnType, argumentTypeList); - // now build a bridge to invoke the original method - MethodVisitor bridgeWriter = classWriter.visitMethod(ACC_SYNTHETIC, - interceptedProxyBridgeMethodName, bridgeDesc, null, null); - GeneratorAdapter bridgeGenerator = new GeneratorAdapter(bridgeWriter, ACC_SYNTHETIC, interceptedProxyBridgeMethodName, bridgeDesc); - bridgeGenerator.loadThis(); - for (int i = 0; i < argumentTypeList.size(); i++) { - bridgeGenerator.loadArg(i); - } - String desc = getMethodDescriptor(returnType, argumentTypeList); - bridgeWriter.visitMethodInsn(INVOKESPECIAL, declaringTypeReference.getInternalName(), methodName, desc, this.isInterface && methodElement.isDefault()); - bridgeGenerator.returnValue(); - bridgeGenerator.endMethod(); + ClassTypeDef declaringTypeDef = (ClassTypeDef) TypeDef.erasure(methodElement.getOwningType()); + proxyBuilder.addMethod( + MethodDef.builder(interceptedProxyBridgeMethodName) + .addModifiers(Modifier.PUBLIC) + .addParameters(argumentTypeList.stream().map(p -> TypeDef.erasure(p.getType())).toList()) + .returns(TypeDef.erasure(returnType)) + .build((aThis, methodParameters) -> aThis.superRef(declaringTypeDef) + .invoke(methodElement, methodParameters).returning()) + ); } } BeanDefinitionWriter beanDefinitionWriter = parentWriter == null ? proxyBeanDefinitionWriter : parentWriter; int methodIndex = beanDefinitionWriter.visitExecutableMethod( - beanType, - methodElement, - interceptedProxyClassName, - interceptedProxyBridgeMethodName + beanType, + methodElement, + interceptedProxyClassName, + interceptedProxyBridgeMethodName ); int index = proxyMethodCount++; @@ -654,101 +721,67 @@ public void visitAroundMethod(TypedElement beanType, } private void buildMethodOverride( - TypedElement returnType, - String methodName, - int index, - List argumentTypeList, - int argumentCount, - boolean isVoidReturn) { + TypedElement returnType, + String methodName, + int index, + List argumentTypeList, + int argumentCount, + boolean isVoidReturn) { // override the original method - String desc = getMethodDescriptor(returnType, argumentTypeList); - MethodVisitor overridden = classWriter.visitMethod(ACC_PUBLIC, methodName, desc, null, null); - GeneratorAdapter overriddenMethodGenerator = new GeneratorAdapter(overridden, ACC_PUBLIC, methodName, desc); - - // instantiate the MethodInterceptorChain - - // ie InterceptorChain chain = new MethodInterceptorChain(interceptors, this, executableMethod, name); - overriddenMethodGenerator.newInstance(TYPE_METHOD_INTERCEPTOR_CHAIN); - overriddenMethodGenerator.dup(); - - // 1st argument: interceptors - // this.interceptors[0]; - overriddenMethodGenerator.loadThis(); - overriddenMethodGenerator.getField(proxyType, FIELD_INTERCEPTORS, FIELD_TYPE_INTERCEPTORS); - overriddenMethodGenerator.push(index); - overriddenMethodGenerator.visitInsn(AALOAD); - - // 2nd argument: this or target - overriddenMethodGenerator.loadThis(); - if (isProxyTarget) { - if (hotswap || lazy) { - overriddenMethodGenerator.invokeInterface(Type.getType(InterceptedProxy.class), Method.getMethod("java.lang.Object interceptedTarget()")); - } else { - overriddenMethodGenerator.getField(proxyType, FIELD_TARGET, getTypeReferenceForName(targetClassFullName)); - } - } - - // 3rd argument: the executable method - // this.proxyMethods[0]; - overriddenMethodGenerator.loadThis(); - overriddenMethodGenerator.getField(proxyType, FIELD_PROXY_METHODS, FIELD_TYPE_PROXY_METHODS); - overriddenMethodGenerator.push(index); - overriddenMethodGenerator.visitInsn(AALOAD); - - if (argumentCount > 0) { - // fourth argument: array of the argument values - overriddenMethodGenerator.push(argumentCount); - overriddenMethodGenerator.newArray(Type.getType(Object.class)); - - // now pass the remaining arguments from the original method - for (int i = 0; i < argumentCount; i++) { - overriddenMethodGenerator.dup(); - ParameterElement argType = argumentTypeList.get(i); - overriddenMethodGenerator.push(i); - overriddenMethodGenerator.loadArg(i); - pushBoxPrimitiveIfNecessary(argType, overriddenMethodGenerator); - overriddenMethodGenerator.visitInsn(AASTORE); - } - - // invoke MethodInterceptorChain constructor with parameters - overriddenMethodGenerator.invokeConstructor(TYPE_METHOD_INTERCEPTOR_CHAIN, Method.getMethod(CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN)); - } else { - // invoke MethodInterceptorChain constructor without parameters - overriddenMethodGenerator.invokeConstructor(TYPE_METHOD_INTERCEPTOR_CHAIN, Method.getMethod(CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS)); - } + proxyBuilder.addMethod( + MethodDef.builder(methodName) + .addModifiers(Modifier.PUBLIC) + .overrides() + .returns(TypeDef.erasure(returnType)) + .addParameters(argumentTypeList.stream().map(p -> TypeDef.erasure(p.getType())).toList()) + .build((aThis, methodParameters) -> { + + ExpressionDef targetArgument; + if (isProxyTarget) { + if (hotswap || lazy) { + targetArgument = aThis.invoke(METHOD_INTERCEPTED_TARGET); + } else { + targetArgument = aThis.field(targetField); + } + } else { + targetArgument = aThis; + } - overriddenMethodGenerator.invokeVirtual(TYPE_INTERCEPTOR_CHAIN, Method.getMethod(METHOD_PROCEED)); - if (isVoidReturn) { - overriddenMethodGenerator.pop(); - } else { - pushCastToType(overriddenMethodGenerator, returnType); - } - overriddenMethodGenerator.returnValue(); - overriddenMethodGenerator.endMethod(); - } - - private void buildMethodDelegate(MethodElement methodElement, MethodElement overriddenBy, boolean isVoidReturn) { - String desc = getMethodDescriptor(methodElement.getReturnType().getType(), Arrays.asList(methodElement.getSuspendParameters())); - MethodVisitor overridden = classWriter.visitMethod(ACC_PUBLIC, methodElement.getName(), desc, null, null); - GeneratorAdapter overriddenMethodGenerator = new GeneratorAdapter(overridden, ACC_PUBLIC, methodElement.getName(), desc); - overriddenMethodGenerator.loadThis(); - int i = 0; - for (ParameterElement param : overriddenBy.getSuspendParameters()) { - overriddenMethodGenerator.loadArg(i++); - pushCastToType(overriddenMethodGenerator, param.getGenericType()); - } - overriddenMethodGenerator.visitMethodInsn(INVOKESPECIAL, - proxyType.getInternalName(), - overriddenBy.getName(), - getMethodDescriptor(overriddenBy.getReturnType().getType(), Arrays.asList(overriddenBy.getSuspendParameters())), - this.isInterface && overriddenBy.isDefault()); - - if (!isVoidReturn && !overriddenBy.isSuspend()) { - ClassElement returnType = overriddenBy.getReturnType(); - pushCastToType(overriddenMethodGenerator, returnType); - } - overriddenMethodGenerator.returnValue(); - overriddenMethodGenerator.endMethod(); + ExpressionDef.InvokeInstanceMethod invocation; + if (argumentCount > 0) { + // invoke MethodInterceptorChain constructor with parameters + invocation = ClassTypeDef.of(MethodInterceptorChain.class).instantiate( + CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN, + + // 1st argument: interceptors + aThis.field(interceptorsField).arrayElement(index), + // 2nd argument: this or target + targetArgument, + // 3rd argument: the executable method + aThis.field(proxyMethodsField).arrayElement(index), + // 4th argument: array of the argument values + TypeDef.OBJECT.array().instantiate(methodParameters) + ).invoke(METHOD_PROCEED); + } else { + // invoke MethodInterceptorChain constructor without parameters + invocation = ClassTypeDef.of(MethodInterceptorChain.class).instantiate( + CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS, + + // 1st argument: interceptors + aThis.field(interceptorsField).arrayElement(index), + // 2nd argument: this or target + targetArgument, + // 3rd argument: the executable method + aThis.field(proxyMethodsField).arrayElement(index) + // fourth argument: array of the argument values + ).invoke(METHOD_PROCEED); + } + if (isVoidReturn) { + return invocation; + } + return invocation.returning(); + }) + ); } /** @@ -756,6 +789,20 @@ private void buildMethodDelegate(MethodElement methodElement, MethodElement over */ @Override public void visitBeanDefinitionEnd() { + ClassTypeDef targetType = ClassTypeDef.of(targetClassFullName); + + if (!isInterface) { + proxyBuilder.superclass(targetType); + } + List interfaces = new ArrayList<>(); + interfaceTypes.stream().map(TypeDef::erasure).forEach(interfaces::add); + if (isInterface && implementInterface) { + interfaces.add(targetType); + } + interfaces.forEach(proxyBuilder::addSuperinterface); + + proxyBuilder.addAnnotation(Generated.class); + if (declaredConstructor == null) { throw new IllegalStateException("The method visitBeanDefinitionConstructor(..) should be called at least once"); } else { @@ -766,441 +813,319 @@ public void visitBeanDefinitionEnd() { processAlreadyVisitedMethods(parentWriter); } - interceptorsListParameter.annotate(AnnotationUtil. ANN_INTERCEPTOR_BINDING_QUALIFIER, builder -> { + interceptorsListParameter.annotate(AnnotationUtil.ANN_INTERCEPTOR_BINDING_QUALIFIER, builder -> { final AnnotationValue[] interceptorBinding = this.interceptorBinding.toArray(ZERO_ANNOTATION_VALUES); builder.values(interceptorBinding); }); qualifierParameter.annotate(AnnotationUtil.NULLABLE); - ClassWriter proxyClassWriter = this.classWriter; - this.constructorWriter = proxyClassWriter.visitMethod( - ACC_PUBLIC, - CONSTRUCTOR_NAME, - newConstructorSignature, - null, - null); - - this.constructorGenerator = new GeneratorAdapter(constructorWriter, ACC_PUBLIC, CONSTRUCTOR_NAME, newConstructorSignature); - GeneratorAdapter proxyConstructorGenerator = this.constructorGenerator; - - proxyConstructorGenerator.loadThis(); - if (isInterface) { - proxyConstructorGenerator.invokeConstructor(TYPE_OBJECT, METHOD_DEFAULT_CONSTRUCTOR); - } else { - List arguments = new ArrayList<>(); - for (Map.Entry e : superConstructorParametersBinding) { - proxyConstructorGenerator.loadArg(e.getValue()); - arguments.add(e.getKey()); - } - proxyConstructorGenerator.invokeConstructor( - getTypeReferenceForName(targetClassFullName), - new Method(CONSTRUCTOR_NAME, getConstructorDescriptor(arguments)) - ); - } - proxyBeanDefinitionWriter.visitBeanDefinitionConstructor( - newConstructor, - constructorRequiresReflection, - visitorContext + newConstructor, + constructorRequiresReflection, + visitorContext ); - GeneratorAdapter targetDefinitionGenerator = null; - GeneratorAdapter targetTypeGenerator = null; if (parentWriter != null) { proxyBeanDefinitionWriter.visitBeanDefinitionInterface(ProxyBeanDefinition.class); - ClassVisitor pcw = proxyBeanDefinitionWriter.getClassWriter(); - targetDefinitionGenerator = new GeneratorAdapter(pcw.visitMethod(ACC_PUBLIC, - METHOD_PROXY_TARGET_TYPE.getName(), - METHOD_PROXY_TARGET_TYPE.getDescriptor(), - null, null - - ), ACC_PUBLIC, METHOD_PROXY_TARGET_TYPE.getName(), METHOD_PROXY_TARGET_TYPE.getDescriptor()); - targetDefinitionGenerator.loadThis(); - targetDefinitionGenerator.push(getTypeReferenceForName(parentWriter.getBeanDefinitionName())); - targetDefinitionGenerator.returnValue(); - - - targetTypeGenerator = new GeneratorAdapter(pcw.visitMethod(ACC_PUBLIC, - METHOD_PROXY_TARGET_CLASS.getName(), - METHOD_PROXY_TARGET_CLASS.getDescriptor(), - null, null - - ), ACC_PUBLIC, METHOD_PROXY_TARGET_CLASS.getName(), METHOD_PROXY_TARGET_CLASS.getDescriptor()); - targetTypeGenerator.loadThis(); - targetTypeGenerator.push(getTypeReferenceForName(parentWriter.getBeanTypeName())); - targetTypeGenerator.returnValue(); + proxyBeanDefinitionWriter.generateProxyReference(parentWriter.getBeanDefinitionName(), parentWriter.getBeanTypeName()); } - Class interceptedInterface = isIntroduction ? Introduced.class : Intercepted.class; - Type targetType = getTypeReferenceForName(targetClassFullName); - - // add the $beanLocator field if (isProxyTarget) { - // add the $proxyBeanDefinition field - proxyClassWriter.visitField( - ACC_PRIVATE | ACC_FINAL, - FIELD_PROXY_BEAN_DEFINITION, - TYPE_BEAN_DEFINITION.getDescriptor(), - null, - null - ); - - proxyClassWriter.visitField( - ACC_PRIVATE | ACC_FINAL, - FIELD_BEAN_LOCATOR, - TYPE_BEAN_LOCATOR.getDescriptor(), - null, - null - ); - - // add the $beanQualifier field - proxyClassWriter.visitField( - ACC_PRIVATE, - FIELD_BEAN_QUALIFIER, - Type.getType(Qualifier.class).getDescriptor(), - null, - null - ); - - writeWithQualifierMethod(proxyClassWriter); - if (!lazy || cacheLazyTarget) { - // add the $target field for the target bean - proxyClassWriter.visitField( - ACC_PRIVATE, - FIELD_TARGET, - targetType.getDescriptor(), - null, - null - ); - } - if (lazy) { - interceptedInterface = InterceptedProxy.class; - proxyClassWriter.visitField( - ACC_PRIVATE, - FIELD_BEAN_RESOLUTION_CONTEXT, - Type.getType(BeanResolutionContext.class).getDescriptor(), - null, - null - ); - } else { - interceptedInterface = hotswap ? HotSwappableInterceptedProxy.class : InterceptedProxy.class; - if (hotswap) { - // Add ReadWriteLock field - // private final ReentrantReadWriteLock $target_rwl = new ReentrantReadWriteLock(); - proxyClassWriter.visitField( - ACC_PRIVATE | ACC_FINAL, - FIELD_READ_WRITE_LOCK, - TYPE_READ_WRITE_LOCK.getDescriptor(), - null, null - ); - proxyConstructorGenerator.loadThis(); - pushNewInstance(proxyConstructorGenerator, TYPE_READ_WRITE_LOCK); - proxyConstructorGenerator.putField(proxyType, FIELD_READ_WRITE_LOCK, TYPE_READ_WRITE_LOCK); - - // Add Read Lock field - // private final Lock $target_rl = $target_rwl.readLock(); - proxyClassWriter.visitField( - ACC_PRIVATE | ACC_FINAL, - FIELD_READ_LOCK, - TYPE_LOCK.getDescriptor(), - null, null - ); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_READ_WRITE_LOCK, TYPE_READ_WRITE_LOCK); - proxyConstructorGenerator.invokeInterface( - Type.getType(ReadWriteLock.class), - Method.getMethod(Lock.class.getName() + " readLock()") - ); - proxyConstructorGenerator.putField(proxyType, FIELD_READ_LOCK, TYPE_LOCK); - - // Add Write Lock field - // private final Lock $target_wl = $target_rwl.writeLock(); - proxyClassWriter.visitField( - ACC_PRIVATE | ACC_FINAL, - FIELD_WRITE_LOCK, - Type.getDescriptor(Lock.class), - null, null - ); - - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_READ_WRITE_LOCK, TYPE_READ_WRITE_LOCK); - proxyConstructorGenerator.invokeInterface( - Type.getType(ReadWriteLock.class), - Method.getMethod(Lock.class.getName() + " writeLock()") - ); - proxyConstructorGenerator.putField(proxyType, FIELD_WRITE_LOCK, TYPE_LOCK); - } - } - // assign the bean locator - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.loadArg(beanContextArgumentIndex); - proxyConstructorGenerator.putField(proxyType, FIELD_BEAN_LOCATOR, TYPE_BEAN_LOCATOR); - - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.loadArg(qualifierIndex); - proxyConstructorGenerator.putField(proxyType, FIELD_BEAN_QUALIFIER, Type.getType(Qualifier.class)); - - proxyConstructorGenerator.loadThis(); - pushResolveProxyBeanDefinition(proxyConstructorGenerator, targetType); - proxyConstructorGenerator.putField(proxyType, FIELD_PROXY_BEAN_DEFINITION, TYPE_BEAN_DEFINITION); - - if (!lazy) { - proxyConstructorGenerator.loadThis(); - pushResolveProxyTargetBean(proxyConstructorGenerator, targetType); - proxyConstructorGenerator.putField(proxyType, FIELD_TARGET, targetType); - } else { - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.loadArg(beanResolutionContextArgumentIndex); - proxyConstructorGenerator.invokeInterface( - Type.getType(BeanResolutionContext.class), - Method.getMethod( - ReflectionUtils.getRequiredMethod(BeanResolutionContext.class, "copy") - ) - ); - proxyConstructorGenerator.putField(proxyType, FIELD_BEAN_RESOLUTION_CONTEXT, Type.getType(BeanResolutionContext.class)); - } - - // Write the Object interceptedTarget() method - writeInterceptedTargetMethod(proxyClassWriter, targetType); - - if (!lazy || cacheLazyTarget) { - // Write `boolean hasCachedInterceptedTarget()` - writeHasCachedInterceptedTargetMethod(proxyClassWriter, targetType); - } - - // Write the swap method - // e. T swap(T newInstance); - if (hotswap && !lazy) { - writeSwapMethod(proxyClassWriter, targetType); - } + generateProxyTarget(targetType); + } else { + proxyBuilder.addSuperinterface(TypeDef.of(isIntroduction ? Introduced.class : Intercepted.class)); + proxyBuilder.addMethod(MethodDef.constructor() + .addParameters(Arrays.stream(realConstructor.getParameters()).map(p -> TypeDef.erasure(p.getType())).toList()) + .build((aThis, methodParameters) -> StatementDef.multi( + invokeSuperConstructor(aThis, methodParameters), + initializeProxyMethodsAndInterceptors(aThis, methodParameters) + ))); } - String[] interfaces = getImplementedInterfaceInternalNames(); - if (isInterface && implementInterface) { - String[] adviceInterfaces = { - getInternalName(targetClassFullName), - Type.getInternalName(interceptedInterface) - }; - interfaces = ArrayUtils.concat(interfaces, adviceInterfaces); - } else { - String[] adviceInterfaces = {Type.getInternalName(interceptedInterface)}; - interfaces = ArrayUtils.concat(interfaces, adviceInterfaces); + for (Runnable fieldInjectionPoint : deferredInjectionPoints) { + fieldInjectionPoint.run(); } - proxyClassWriter.visit(V17, ACC_SYNTHETIC, - proxyInternalName, - null, - isInterface ? TYPE_OBJECT.getInternalName() : getTypeReferenceForName(targetClassFullName).getInternalName(), - interfaces); - - // set $proxyMethods field - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.push(proxyMethodCount); - proxyConstructorGenerator.newArray(EXECUTABLE_METHOD_TYPE); - proxyConstructorGenerator.putField( - proxyType, - FIELD_PROXY_METHODS, - FIELD_TYPE_PROXY_METHODS - ); - // set $interceptors field - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.push(proxyMethodCount); - proxyConstructorGenerator.newArray(INTERCEPTOR_ARRAY_TYPE); - proxyConstructorGenerator.putField( - proxyType, - FIELD_INTERCEPTORS, - FIELD_TYPE_INTERCEPTORS - ); + proxyBeanDefinitionWriter.visitBeanDefinitionEnd(); + } - // now initialize the held values - if (isProxyTarget) { - if (proxiedMethods.size() == proxyMethodCount) { + private void generateProxyTarget(ClassTypeDef targetType) { + List bodyBuilders = new ArrayList<>(); - Iterator iterator = proxyTargetMethods.iterator(); - for (int i = 0; i < proxyMethodCount; i++) { - MethodRef methodRef = iterator.next(); + FieldDef proxyBeanDefinitionField = FieldDef.builder(FIELD_PROXY_BEAN_DEFINITION, BeanDefinition.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + proxyBuilder.addField(proxyBeanDefinitionField); + bodyBuilders.add((aThis, methodParameters) -> aThis.field(proxyBeanDefinitionField).assign( + methodParameters.get(beanContextArgumentIndex).invoke( + METHOD_GET_PROXY_BEAN_DEFINITION, + + // 1nd argument: the type + pushTargetArgument(targetType), + // 2rd argument: the qualifier + methodParameters.get(qualifierIndex) + ) + )); - // The following will initialize the array of $proxyMethod instances - // E.g. this.$proxyMethods[0] = $PARENT_BEAN.getRequiredMethod("test", new Class[]{String.class}); - proxyConstructorGenerator.loadThis(); + FieldDef beanQualifierField = FieldDef.builder(FIELD_BEAN_QUALIFIER, TypeDef.of(Qualifier.class)) + .addModifiers(Modifier.PRIVATE) + .build(); + proxyBuilder.addField(beanQualifierField); + proxyBuilder.addMethod(writeWithQualifierMethod(beanQualifierField)); + bodyBuilders.add((aThis, methodParameters) -> + aThis.field(beanQualifierField).assign(methodParameters.get(qualifierIndex))); - // Step 1: dereference the array - this.$proxyMethods[0] - proxyConstructorGenerator.getField(proxyType, FIELD_PROXY_METHODS, FIELD_TYPE_PROXY_METHODS); - proxyConstructorGenerator.push(i); + MethodDef interceptedTargetMethod; + if (lazy) { + proxyBuilder.addSuperinterface(TypeDef.of(InterceptedProxy.class)); - // Step 2: lookup the Method instance from the declaring type - // $proxyBeanDefinition.getMethod("test", new Class[]{String.class}); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_PROXY_BEAN_DEFINITION, TYPE_BEAN_DEFINITION); + FieldDef beanResolutionContextField = FieldDef.builder(FIELD_BEAN_RESOLUTION_CONTEXT, BeanResolutionContext.class) + .addModifiers(Modifier.PRIVATE) + .build(); - // Arguments are written as generic types, so we need to look for the method using the generic arguments - pushMethodNameAndTypesArguments(proxyConstructorGenerator, methodRef.name, methodRef.genericArgumentTypes); + proxyBuilder.addField(beanResolutionContextField); - proxyConstructorGenerator.invokeInterface( - TYPE_BEAN_DEFINITION, - METHOD_BEAN_DEFINITION_GET_REQUIRED_METHOD - ); + FieldDef beanLocatorField = FieldDef.builder(FIELD_BEAN_LOCATOR, BeanLocator.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); - // Step 3: store the result in the array - proxyConstructorGenerator.arrayStore(FIELD_TYPE_PROXY_METHODS); + proxyBuilder.addField(beanLocatorField); - // Step 4: Resolve the interceptors - // this.$interceptors[0] = InterceptorChain.resolveAroundInterceptors(this.$proxyMethods[0], var2); - pushResolveInterceptorsCall(proxyConstructorGenerator, i, isIntroduction); - } - } - } else if (!proxiedMethods.isEmpty()) { - BeanDefinitionWriter beanDefinitionWriter = parentWriter == null ? proxyBeanDefinitionWriter : parentWriter; - ExecutableMethodsDefinitionWriter executableMethodsDefinitionWriter = beanDefinitionWriter.getExecutableMethodsWriter(); - Type executableMethodsDefinitionType = executableMethodsDefinitionWriter.getClassType(); - proxyConstructorGenerator.newInstance(executableMethodsDefinitionType); - proxyConstructorGenerator.dup(); - if (executableMethodsDefinitionWriter.isSupportsInterceptedProxy()) { - proxyConstructorGenerator.push(true); - proxyConstructorGenerator.invokeConstructor(executableMethodsDefinitionType, new Method(CONSTRUCTOR_NAME, getConstructorDescriptor(boolean.class))); + if (cacheLazyTarget) { + interceptedTargetMethod = getCacheLazyTargetInterceptedTargetMethod( + targetField, + beanLocatorField, + beanResolutionContextField, + proxyBeanDefinitionField, + beanQualifierField + ); + proxyBuilder.addMethod( + getHasCachedInterceptedTargetMethod(targetField) + ); } else { - proxyConstructorGenerator.invokeConstructor(executableMethodsDefinitionType, new Method(CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR)); - } - int executableMethodsDefinitionIndex = proxyConstructorGenerator.newLocal(executableMethodsDefinitionType); - proxyConstructorGenerator.storeLocal(executableMethodsDefinitionIndex, executableMethodsDefinitionType); - - for (int i = 0; i < proxyMethodCount; i++) { - MethodRef methodRef = proxiedMethods.get(i); - int methodIndex = methodRef.methodIndex; - - boolean introduction = isIntroduction && ( - executableMethodsDefinitionWriter.isAbstract(methodIndex) || ( - executableMethodsDefinitionWriter.isInterface(methodIndex) && !executableMethodsDefinitionWriter.isDefault(methodIndex))); - - // The following will initialize the array of $proxyMethod instances - // E.g. this.proxyMethods[0] = new $blah0(); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_PROXY_METHODS, FIELD_TYPE_PROXY_METHODS); - proxyConstructorGenerator.push(i); - // getExecutableMethodByIndex - proxyConstructorGenerator.loadLocal(executableMethodsDefinitionIndex); - proxyConstructorGenerator.push(methodIndex); - proxyConstructorGenerator.invokeVirtual(executableMethodsDefinitionType, ExecutableMethodsDefinitionWriter.GET_EXECUTABLE_AT_INDEX_METHOD); - proxyConstructorGenerator.visitInsn(AASTORE); - - pushResolveInterceptorsCall(proxyConstructorGenerator, i, introduction); + interceptedTargetMethod = getLazyInterceptedTargetMethod( + beanLocatorField, + beanResolutionContextField, + proxyBeanDefinitionField, + beanQualifierField + ); } - } - for (Runnable fieldInjectionPoint : deferredInjectionPoints) { - fieldInjectionPoint.run(); - } + bodyBuilders.add((aThis, methodParameters) -> StatementDef.multi( + aThis.field(beanLocatorField).assign(methodParameters.get(beanContextArgumentIndex)), + aThis.field(beanResolutionContextField).assign( + methodParameters.get(beanResolutionContextArgumentIndex) + .invoke(COPY_BEAN_CONTEXT_METHOD) + ) + )); + } else { + if (hotswap) { + proxyBuilder.addSuperinterface(TypeDef.parameterized(HotSwappableInterceptedProxy.class, targetType)); - constructorWriter.visitInsn(RETURN); - constructorWriter.visitMaxs(DEFAULT_MAX_STACK, 1); + ClassTypeDef readWriteLockType = ClassTypeDef.of(ReentrantReadWriteLock.class); + FieldDef readWriteLockField = FieldDef.builder(FIELD_READ_WRITE_LOCK, readWriteLockType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .initializer(readWriteLockType.instantiate()) + .build(); - this.constructorWriter.visitEnd(); - proxyBeanDefinitionWriter.visitBeanDefinitionEnd(); - if (targetDefinitionGenerator != null) { - targetDefinitionGenerator.visitMaxs(1, 1); - targetDefinitionGenerator.visitEnd(); - } - - if (targetTypeGenerator != null) { - targetTypeGenerator.visitMaxs(1, 1); - targetTypeGenerator.visitEnd(); - } + proxyBuilder.addField(readWriteLockField); + ClassTypeDef lockType = ClassTypeDef.of(Lock.class); + FieldDef readLockField = FieldDef.builder(FIELD_READ_LOCK, lockType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .initializer(new VariableDef.This().field(readWriteLockField).invoke(GET_READ_LOCK_METHOD)) + .build(); - proxyClassWriter.visitEnd(); - } + proxyBuilder.addField(readLockField); - private void pushResolveLazyProxyTargetBean(GeneratorAdapter generatorAdapter, Type targetType) { - // load the bean context - generatorAdapter.loadThis(); - generatorAdapter.getField(proxyType, FIELD_BEAN_LOCATOR, TYPE_BEAN_LOCATOR); - pushCastToType(generatorAdapter, TYPE_DEFAULT_BEAN_CONTEXT); + FieldDef writeLockField = FieldDef.builder(FIELD_WRITE_LOCK, lockType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .initializer(new VariableDef.This().field(readWriteLockField).invoke(GET_WRITE_LOCK_METHOD)) + .build(); - // 1st argument: the bean resolution context - generatorAdapter.loadThis(); - generatorAdapter.getField(proxyType, FIELD_BEAN_RESOLUTION_CONTEXT, Type.getType(BeanResolutionContext.class)); - // 2nd argument: this.$proxyBeanDefinition - generatorAdapter.loadThis(); - generatorAdapter.getField(proxyType, FIELD_PROXY_BEAN_DEFINITION, TYPE_BEAN_DEFINITION); - // 3rd argument: the type - pushTargetArgument(generatorAdapter, targetType); - // 4th argument: the qualifier - pushQualifier(generatorAdapter); + proxyBuilder.addField(writeLockField); - generatorAdapter.invokeVirtual( - TYPE_DEFAULT_BEAN_CONTEXT, - METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT - ); - pushCastToType(generatorAdapter, getTypeReferenceForName(targetClassFullName)); - } + proxyBuilder.addMethod( + getSwapMethod(targetField, writeLockField) + ); + interceptedTargetMethod = getHotSwapInterceptedTargetMethod(targetField, readLockField); + } else { + proxyBuilder.addSuperinterface(TypeDef.parameterized(InterceptedProxy.class, targetType)); + interceptedTargetMethod = getSimpleInterceptedTargetMethod(targetField); + } - private void pushResolveProxyBeanDefinition(GeneratorAdapter generatorAdapter, Type targetType) { - // load the bean context - generatorAdapter.loadArg(beanContextArgumentIndex); + // Non-lazy target + bodyBuilders.add((aThis, methodParameters) -> aThis.field(targetField).assign( + methodParameters.get(beanContextArgumentIndex) + .cast(DefaultBeanContext.class).invoke( + METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT, + + // 1st argument: the bean resolution context + methodParameters.get(beanResolutionContextArgumentIndex), + // 2nd argument: this.$proxyBeanDefinition + aThis.field(proxyBeanDefinitionField), + // 3rd argument: the type + pushTargetArgument(targetType), + // 4th argument: the qualifier + methodParameters.get(qualifierIndex) + ).cast(targetType) + ) + ); + } - // 1nd argument: the type - pushTargetArgument(generatorAdapter, targetType); - // 2rd argument: the qualifier - pushQualifier(generatorAdapter); + proxyBuilder.addMethod(interceptedTargetMethod); - generatorAdapter.invokeInterface( - TYPE_BEAN_DEFINITION_REGISTRY, - METHOD_GET_PROXY_BEAN_DEFINITION - ); + proxyBuilder.addMethod(MethodDef.constructor() + .addParameters(Arrays.stream(realConstructor.getParameters()).map(p -> TypeDef.erasure(p.getType())).toList()) + .build((aThis, methodParameters) -> { + List constructorStatements = new ArrayList<>(); + constructorStatements.add( + invokeSuperConstructor(aThis, methodParameters) + ); + bodyBuilders.forEach(bodyBuilder -> constructorStatements.add(bodyBuilder.apply(aThis, methodParameters))); + constructorStatements.add( + initializeProxyTargetMethodsAndInterceptors(aThis, methodParameters, proxyBeanDefinitionField) + ); + return StatementDef.multi(constructorStatements); + })); } - private void pushQualifier(GeneratorAdapter generatorAdapter) { - generatorAdapter.loadThis(); - generatorAdapter.getField(proxyType, FIELD_BEAN_QUALIFIER, Type.getType(Qualifier.class)); + private StatementDef initializeProxyMethodsAndInterceptors(VariableDef.This aThis, + List parameters) { + if (proxiedMethods.isEmpty()) { + return StatementDef.multi(); + } + BeanDefinitionWriter beanDefinitionWriter = parentWriter == null ? proxyBeanDefinitionWriter : parentWriter; + ExecutableMethodsDefinitionWriter executableMethodsDefinitionWriter = beanDefinitionWriter.getExecutableMethodsWriter(); + ClassTypeDef executableMethodsType = executableMethodsDefinitionWriter.getClassTypeDef(); + ExpressionDef.NewInstance executableMethodsInstance; + if (executableMethodsDefinitionWriter.isSupportsInterceptedProxy()) { + executableMethodsInstance = executableMethodsType.instantiate(TypeDef.Primitive.BOOLEAN.constant(true)); + } else { + executableMethodsInstance = executableMethodsType.instantiate(); + } + int[] index = {0}; + return executableMethodsInstance.newLocal("executableMethods", executableMethodsVar -> StatementDef.multi( + aThis.field(proxyMethodsField).assign( + ClassTypeDef.of(ExecutableMethod.class).array().instantiate( + proxyTargetMethods.stream().map(methodRef -> + executableMethodsVar.invoke( + ExecutableMethodsDefinitionWriter.GET_EXECUTABLE_AT_INDEX_METHOD, + + TypeDef.Primitive.INT.constant(methodRef.methodIndex) + )).toList() + ) + ), + aThis.field(interceptorsField).assign( + ClassTypeDef.of(Interceptor.class).array(2).instantiate( + proxyTargetMethods.stream().map(methodRef -> { + int methodIndex = methodRef.methodIndex; + boolean introduction = isIntroduction && ( + executableMethodsDefinitionWriter.isAbstract(methodIndex) || ( + executableMethodsDefinitionWriter.isInterface(methodIndex) && !executableMethodsDefinitionWriter.isDefault(methodIndex))); + + return ClassTypeDef.of(InterceptorChain.class).invokeStatic( + (introduction ? RESOLVE_INTRODUCTION_INTERCEPTORS_METHOD : RESOLVE_AROUND_INTERCEPTORS_METHOD), + + // First argument. The interceptor registry + parameters.get(interceptorRegistryArgumentIndex), + // Second argument i.e. proxyMethods[0] + aThis.field(proxyMethodsField).arrayElement(index[0]++), + // Third argument i.e. interceptors + parameters.get(interceptorsListArgumentIndex) + ); + } + ).toList() + ) + ) + )); } - private void pushResolveProxyTargetBean(GeneratorAdapter generatorAdapter, Type targetType) { - // load the bean context - generatorAdapter.loadArg(beanContextArgumentIndex); - pushCastToType(generatorAdapter, TYPE_DEFAULT_BEAN_CONTEXT); - - // 1st argument: the bean resolution context - generatorAdapter.loadArg(beanResolutionContextArgumentIndex); - // 2nd argument: this.$proxyBeanDefinition - generatorAdapter.loadThis(); - generatorAdapter.getField(proxyType, FIELD_PROXY_BEAN_DEFINITION, TYPE_BEAN_DEFINITION); - // 3rd argument: the type - pushTargetArgument(generatorAdapter, targetType); - // 4th argument: the qualifier - pushQualifier(generatorAdapter); + private StatementDef initializeProxyTargetMethodsAndInterceptors(VariableDef.This aThis, + List parameters, + FieldDef proxyBeanDefinitionField) { + if (proxiedMethods.size() != proxyMethodCount) { + throw new IllegalStateException("Expected proxy methods count to match actual methods"); + } + int[] index = {0}; + return StatementDef.multi( + aThis.field(proxyMethodsField).assign( + ClassTypeDef.of(ExecutableMethod.class).array().instantiate( + proxyTargetMethods.stream().map(methodRef -> + aThis.field(proxyBeanDefinitionField).invoke( + METHOD_BEAN_DEFINITION_GET_REQUIRED_METHOD, + + ExpressionDef.constant(methodRef.name), + TypeDef.CLASS.array().instantiate( + methodRef.genericArgumentTypes.stream().map(t -> ExpressionDef.constant(TypeDef.erasure(t))).toList() + ) + ) + ).toList() + ) + ), + aThis.field(interceptorsField).assign( + ClassTypeDef.of(Interceptor.class).array(2).instantiate( + proxyTargetMethods.stream().map(methodRef -> + ClassTypeDef.of(InterceptorChain.class).invokeStatic( + (isIntroduction ? RESOLVE_INTRODUCTION_INTERCEPTORS_METHOD : RESOLVE_AROUND_INTERCEPTORS_METHOD), + + // First argument. The interceptor registry + parameters.get(interceptorRegistryArgumentIndex), + // Second argument i.e. proxyMethods[0] + aThis.field(proxyMethodsField).arrayElement(index[0]++), + // Third argument i.e. interceptors + parameters.get(interceptorsListArgumentIndex) + ) + ).toList() + ) + ) + ); + } - generatorAdapter.invokeVirtual( - TYPE_DEFAULT_BEAN_CONTEXT, - METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT + private ExpressionDef.InvokeInstanceMethod invokeSuperConstructor(VariableDef.This aThis, List methodParameters) { + if (isInterface) { + return aThis.superRef().invokeConstructor(); + } + List values = new ArrayList<>(); + List arguments = new ArrayList<>(); + for (Map.Entry e : superConstructorParametersBinding) { + values.add(methodParameters.get(e.getValue())); + arguments.add(TypeDef.erasure(e.getKey().getType())); + } + return aThis.superRef().invokeConstructor(arguments, values); + } + + private ExpressionDef.InvokeInstanceMethod pushResolveLazyProxyTargetBean(VariableDef.This aThis, + List parameters, + FieldDef beanLocatorField, + FieldDef beanResolutionContextField, + FieldDef proxyBeanDefinitionField, + FieldDef beanQualifierField) { + return aThis.field(beanLocatorField).cast(DefaultBeanContext.class).invoke( + METHOD_GET_PROXY_TARGET_BEAN_WITH_BEAN_DEFINITION_AND_CONTEXT, + + // 1st argument: the bean resolution context + aThis.field(beanResolutionContextField), + // 2nd argument: this.$proxyBeanDefinition + aThis.field(proxyBeanDefinitionField), + // 3rd argument: the type + pushTargetArgument(ClassTypeDef.of(targetClassFullName)), + // 4th argument: the qualifier + aThis.field(beanQualifierField) ); - pushCastToType(generatorAdapter, getTypeReferenceForName(targetClassFullName)); } - private void pushTargetArgument(GeneratorAdapter proxyConstructorGenerator, Type targetType) { - buildArgumentWithGenerics( - proxyConstructorGenerator, - targetType, + private ExpressionDef pushTargetArgument(TypeDef targetType) { + return ArgumentExpUtils.buildArgumentWithGenerics( + targetType, new AnnotationMetadataReference( - getBeanDefinitionName(), - getAnnotationMetadata() + getBeanDefinitionName(), + getAnnotationMetadata() ), parentWriter != null ? parentWriter.getTypeArguments() : proxyBeanDefinitionWriter.getTypeArguments() ); } - /** - * Write the proxy to the given compilation directory. - * - * @param compilationDir The target compilation directory - * @throws IOException If an error occurs writing the file - */ - @Override - public void writeTo(File compilationDir) throws IOException { - accept(newClassWriterOutputVisitor(compilationDir)); - } - @NonNull @Override public ClassElement[] getTypeArguments() { @@ -1222,7 +1147,7 @@ public Map getTypeArgumentMap() { public void accept(ClassWriterOutputVisitor visitor) throws IOException { proxyBeanDefinitionWriter.accept(visitor); try (OutputStream out = visitor.visitClass(proxyFullName, getOriginatingElements())) { - out.write(classWriter.toByteArray()); + out.write(new ByteCodeWriter().write(proxyBuilder.build())); } } @@ -1238,48 +1163,48 @@ public void visitSuperBeanDefinitionFactory(String beanName) { @Override public void visitSetterValue( - TypedElement declaringType, - MethodElement methodElement, - AnnotationMetadata annotationMetadata, - boolean requiresReflection, - boolean isOptional) { + TypedElement declaringType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection, + boolean isOptional) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitSetterValue( - declaringType, - methodElement, - annotationMetadata, - requiresReflection, - isOptional - ) + proxyBeanDefinitionWriter.visitSetterValue( + declaringType, + methodElement, + annotationMetadata, + requiresReflection, + isOptional + ) ); } @Override public void visitPostConstructMethod( - TypedElement declaringType, - MethodElement methodElement, - boolean requiresReflection, - VisitorContext visitorContext) { + TypedElement declaringType, + MethodElement methodElement, + boolean requiresReflection, + VisitorContext visitorContext) { deferredInjectionPoints.add(() -> proxyBeanDefinitionWriter.visitPostConstructMethod( - declaringType, - methodElement, - requiresReflection, - visitorContext + declaringType, + methodElement, + requiresReflection, + visitorContext )); } @Override public void visitPreDestroyMethod( - TypedElement declaringType, - MethodElement methodElement, - boolean requiresReflection, - VisitorContext visitorContext) { + TypedElement declaringType, + MethodElement methodElement, + boolean requiresReflection, + VisitorContext visitorContext) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitPreDestroyMethod( - declaringType, - methodElement, - requiresReflection, - visitorContext) + proxyBeanDefinitionWriter.visitPreDestroyMethod( + declaringType, + methodElement, + requiresReflection, + visitorContext) ); } @@ -1289,25 +1214,25 @@ public void visitMethodInjectionPoint(TypedElement beanType, boolean requiresReflection, VisitorContext visitorContext) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitMethodInjectionPoint( - beanType, - methodElement, - requiresReflection, - visitorContext) + proxyBeanDefinitionWriter.visitMethodInjectionPoint( + beanType, + methodElement, + requiresReflection, + visitorContext) ); } @Override public int visitExecutableMethod( - TypedElement declaringBean, - MethodElement methodElement, - VisitorContext visitorContext) { + TypedElement declaringBean, + MethodElement methodElement, + VisitorContext visitorContext) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitExecutableMethod( - declaringBean, - methodElement, - visitorContext - ) + proxyBeanDefinitionWriter.visitExecutableMethod( + declaringBean, + methodElement, + visitorContext + ) ); return -1; } @@ -1318,12 +1243,12 @@ public void visitFieldInjectionPoint( FieldElement fieldType, boolean requiresReflection, VisitorContext visitorContext) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitFieldInjectionPoint( - declaringType, - fieldType, - requiresReflection, - visitorContext - ) + proxyBeanDefinitionWriter.visitFieldInjectionPoint( + declaringType, + fieldType, + requiresReflection, + visitorContext + ) ); } @@ -1333,23 +1258,22 @@ public void visitAnnotationMemberPropertyInjectionPoint(TypedElement annotationM String requiredValue, String notEqualsValue) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitAnnotationMemberPropertyInjectionPoint( - annotationMemberBeanType, - annotationMemberProperty, - requiredValue, - notEqualsValue)); + proxyBeanDefinitionWriter.visitAnnotationMemberPropertyInjectionPoint( + annotationMemberBeanType, + annotationMemberProperty, + requiredValue, + notEqualsValue)); } @Override - public void visitFieldValue( - TypedElement declaringType, - FieldElement fieldType, - boolean requiresReflection, boolean isOptional) { + public void visitFieldValue(TypedElement declaringType, + FieldElement fieldType, + boolean requiresReflection, boolean isOptional) { deferredInjectionPoints.add(() -> - proxyBeanDefinitionWriter.visitFieldValue( - declaringType, - fieldType, requiresReflection, isOptional - ) + proxyBeanDefinitionWriter.visitFieldValue( + declaringType, + fieldType, requiresReflection, isOptional + ) ); } @@ -1427,7 +1351,7 @@ public void visitInterceptorBinding(AnnotationValue... interceptorBinding) { if (interceptorBinding != null) { for (AnnotationValue annotationValue : interceptorBinding) { annotationValue.stringValue().ifPresent(annName -> - this.interceptorBinding.add(annotationValue) + this.interceptorBinding.add(annotationValue) ); } } @@ -1437,245 +1361,167 @@ private Set> toInterceptorBindingMap(AnnotationValue[] int return new LinkedHashSet<>(Arrays.asList(interceptorBinding)); } - private void readUnlock(GeneratorAdapter interceptedTargetVisitor) { - invokeMethodOnLock(interceptedTargetVisitor, FIELD_READ_LOCK, Method.getMethod("void unlock()")); - } - - private void readLock(GeneratorAdapter interceptedTargetVisitor) { - invokeMethodOnLock(interceptedTargetVisitor, FIELD_READ_LOCK, Method.getMethod("void lock()")); - } - - private void writeUnlock(GeneratorAdapter interceptedTargetVisitor) { - invokeMethodOnLock(interceptedTargetVisitor, FIELD_WRITE_LOCK, Method.getMethod("void unlock()")); - } - - private void writeLock(GeneratorAdapter interceptedTargetVisitor) { - invokeMethodOnLock(interceptedTargetVisitor, FIELD_WRITE_LOCK, Method.getMethod("void lock()")); - } - - private void invokeMethodOnLock(GeneratorAdapter interceptedTargetVisitor, String field, Method method) { - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.getField(proxyType, field, TYPE_LOCK); - interceptedTargetVisitor.invokeInterface(TYPE_LOCK, method); + private MethodDef writeWithQualifierMethod(FieldDef beanQualifier) { + return MethodDef.override(WITH_QUALIFIER_METHOD) + .build((aThis, methodParameters) -> aThis.field(beanQualifier).put(methodParameters.get(0))); + } + + private MethodDef getSwapMethod(FieldDef targetField, FieldDef writeField) { + Objects.requireNonNull(targetField); + Objects.requireNonNull(writeField); + return MethodDef.override(SWAP_METHOD) + .build((aThis, methodParameters) -> { + VariableDef.Field lock = aThis.field(writeField); + return StatementDef.multi( + lock.invoke(LOCK_METHOD), + StatementDef.doTry( + aThis.field(targetField).newLocal("target", targetVar -> StatementDef.multi( + aThis.field(targetField).assign(methodParameters.get(0)), + targetVar.returning() + )) + ).doFinally(lock.invoke(UNLOCK_METHOD)) + ); + }); + } + + private MethodDef getLazyInterceptedTargetMethod(FieldDef beanLocatorField, + FieldDef beanResolutionContextField, + FieldDef proxyBeanDefinitionField, + FieldDef beanQualifierField) { + + return MethodDef.override(METHOD_INTERCEPTED_TARGET) + .build((aThis, methodParameters) -> pushResolveLazyProxyTargetBean( + aThis, + methodParameters, + beanLocatorField, + beanResolutionContextField, + proxyBeanDefinitionField, + beanQualifierField + ).returning()); + } + + private MethodDef getCacheLazyTargetInterceptedTargetMethod(FieldDef targetField, + FieldDef beanLocatorField, + FieldDef beanResolutionContextField, + FieldDef proxyBeanDefinitionField, + FieldDef beanQualifierField) { + + return MethodDef.override(METHOD_INTERCEPTED_TARGET) + .build((aThis, methodParameters) -> { +// B var1 = this.$target; +// if (var1 == null) { +// synchronized(this) { +// var1 = this.$target; +// if (var1 == null) { +// this.$target = (B)((DefaultBeanContext)this.$beanLocator).getProxyTargetBean(this.$beanResolutionContext, this.$proxyBeanDefinition, Argument.of(B.class, $B$Definition$Intercepted$Definition.$ANNOTATION_METADATA, new Class[0]), this.$beanQualifier); +// this.$beanResolutionContext = null; +// } +// } +// } +// return this.$target; + VariableDef.Field targetFieldAccess = aThis.field(targetField); + return StatementDef.multi( + targetFieldAccess.newLocal("target", targetVar -> + targetVar.ifNull( + new StatementDef.Synchronized( + aThis, + StatementDef.multi( + targetVar.assign(targetFieldAccess), + targetVar.ifNull( + StatementDef.multi( + targetFieldAccess.assign( + pushResolveLazyProxyTargetBean( + aThis, + methodParameters, + beanLocatorField, + beanResolutionContextField, + proxyBeanDefinitionField, + beanQualifierField) + ), + aThis.field(beanResolutionContextField).assign(ExpressionDef.nullValue()) + ) + ) + ) + ) + ) + ), + targetFieldAccess.returning() + ); + }); + } + + private MethodDef getHotSwapInterceptedTargetMethod(FieldDef targetField, + FieldDef readLockField) { + + return MethodDef.override(METHOD_INTERCEPTED_TARGET) + .build((aThis, methodParameters) -> { + // this.$target_rl.lock(); + // + // HotswappableProxyingClass var1; + // try { + // var1 = this.$target; + // } finally { + // this.$target_rl.unlock(); + // } + // + // return var1; + return StatementDef.multi( + aThis.field(readLockField).invoke(LOCK_METHOD), + aThis.field(targetField).returning() + .doTry() + .doFinally(aThis.field(readLockField).invoke(UNLOCK_METHOD)) + ); + }); } - private void writeWithQualifierMethod(ClassWriter proxyClassWriter) { - GeneratorAdapter withQualifierMethod = startPublicMethod(proxyClassWriter, "$withBeanQualifier", void.class.getName(), Qualifier.class.getName()); - - withQualifierMethod.loadThis(); - withQualifierMethod.loadArg(0); - withQualifierMethod.putField(proxyType, FIELD_BEAN_QUALIFIER, Type.getType(Qualifier.class)); - withQualifierMethod.visitInsn(RETURN); - withQualifierMethod.visitEnd(); - withQualifierMethod.visitMaxs(1, 1); + private MethodDef getSimpleInterceptedTargetMethod(FieldDef targetField) { + Objects.requireNonNull(targetField); + return MethodDef.override(METHOD_INTERCEPTED_TARGET) + .build((aThis, methodParameters) -> aThis.field(targetField).returning()); } - private void writeSwapMethod(ClassWriter proxyClassWriter, Type targetType) { - GeneratorAdapter swapGenerator = startPublicMethod(proxyClassWriter, "swap", targetType.getClassName(), targetType.getClassName()); - Label l0 = new Label(); - Label l1 = new Label(); - Label l2 = new Label(); - swapGenerator.visitTryCatchBlock( - l0, - l1, - l2, - null - ); - // add write lock - writeLock(swapGenerator); - swapGenerator.visitLabel(l0); - swapGenerator.loadThis(); - swapGenerator.getField(proxyType, FIELD_TARGET, targetType); - // release write lock - int localRef = swapGenerator.newLocal(targetType); - swapGenerator.storeLocal(localRef); - - // assign the new value - swapGenerator.loadThis(); - swapGenerator.visitVarInsn(ALOAD, 1); - swapGenerator.putField(proxyType, FIELD_TARGET, targetType); - - swapGenerator.visitLabel(l1); - writeUnlock(swapGenerator); - swapGenerator.loadLocal(localRef); - swapGenerator.returnValue(); - swapGenerator.visitLabel(l2); - // release write lock in finally - int var = swapGenerator.newLocal(targetType); - swapGenerator.storeLocal(var); - writeUnlock(swapGenerator); - swapGenerator.loadLocal(var); - swapGenerator.throwException(); - - swapGenerator.visitMaxs(2, MAX_LOCALS); - swapGenerator.visitEnd(); - } - - private void writeInterceptedTargetMethod(ClassWriter proxyClassWriter, Type targetType) { - // add interceptedTarget() method - GeneratorAdapter interceptedTargetVisitor = startPublicMethod( - proxyClassWriter, - "interceptedTarget", - Object.class.getName()); - - if (lazy) { - if (cacheLazyTarget) { - // Object local = this.$target; - int targetLocal = interceptedTargetVisitor.newLocal(targetType); - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.getField(proxyType, FIELD_TARGET, targetType); - interceptedTargetVisitor.storeLocal(targetLocal, targetType); - // if (local == null) { - interceptedTargetVisitor.loadLocal(targetLocal, targetType); - Label returnLabel = new Label(); - interceptedTargetVisitor.ifNonNull(returnLabel); - // synchronized (this) { - Label synchronizationEnd = new Label(); - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.monitorEnter(); - - Label tryLabel = new Label(); - Label catchLabel = new Label(); - - interceptedTargetVisitor.visitTryCatchBlock(tryLabel, returnLabel, catchLabel, null); - - // Try body - interceptedTargetVisitor.visitLabel(tryLabel); - // local = this.$target - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.getField(proxyType, FIELD_TARGET, targetType); - interceptedTargetVisitor.storeLocal(targetLocal, targetType); - // if (local == null) { - interceptedTargetVisitor.loadLocal(targetLocal, targetType); - interceptedTargetVisitor.ifNonNull(synchronizationEnd); - // this.$target = - interceptedTargetVisitor.loadThis(); - pushResolveLazyProxyTargetBean(interceptedTargetVisitor, targetType); - interceptedTargetVisitor.putField(proxyType, FIELD_TARGET, targetType); - // cleanup this.$beanResolutionContext - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.push((String) null); - interceptedTargetVisitor.putField(proxyType, FIELD_BEAN_RESOLUTION_CONTEXT, Type.getType(BeanResolutionContext.class)); - interceptedTargetVisitor.goTo(synchronizationEnd); - - // Catch body - interceptedTargetVisitor.visitLabel(catchLabel); - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.monitorExit(); - interceptedTargetVisitor.throwException(); - - // Synchronization end label - interceptedTargetVisitor.visitLabel(synchronizationEnd); - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.monitorExit(); - interceptedTargetVisitor.goTo(returnLabel); - - // Return label just loads and returns value - interceptedTargetVisitor.visitLabel(returnLabel); - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.getField(proxyType, FIELD_TARGET, targetType); - interceptedTargetVisitor.returnValue(); - } else { - pushResolveLazyProxyTargetBean(interceptedTargetVisitor, targetType); - interceptedTargetVisitor.returnValue(); - } - } else { - int localRef = -1; - Label l1 = null; - Label l2 = null; - if (hotswap) { - Label l0 = new Label(); - l1 = new Label(); - l2 = new Label(); - interceptedTargetVisitor.visitTryCatchBlock( - l0, - l1, - l2, - null - ); - // add read lock - readLock(interceptedTargetVisitor); - interceptedTargetVisitor.visitLabel(l0); - } - interceptedTargetVisitor.loadThis(); - interceptedTargetVisitor.getField(proxyType, FIELD_TARGET, targetType); - if (hotswap) { - // release read lock - localRef = interceptedTargetVisitor.newLocal(targetType); - interceptedTargetVisitor.storeLocal(localRef); - interceptedTargetVisitor.visitLabel(l1); - readUnlock(interceptedTargetVisitor); - interceptedTargetVisitor.loadLocal(localRef); - } - interceptedTargetVisitor.returnValue(); - if (localRef > -1) { - interceptedTargetVisitor.visitLabel(l2); - // release read lock in finally - int var = interceptedTargetVisitor.newLocal(targetType); - interceptedTargetVisitor.storeLocal(var); - readUnlock(interceptedTargetVisitor); - interceptedTargetVisitor.loadLocal(var); - interceptedTargetVisitor.throwException(); - } - } - - interceptedTargetVisitor.visitMaxs(1, 2); - interceptedTargetVisitor.visitEnd(); - } - - private void writeHasCachedInterceptedTargetMethod(ClassWriter proxyClassWriter, Type targetType) { - GeneratorAdapter methodVisitor = startPublicMethod(proxyClassWriter, METHOD_HAS_CACHED_INTERCEPTED_METHOD); - methodVisitor.loadThis(); - methodVisitor.getField(proxyType, FIELD_TARGET, targetType); - Label notNull = new Label(); - methodVisitor.ifNonNull(notNull); - methodVisitor.push(false); - methodVisitor.returnValue(); - methodVisitor.visitLabel(notNull); - methodVisitor.push(true); - methodVisitor.returnValue(); - methodVisitor.visitMaxs(1, 2); - methodVisitor.visitEnd(); - } - - private void pushResolveInterceptorsCall(GeneratorAdapter proxyConstructorGenerator, int i, boolean isIntroduction) { - // The following will initialize the array of interceptor instances - // e.g. this.interceptors[0] = InterceptorChain.resolveAroundInterceptors(beanContext, proxyMethods[0], interceptors); - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_INTERCEPTORS, FIELD_TYPE_INTERCEPTORS); - proxyConstructorGenerator.push(i); - - // First argument. The interceptor registry - proxyConstructorGenerator.loadArg(interceptorRegistryArgumentIndex); - - // Second argument i.e. proxyMethods[0] - proxyConstructorGenerator.loadThis(); - proxyConstructorGenerator.getField(proxyType, FIELD_PROXY_METHODS, FIELD_TYPE_PROXY_METHODS); - proxyConstructorGenerator.push(i); - proxyConstructorGenerator.visitInsn(AALOAD); - - // Third argument i.e. interceptors - proxyConstructorGenerator.loadArg(interceptorsListArgumentIndex); - if (isIntroduction) { - proxyConstructorGenerator.invokeStatic(TYPE_INTERCEPTOR_CHAIN, Method.getMethod(RESOLVE_INTRODUCTION_INTERCEPTORS_METHOD)); - } else { - proxyConstructorGenerator.invokeStatic(TYPE_INTERCEPTOR_CHAIN, Method.getMethod(RESOLVE_AROUND_INTERCEPTORS_METHOD)); - } - proxyConstructorGenerator.visitInsn(AASTORE); + private MethodDef getHasCachedInterceptedTargetMethod(FieldDef targetField) { + Objects.requireNonNull(targetField); + return MethodDef.builder(METHOD_HAS_CACHED_INTERCEPTED_METHOD.getName()) + .addModifiers(Modifier.PUBLIC) + .addParameters(METHOD_HAS_CACHED_INTERCEPTED_METHOD.getParameterTypes()) + .build((aThis, methodParameters) -> aThis.field(targetField).isNonNull().returning()); } private void processAlreadyVisitedMethods(BeanDefinitionWriter parent) { final List postConstructMethodVisits = parent.getPostConstructMethodVisits(); for (BeanDefinitionWriter.MethodVisitData methodVisit : postConstructMethodVisits) { visitPostConstructMethod( - methodVisit.getBeanType(), - methodVisit.getMethodElement(), - methodVisit.isRequiresReflection(), - visitorContext + methodVisit.getBeanType(), + methodVisit.getMethodElement(), + methodVisit.isRequiresReflection(), + visitorContext ); } } + /** + * @param p The class element + * @return The string representation + */ + private static String toTypeString(ClassElement p) { + String name = p.getName(); + if (p.isArray()) { + return name + IntStream.range(0, p.getArrayDimensions()).mapToObj(ignore -> "[]").collect(Collectors.joining()); + } + return name; + } + + @Override + public @NonNull Element[] getOriginatingElements() { + return originatingElements.getOriginatingElements(); + } + + @Override + public void addOriginatingElement(Element element) { + originatingElements.addOriginatingElement(element); + } + /** * Method Reference class with names and a list of argument types. Used as the targets. */ @@ -1705,8 +1551,8 @@ public boolean equals(Object o) { } MethodRef methodRef = (MethodRef) o; return Objects.equals(name, methodRef.name) && - Objects.equals(rawTypes, methodRef.rawTypes) && - Objects.equals(returnType, methodRef.returnType); + Objects.equals(rawTypes, methodRef.rawTypes) && + Objects.equals(returnType, methodRef.returnType); } @Override diff --git a/core-processor/src/main/java/io/micronaut/expressions/EvaluatedExpressionWriter.java b/core-processor/src/main/java/io/micronaut/expressions/EvaluatedExpressionWriter.java index aa82733401b..909615d33b1 100644 --- a/core-processor/src/main/java/io/micronaut/expressions/EvaluatedExpressionWriter.java +++ b/core-processor/src/main/java/io/micronaut/expressions/EvaluatedExpressionWriter.java @@ -16,6 +16,7 @@ package io.micronaut.expressions; import io.micronaut.context.expressions.AbstractEvaluatedExpression; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.core.expressions.ExpressionEvaluationContext; import io.micronaut.expressions.context.ExpressionWithContext; @@ -26,10 +27,13 @@ import io.micronaut.expressions.parser.exception.ExpressionCompilationException; import io.micronaut.expressions.parser.exception.ExpressionParsingException; import io.micronaut.inject.ast.Element; +import io.micronaut.inject.processing.JavaModelUtils; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.inject.writer.AbstractClassFileWriter; +import io.micronaut.inject.writer.ClassOutputWriter; import io.micronaut.inject.writer.ClassWriterOutputVisitor; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; @@ -39,6 +43,8 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; @@ -50,7 +56,13 @@ * @since 4.0.0 */ @Internal -public final class EvaluatedExpressionWriter extends AbstractClassFileWriter { +public final class EvaluatedExpressionWriter implements ClassOutputWriter, Opcodes { + + private static final String CONSTRUCTOR_NAME = ""; + private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[])+$"); + + private static final Type TYPE_GENERATED = Type.getType(Generated.class); + private static final Method EVALUATED_EXPRESSIONS_CONSTRUCTOR = new Method(CONSTRUCTOR_NAME, getConstructorDescriptor(Object.class)); @@ -77,8 +89,7 @@ public void accept(ClassWriterOutputVisitor outputVisitor) throws IOException { if (WRITTEN_CLASSES.contains(expressionClassName)) { return; } - try (OutputStream outputStream = outputVisitor.visitClass(expressionClassName, - getOriginatingElements())) { + try (OutputStream outputStream = outputVisitor.visitClass(expressionClassName, originatingElement)) { ClassWriter classWriter = generateClassBytes(expressionClassName); outputStream.write(classWriter.toByteArray()); WRITTEN_CLASSES.add(expressionClassName); @@ -146,4 +157,96 @@ private void failCompilation(Throwable ex, Object initialAnnotationValue) { visitorContext.fail(message, originatingElement); } + + private static void pushBoxPrimitiveIfNecessary(Type fieldType, GeneratorAdapter injectMethodVisitor) { + if (JavaModelUtils.isPrimitive(fieldType)) { + injectMethodVisitor.valueOf(fieldType); + } + } + + private void startPublicClass(ClassVisitor classWriter, String className, Type superType) { + classWriter.visit(V17, ACC_PUBLIC | ACC_SYNTHETIC, className, null, superType.getInternalName(), null); + classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); + } + + private GeneratorAdapter startConstructor(ClassVisitor classWriter, Class... argumentTypes) { + String descriptor = getConstructorDescriptor(argumentTypes); + return new GeneratorAdapter(classWriter.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, descriptor, null, null), ACC_PUBLIC, CONSTRUCTOR_NAME, descriptor); + } + + private GeneratorAdapter startProtectedMethod(ClassWriter writer, String methodName, String returnType, String... argumentTypes) { + return new GeneratorAdapter(writer.visitMethod( + ACC_PROTECTED, + methodName, + getMethodDescriptor(returnType, argumentTypes), + null, + null + ), ACC_PROTECTED, + methodName, + getMethodDescriptor(returnType, argumentTypes)); + } + + private static String getTypeDescriptor(Class type) { + return Type.getDescriptor(type); + } + + private static String getTypeDescriptor(String className, String... genericTypes) { + if (JavaModelUtils.NAME_TO_TYPE_MAP.containsKey(className)) { + return JavaModelUtils.NAME_TO_TYPE_MAP.get(className); + } else { + String internalName = getInternalName(className); + StringBuilder start = new StringBuilder(40); + Matcher matcher = ARRAY_PATTERN.matcher(className); + if (matcher.find()) { + int dimensions = matcher.group(0).length() / 2; + for (int i = 0; i < dimensions; i++) { + start.append('['); + } + } + start.append('L').append(internalName); + if (genericTypes != null && genericTypes.length > 0) { + start.append('<'); + for (String genericType : genericTypes) { + start.append(getTypeDescriptor(genericType)); + } + start.append('>'); + } + return start.append(';').toString(); + } + } + + private static String getMethodDescriptor(String returnType, String... argumentTypes) { + StringBuilder builder = new StringBuilder(); + builder.append('('); + + for (String argumentType : argumentTypes) { + builder.append(getTypeDescriptor(argumentType)); + } + + builder.append(')'); + + builder.append(getTypeDescriptor(returnType)); + return builder.toString(); + } + + private static String getConstructorDescriptor(Class... argumentTypes) { + StringBuilder builder = new StringBuilder(); + builder.append('('); + + for (Class argumentType : argumentTypes) { + builder.append(getTypeDescriptor(argumentType)); + } + + return builder.append(")V").toString(); + } + + private static String getInternalName(String className) { + String newClassName = className.replace('.', '/'); + Matcher matcher = ARRAY_PATTERN.matcher(newClassName); + if (matcher.find()) { + newClassName = matcher.replaceFirst(""); + } + return newClassName; + } + } diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java new file mode 100644 index 00000000000..5fcca663499 --- /dev/null +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java @@ -0,0 +1,600 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.inject.annotation; + +import io.micronaut.context.expressions.AbstractEvaluatedExpression; +import io.micronaut.core.annotation.AnnotationClassValue; +import io.micronaut.core.annotation.AnnotationDefaultValuesProvider; +import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.expressions.EvaluatedExpressionReference; +import io.micronaut.core.reflect.ReflectionUtils; +import io.micronaut.core.util.CollectionUtils; +import io.micronaut.inject.writer.ExpressionsUtils; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; + +import javax.lang.model.element.Modifier; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Responsible for writing class files that are instances of {@link AnnotationMetadata}. + * + * @author Graeme Rocher + * @since 1.0 + */ +@Internal +public class AnnotationMetadataGenUtils { + + /** + * Field name for annotation metadata. + */ + public static final String FIELD_ANNOTATION_METADATA_NAME = "$ANNOTATION_METADATA"; + public static final ClassTypeDef TYPE_ANNOTATION_METADATA = ClassTypeDef.of(AnnotationMetadata.class); + + public static final FieldDef FIELD_ANNOTATION_METADATA = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA).build(); + public static final ExpressionDef EMPTY_METADATA = TYPE_ANNOTATION_METADATA.getStaticField( + FieldDef.builder("EMPTY_METADATA", TYPE_ANNOTATION_METADATA).build() + ); + + private static final ClassTypeDef TYPE_DEFAULT_ANNOTATION_METADATA = ClassTypeDef.of(DefaultAnnotationMetadata.class); + private static final ClassTypeDef TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY = ClassTypeDef.of(AnnotationMetadataHierarchy.class); + private static final ClassTypeDef TYPE_ANNOTATION_CLASS_VALUE = ClassTypeDef.of(AnnotationClassValue.class); + + private static final String LOAD_CLASS_PREFIX = "$micronaut_load_class_value_"; + + private static final Method METHOD_REGISTER_ANNOTATION_DEFAULTS = ReflectionUtils.getRequiredInternalMethod( + DefaultAnnotationMetadata.class, + "registerAnnotationDefaults", + AnnotationClassValue.class, + Map.class + ); + + private static final Method METHOD_REGISTER_ANNOTATION_TYPE = ReflectionUtils.getRequiredInternalMethod( + DefaultAnnotationMetadata.class, + "registerAnnotationType", + AnnotationClassValue.class + ); + + private static final Method METHOD_REGISTER_REPEATABLE_ANNOTATIONS = ReflectionUtils.getRequiredInternalMethod( + DefaultAnnotationMetadata.class, + "registerRepeatableAnnotations", + Map.class + ); + + private static final Constructor CONSTRUCTOR_ANNOTATION_METADATA = ReflectionUtils.getRequiredInternalConstructor( + DefaultAnnotationMetadata.class, + Map.class, + Map.class, + Map.class, + Map.class, + Map.class, + boolean.class, + boolean.class + ); + + private static final Constructor CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY = ReflectionUtils.getRequiredInternalConstructor( + AnnotationMetadataHierarchy.class, + AnnotationMetadata[].class + ); + + private static final Constructor CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP = ReflectionUtils.getRequiredInternalConstructor( + AnnotationValue.class, + String.class, + Map.class, + AnnotationDefaultValuesProvider.class + ); + + private static final Constructor CONSTRUCTOR_CLASS_VALUE = ReflectionUtils.getRequiredInternalConstructor( + AnnotationClassValue.class, + String.class + ); + + private static final Constructor CONSTRUCTOR_CLASS_VALUE_WITH_CLASS = ReflectionUtils.getRequiredInternalConstructor( + AnnotationClassValue.class, + Class.class + ); + + private static final Constructor CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE = ReflectionUtils.getRequiredInternalConstructor( + AnnotationClassValue.class, + Object.class + ); + + private static final Constructor CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION = ReflectionUtils.getRequiredInternalConstructor( + AbstractEvaluatedExpression.class, + Object.class + ); + + private static final Field ANNOTATION_DEFAULT_VALUES_PROVIDER = ReflectionUtils.getRequiredField( + AnnotationMetadataSupport.class, + "ANNOTATION_DEFAULT_VALUES_PROVIDER" + ); + + @Internal + public static ExpressionDef instantiateNewMetadata(ClassTypeDef owningType, + MutableAnnotationMetadata annotationMetadata, + Map loadTypeMethods) { + return instantiateInternal(owningType, annotationMetadata, true, loadTypeMethods); + } + + @Internal + public static ExpressionDef instantiateNewMetadataHierarchy( + ClassTypeDef owningType, + AnnotationMetadataHierarchy hierarchy, + Map loadTypeMethods) { + + if (hierarchy.isEmpty()) { + return emptyMetadata(); + } + List notEmpty = CollectionUtils.iterableToList(hierarchy) + .stream().filter(h -> !h.isEmpty()).toList(); + if (notEmpty.size() == 1) { + return pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, notEmpty.get(0)); + } + + return TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY.instantiate( + CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY, + + TYPE_ANNOTATION_METADATA.array().instantiate( + pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, hierarchy.getRootMetadata()), + pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, hierarchy.getDeclaredMetadata()) + ) + ); + } + + @Internal + public static ExpressionDef annotationMetadataReference(AnnotationMetadataReference annotationMetadata) { + return ClassTypeDef.of(annotationMetadata.getClassName()).getStaticField(FIELD_ANNOTATION_METADATA); + } + + @Internal + private static ExpressionDef pushNewAnnotationMetadataOrReference( + ClassTypeDef owningType, + Map loadTypeMethods, + AnnotationMetadata annotationMetadata) { + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + // Synthetic property getters / setters can consist of field + (setter / getter) annotation hierarchy + annotationMetadata = MutableAnnotationMetadata.of(annotationMetadataHierarchy); + } + if (annotationMetadata.isEmpty()) { + return emptyMetadata(); + } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + return instantiateNewMetadata(owningType, mutableAnnotationMetadata, loadTypeMethods); + } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { + return annotationMetadataReference(reference); + } else { + throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); + } + } + + public static ExpressionDef emptyMetadata() { + return TYPE_ANNOTATION_METADATA.getStaticField("EMPTY_METADATA", TYPE_ANNOTATION_METADATA); + } + + @Internal + public static void writeAnnotationDefaults( + List statements, + ClassTypeDef owningType, + MutableAnnotationMetadata annotationMetadata, + Map loadTypeMethods) { + final Map> annotationDefaultValues = annotationMetadata.annotationDefaultValues; + + if (CollectionUtils.isNotEmpty(annotationDefaultValues)) { + writeAnnotationDefaultsInternal(statements, owningType, loadTypeMethods, annotationDefaultValues, new HashSet<>()); + } + if (annotationMetadata.annotationRepeatableContainer != null && !annotationMetadata.annotationRepeatableContainer.isEmpty()) { + Map annotationRepeatableContainer = new LinkedHashMap<>(annotationMetadata.annotationRepeatableContainer); + AnnotationMetadataSupport.getCoreRepeatableAnnotationsContainers().forEach(annotationRepeatableContainer::remove); + AnnotationMetadataSupport.registerRepeatableAnnotations(annotationRepeatableContainer); + if (!annotationRepeatableContainer.isEmpty()) { + statements.add( + TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( + METHOD_REGISTER_REPEATABLE_ANNOTATIONS, + stringMapOf(owningType, annotationRepeatableContainer, loadTypeMethods) + ) + ); + } + } + } + + private static void writeAnnotationDefaultsInternal(List statements, + ClassTypeDef owningType, + Map loadTypeMethods, + Map> annotationDefaultValues, + Set writtenAnnotations) { + for (Map.Entry> entry : annotationDefaultValues.entrySet()) { + writeAnnotationDefaultsInternal(statements, + owningType, + loadTypeMethods, + writtenAnnotations, + entry.getKey(), + entry.getValue()); + } + } + + @NonNull + private static void writeAnnotationDefaultsInternal(List statements, + ClassTypeDef owningType, + Map loadTypeMethods, + Set writtenAnnotations, + String annotationName, + Map annotationValues) { + final boolean typeOnly = CollectionUtils.isEmpty(annotationValues); + + // skip already registered + if (typeOnly && AnnotationMetadataSupport.getRegisteredAnnotationType(annotationName).isPresent() || AnnotationMetadataSupport.getCoreAnnotationDefaults().containsKey(annotationName)) { + return; + } + + if (!writtenAnnotations.add(annotationName)) { + return; + } + + for (Map.Entry values : annotationValues.entrySet()) { + Object value = values.getValue(); + if (value instanceof AnnotationValue annotationValue && CollectionUtils.isNotEmpty(annotationValue.getDefaultValues())) { + writeAnnotationDefaultsInternal( + statements, + owningType, + loadTypeMethods, + writtenAnnotations, + annotationValue.getAnnotationName(), + annotationValue.getDefaultValues() + ); + } + } + + if (!typeOnly) { + statements.add( + TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( + METHOD_REGISTER_ANNOTATION_DEFAULTS, + invokeLoadClassValueMethod(owningType, loadTypeMethods, new AnnotationClassValue<>(annotationName)), + stringMapOf(owningType, annotationValues, loadTypeMethods) + ) + ); + } else { + statements.add( + TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( + METHOD_REGISTER_ANNOTATION_TYPE, + invokeLoadClassValueMethod(owningType, loadTypeMethods, new AnnotationClassValue<>(annotationName)) + ) + ); + } + writtenAnnotations.add(annotationName); + } + + private static ExpressionDef instantiateInternal( + ClassTypeDef owningType, + MutableAnnotationMetadata annotationMetadata, + boolean isNew, + Map loadTypeMethods) { + if (!isNew) { + throw new IllegalStateException(); + } + Map> annotationsByStereotype = annotationMetadata.annotationsByStereotype; + if (annotationMetadata.getSourceRetentionAnnotations() != null && annotationsByStereotype != null) { + annotationsByStereotype = new LinkedHashMap<>(annotationsByStereotype); + for (String sourceRetentionAnnotation : annotationMetadata.getSourceRetentionAnnotations()) { + annotationsByStereotype.remove(sourceRetentionAnnotation); + } + } + return TYPE_DEFAULT_ANNOTATION_METADATA + .instantiate( + CONSTRUCTOR_ANNOTATION_METADATA, + + // 1st argument: the declared annotations + pushCreateAnnotationData(owningType, annotationMetadata.declaredAnnotations, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + // 2nd argument: the declared stereotypes + pushCreateAnnotationData(owningType, annotationMetadata.declaredStereotypes, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + // 3rd argument: all stereotypes + pushCreateAnnotationData(owningType, annotationMetadata.allStereotypes, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + // 4th argument: all annotations + pushCreateAnnotationData(owningType, annotationMetadata.allAnnotations, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + // 5th argument: annotations by stereotype, + ExpressionsUtils.stringMapOf(annotationsByStereotype, false, Collections.emptyList(), ExpressionsUtils::listOfString), + // 6th argument: has property expressions, + ExpressionDef.constant(annotationMetadata.hasPropertyExpressions()), + // 7th argument: has evaluated expressions + ExpressionDef.constant(annotationMetadata.hasEvaluatedExpressions()) + ); + } + + private static ExpressionDef pushCreateAnnotationData( + ClassTypeDef declaringType, + Map> annotationData, + Map loadTypeMethods, + Set sourceRetentionAnnotations) { + if (annotationData != null) { + annotationData = new LinkedHashMap<>(annotationData); + for (String sourceRetentionAnnotation : sourceRetentionAnnotations) { + annotationData.remove(sourceRetentionAnnotation); + } + } + + return ExpressionsUtils.stringMapOf(annotationData, false, Collections.emptyMap(), + attributes -> ExpressionsUtils.stringMapOf(attributes, true, null, + value -> asValueExpression(declaringType, value, loadTypeMethods))); + } + + private static ExpressionDef asValueExpression(ClassTypeDef declaringType, + Object value, + Map loadTypeMethods) { + if (value == null) { + throw new IllegalStateException("Cannot map null value in: " + declaringType.getName()); + } + if (value instanceof Enum anEnum) { + return ExpressionDef.constant(anEnum.name()); + } + if (value instanceof Boolean || value instanceof String || value instanceof Number || value instanceof Character) { + return ExpressionDef.constant(value); + } + if (value instanceof AnnotationClassValue acv) { + if (acv.isInstantiated()) { + return TYPE_ANNOTATION_CLASS_VALUE + .instantiate(CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE, + ClassTypeDef.of(acv.getName()).instantiate() + ); + } else { + return invokeLoadClassValueMethod(declaringType, loadTypeMethods, acv); + } + } + if (value.getClass().isArray()) { + Class arrayComponentType = value.getClass().getComponentType(); + if (Enum.class.isAssignableFrom(arrayComponentType)) { + // Express enums as strings + arrayComponentType = String.class; + } + return TypeDef.of(arrayComponentType).array().instantiate(Arrays.stream(getArray(value)) + .map(v -> asValueExpression(declaringType, v, loadTypeMethods)) + .toList()); + } + if (value instanceof Collection collection) { + if (collection.isEmpty()) { + return ExpressionDef.constant(new Object[0]); + } + Class componentType = null; + for (Object o : collection) { + if (componentType == null) { + componentType = o.getClass(); + } else if (!o.getClass().equals(componentType)) { + componentType = Object.class; + break; + } + } + if (Enum.class.isAssignableFrom(componentType)) { + // Express enums as strings + componentType = String.class; + } + return TypeDef.of(componentType).array() + .instantiate(collection.stream().map(i -> asValueExpression(declaringType, i, loadTypeMethods)).toList()); + } + if (value instanceof AnnotationValue data) { + return ClassTypeDef.of(AnnotationValue.class) + .instantiate( + CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP, + ExpressionDef.constant(data.getAnnotationName()), + stringMapOf(declaringType, data.getValues(), loadTypeMethods), + ClassTypeDef.of(AnnotationMetadataSupport.class).getStaticField(ANNOTATION_DEFAULT_VALUES_PROVIDER) + ); + } + if (value instanceof EvaluatedExpressionReference expressionReference) { + Object annotationValue = expressionReference.annotationValue(); + if (annotationValue instanceof String || annotationValue instanceof String[]) { + return ClassTypeDef.of(expressionReference.expressionClassName()) + .instantiate( + CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION, + + ExpressionDef.constant(annotationValue) + ); + } else { + throw new IllegalStateException(); + } + } + throw new IllegalStateException("Unsupported Map value: " + value + " " + value.getClass().getName()); + } + + private static Object[] getArray(Object val) { + if (val instanceof Object[]) { + return (Object[]) val; + } + Object[] outputArray = new Object[Array.getLength(val)]; + for (int i = 0; i < outputArray.length; ++i) { + outputArray[i] = Array.get(val, i); + } + return outputArray; + } + + public static boolean isSupportedMapValue(Object value) { + if (value == null) { + return false; + } else if (value instanceof Boolean) { + return true; + } else if (value instanceof String) { + return true; + } else if (value instanceof AnnotationClassValue) { + return true; + } else if (value instanceof Enum) { + return true; + } else if (value.getClass().isArray()) { + return true; + } else if (value instanceof Collection) { + return true; + } else if (value instanceof Map) { + return true; + } else if (value instanceof Long) { + return true; + } else if (value instanceof Double) { + return true; + } else if (value instanceof Float) { + return true; + } else if (value instanceof Byte) { + return true; + } else if (value instanceof Short) { + return true; + } else if (value instanceof Character) { + return true; + } else if (value instanceof Number) { + return true; + } else if (value instanceof AnnotationValue) { + return true; + } else if (value instanceof EvaluatedExpressionReference) { + return true; + } else if (value instanceof Class) { + // The class should be added as AnnotationClassValue + return false; + } + return false; + } + + @NonNull + public static ExpressionDef.InvokeStaticMethod invokeLoadClassValueMethod(ClassTypeDef declaringType, + Map loadTypeMethods, + AnnotationClassValue acv) { + final String typeName = acv.getName(); + + final MethodDef loadTypeGeneratorMethod = loadTypeMethods.computeIfAbsent(typeName, type -> { + + final String methodName = LOAD_CLASS_PREFIX + loadTypeMethods.size(); + + // This logic will generate a method such as the following, allowing non-dynamic classloading: + // + // AnnotationClassValue $micronaut_load_class_value_0() { + // try { + // return new AnnotationClassValue(test.MyClass.class); + // } catch(Throwable e) { + // return new AnnotationClassValue("test.MyClass"); + // } + // } + + return MethodDef.builder(methodName) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .returns(TYPE_ANNOTATION_CLASS_VALUE) + .buildStatic(methodParameters -> StatementDef.doTry( + TYPE_ANNOTATION_CLASS_VALUE.instantiate( + CONSTRUCTOR_CLASS_VALUE_WITH_CLASS, + ExpressionDef.constant(TypeDef.of(typeName)) + ).returning() + ).doCatch(Throwable.class, exceptionVar -> TYPE_ANNOTATION_CLASS_VALUE.instantiate( + CONSTRUCTOR_CLASS_VALUE, + ExpressionDef.constant(typeName) + ).returning())); + }); + + return declaringType.invokeStatic(loadTypeGeneratorMethod); + } + + public static MethodDef getAnnotationMetadataMethodDef(ClassTypeDef owningType, AnnotationMetadata am) { + return MethodDef.builder("getAnnotationMetadata").returns(TYPE_ANNOTATION_METADATA) + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) -> { + // in order to save memory of a method doesn't have any annotations of its own but merely references class metadata + // then we set up an annotation metadata reference from the method to the class (or inherited method) metadata + AnnotationMetadata annotationMetadata = am.getTargetAnnotationMetadata(); + if (annotationMetadata.isEmpty()) { + return AnnotationMetadataGenUtils.EMPTY_METADATA.returning(); + } + if (annotationMetadata instanceof AnnotationMetadataReference reference) { + return annotationMetadataReference(reference).returning(); + } + return owningType.getStaticField(FIELD_ANNOTATION_METADATA).returning(); + }); + } + + @Nullable + public static FieldDef createAnnotationMetadataField(ClassTypeDef targetType, + AnnotationMetadata annotationMetadata, + Map loadTypeMethods) { + if (annotationMetadata instanceof AnnotationMetadataReference) { + return null; + } + FieldDef.FieldDefBuilder fieldDefBuilder = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); + + ExpressionDef initializer; + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (annotationMetadata.isEmpty()) { + initializer = AnnotationMetadataGenUtils.EMPTY_METADATA; + } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + initializer = AnnotationMetadataGenUtils.instantiateNewMetadata( + targetType, + mutableAnnotationMetadata, + loadTypeMethods + ); + } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + initializer = AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(targetType, annotationMetadataHierarchy, loadTypeMethods); + } else { + throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); + } + fieldDefBuilder.initializer(initializer); + + return fieldDefBuilder.build(); + } + + public static void writeAnnotationDefault(List statements, + ClassTypeDef targetClassType, + AnnotationMetadata annotationMetadata, + Map loadTypeMethods) { + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (annotationMetadata.isEmpty()) { + return; + } + if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + annotationMetadata = annotationMetadataHierarchy.merge(); + } + if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + AnnotationMetadataGenUtils.writeAnnotationDefaults( + statements, + targetClassType, + mutableAnnotationMetadata, + loadTypeMethods + ); + } else { + throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); + } + } + + private static ExpressionDef stringMapOf(ClassTypeDef declaringType, + Map annotationData, + Map loadTypeMethods) { + return ExpressionsUtils.stringMapOf( + annotationData, + true, + null, + AnnotationMetadataGenUtils::isSupportedMapValue, + o -> asValueExpression(declaringType, o, loadTypeMethods) + ); + } + +} diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java index 1b985c2363d..94ed7d17182 100644 --- a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java @@ -15,803 +15,58 @@ */ package io.micronaut.inject.annotation; -import io.micronaut.context.expressions.AbstractEvaluatedExpression; -import io.micronaut.core.annotation.AnnotationClassValue; -import io.micronaut.core.annotation.AnnotationDefaultValuesProvider; import io.micronaut.core.annotation.AnnotationMetadata; -import io.micronaut.core.annotation.AnnotationMetadataDelegate; -import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.annotation.AnnotationMetadataProvider; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.UsedByGeneratedCode; -import io.micronaut.core.expressions.EvaluatedExpressionReference; import io.micronaut.core.reflect.ReflectionUtils; -import io.micronaut.core.util.ArrayUtils; -import io.micronaut.core.util.CollectionUtils; -import io.micronaut.inject.ast.ClassElement; -import io.micronaut.inject.writer.AbstractAnnotationMetadataWriter; -import io.micronaut.inject.writer.AbstractClassFileWriter; -import io.micronaut.inject.writer.ClassGenerationException; -import io.micronaut.inject.writer.ClassWriterOutputVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; - -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Array; -import java.util.Collection; -import java.util.Collections; +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; + +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; /** - * Responsible for writing class files that are instances of {@link AnnotationMetadata}. + * Responsible for writing class files that are instances of {@link AnnotationMetadataProvider}. * * @author Graeme Rocher + * @author Denis Stepanov * @since 1.0 */ @Internal -public class AnnotationMetadataWriter extends AbstractClassFileWriter { - - private static final Type TYPE_DEFAULT_ANNOTATION_METADATA = Type.getType(DefaultAnnotationMetadata.class); - private static final Type TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY = Type.getType(AnnotationMetadataHierarchy.class); - private static final Type TYPE_ANNOTATION_CLASS_VALUE = Type.getType(AnnotationClassValue.class); - - private static final org.objectweb.asm.commons.Method METHOD_REGISTER_ANNOTATION_DEFAULTS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - DefaultAnnotationMetadata.class, - "registerAnnotationDefaults", - AnnotationClassValue.class, - Map.class - ) - ); - - private static final org.objectweb.asm.commons.Method METHOD_REGISTER_ANNOTATION_TYPE = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - DefaultAnnotationMetadata.class, - "registerAnnotationType", - AnnotationClassValue.class - ) - ); - - private static final org.objectweb.asm.commons.Method METHOD_REGISTER_REPEATABLE_ANNOTATIONS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - DefaultAnnotationMetadata.class, - "registerRepeatableAnnotations", - Map.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_ANNOTATION_METADATA = Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - DefaultAnnotationMetadata.class, - Map.class, - Map.class, - Map.class, - Map.class, - Map.class, - boolean.class, - boolean.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - AnnotationMetadataHierarchy.class, - AnnotationMetadata[].class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - io.micronaut.core.annotation.AnnotationValue.class, - String.class, - Map.class, - AnnotationDefaultValuesProvider.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_CLASS_VALUE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - AnnotationClassValue.class, - String.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_CLASS_VALUE_WITH_CLASS = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - AnnotationClassValue.class, - Class.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - AnnotationClassValue.class, - Object.class - ) - ); - - private static final org.objectweb.asm.commons.Method CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor( - AbstractEvaluatedExpression.class, - Object.class - )); - - private static final String LOAD_CLASS_PREFIX = "$micronaut_load_class_value_"; - - private final String className; - private final AnnotationMetadata annotationMetadata; - private final boolean writeAnnotationDefaults; - - /** - * Constructs a new writer for the given class name and metadata. - * - * @param className The class name for which the metadata relates - * @param originatingElement The originating element - * @param annotationMetadata The annotation metadata - * @param writeAnnotationDefaults Whether annotations defaults should be written - * @deprecated No longer needs to be instantiated directly, just use the static methods - */ - @Deprecated - public AnnotationMetadataWriter( - String className, - ClassElement originatingElement, - AnnotationMetadata annotationMetadata, - boolean writeAnnotationDefaults) { - super(originatingElement); - this.className = className + AnnotationMetadata.CLASS_NAME_SUFFIX; - if (annotationMetadata instanceof AnnotationMetadataDelegate delegate) { - annotationMetadata = delegate.getAnnotationMetadata(); - } - if (annotationMetadata instanceof DefaultAnnotationMetadata) { - this.annotationMetadata = annotationMetadata; - } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - this.annotationMetadata = annotationMetadataHierarchy.getDeclaredMetadata(); - } else { - throw new ClassGenerationException("Compile time metadata required to generate class: " + className); - } - this.writeAnnotationDefaults = writeAnnotationDefaults; - } - - /** - * Constructs a new writer for the given class name and metadata. - * - * @param className The class name for which the metadata relates - * @param originatingElement The originating element - * @param annotationMetadata The annotation metadata - * @deprecated No longer needs to be instantiated directly, just use the static methods - */ - @Deprecated - public AnnotationMetadataWriter( - String className, - ClassElement originatingElement, - AnnotationMetadata annotationMetadata) { - this(className, originatingElement, annotationMetadata, false); - } - - /** - * @return The class name that this metadata will generate - */ - public String getClassName() { - return className; - } +public class AnnotationMetadataWriter { /** - * Accept an {@link ClassWriterOutputVisitor} to write all generated classes. + * Create a new {@link AnnotationMetadataProvider} class that is including the annotation metadata. * - * @param outputVisitor The {@link ClassWriterOutputVisitor} - * @throws IOException If an error occurs - */ - @Override - public void accept(ClassWriterOutputVisitor outputVisitor) throws IOException { - ClassWriter classWriter = generateClassBytes(); - if (classWriter != null) { - - try (OutputStream outputStream = outputVisitor.visitClass(className, getOriginatingElements())) { - outputStream.write(classWriter.toByteArray()); - } - } - } - - /** - * Write the class to the output stream, such a JavaFileObject created from a java annotation processor Filer object. - * - * @param outputStream the output stream pointing to the target class file - */ - public void writeTo(OutputStream outputStream) { - try { - ClassWriter classWriter = generateClassBytes(); - - writeClassToDisk(outputStream, classWriter); - } catch (Throwable e) { - throw new ClassGenerationException("Error generating annotation metadata: " + e.getMessage(), e); - } - } - - /** - * Writes out the byte code necessary to instantiate the given {@link MutableAnnotationMetadata}. - * - * @param owningType The owning type - * @param declaringClassWriter The declaring class writer - * @param generatorAdapter The generator adapter - * @param annotationMetadata The annotation metadata - * @param defaultsStorage The annotation defaults - * @param loadTypeMethods The generated load type methods - */ - @Internal - @UsedByGeneratedCode - public static void instantiateNewMetadata(Type owningType, ClassWriter declaringClassWriter, GeneratorAdapter generatorAdapter, MutableAnnotationMetadata annotationMetadata, Map defaultsStorage, Map loadTypeMethods) { - instantiateInternal(owningType, declaringClassWriter, generatorAdapter, annotationMetadata, true, defaultsStorage, loadTypeMethods); - } - - /** - * Writes out the byte code necessary to instantiate the given {@link AnnotationMetadataHierarchy}. - * - * @param owningType The owning type - * @param classWriter The declaring class writer - * @param generatorAdapter The generator adapter - * @param hierarchy The annotation metadata - * @param defaultsStorage The annotation defaults - * @param loadTypeMethods The generated load type methods - */ - @Internal - @UsedByGeneratedCode - public static void instantiateNewMetadataHierarchy( - Type owningType, - ClassWriter classWriter, - GeneratorAdapter generatorAdapter, - AnnotationMetadataHierarchy hierarchy, - Map defaultsStorage, - Map loadTypeMethods) { - - if (hierarchy.isEmpty()) { - generatorAdapter.getStatic(Type.getType(AnnotationMetadata.class), "EMPTY_METADATA", Type.getType(AnnotationMetadata.class)); - return; - } - List notEmpty = CollectionUtils.iterableToList(hierarchy) - .stream().filter(h -> !h.isEmpty()).toList(); - if (notEmpty.size() == 1) { - pushNewAnnotationMetadataOrReference(owningType, classWriter, generatorAdapter, defaultsStorage, loadTypeMethods, notEmpty.get(0)); - return; - } - - generatorAdapter.visitTypeInsn(NEW, TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY.getInternalName()); - generatorAdapter.visitInsn(DUP); - - pushNewArray(generatorAdapter, AnnotationMetadata.class, 2); - pushStoreInArray(generatorAdapter, 0, 2, () -> { - final AnnotationMetadata rootMetadata = hierarchy.getRootMetadata(); - pushNewAnnotationMetadataOrReference(owningType, classWriter, generatorAdapter, defaultsStorage, loadTypeMethods, rootMetadata); - }); - pushStoreInArray(generatorAdapter, 1, 2, () -> { - final AnnotationMetadata declaredMetadata = hierarchy.getDeclaredMetadata(); - pushNewAnnotationMetadataOrReference(owningType, classWriter, generatorAdapter, defaultsStorage, loadTypeMethods, declaredMetadata); - }); - - // invoke the constructor - generatorAdapter.invokeConstructor(TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY, CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY); - } - - /** - * Pushes an annotation metadata reference. - * - * @param generatorAdapter The generator adapter + * @param className The class name * @param annotationMetadata The metadata + * @return The generated bytecode */ - @Internal - public static void pushAnnotationMetadataReference(GeneratorAdapter generatorAdapter, AnnotationMetadataReference annotationMetadata) { - final String className = annotationMetadata.getClassName(); - final Type type = getTypeReferenceForName(className); - generatorAdapter.getStatic(type, AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } - - @Internal - private static void pushNewAnnotationMetadataOrReference( - Type owningType, - ClassWriter classWriter, - GeneratorAdapter generatorAdapter, - Map defaultsStorage, - Map loadTypeMethods, - AnnotationMetadata annotationMetadata) { - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - // Synthetic property getters / setters can consist of field + (setter / getter) annotation hierarchy - annotationMetadata = MutableAnnotationMetadata.of(annotationMetadataHierarchy); - } - if (annotationMetadata.isEmpty()) { - generatorAdapter.getStatic(Type.getType(AnnotationMetadata.class), "EMPTY_METADATA", Type.getType(AnnotationMetadata.class)); - } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - instantiateNewMetadata( - owningType, - classWriter, - generatorAdapter, - mutableAnnotationMetadata, - defaultsStorage, - loadTypeMethods + public static byte[] write(String className, AnnotationMetadata annotationMetadata) { + Map loadTypeMethods = new HashMap<>(); + ClassTypeDef type = ClassTypeDef.of(className + AnnotationMetadata.CLASS_NAME_SUFFIX); + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(type, annotationMetadata, loadTypeMethods); + + List statements = new ArrayList<>(); + AnnotationMetadataGenUtils.writeAnnotationDefault(statements, type, annotationMetadata, loadTypeMethods); + + ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(type.getName()) + .addSuperinterface(TypeDef.of(AnnotationMetadataProvider.class)) + .addField(annotationMetadataField) + .addStaticInitializer(StatementDef.multi(statements)) + .addMethod(MethodDef.override( + ReflectionUtils.getRequiredMethod(AnnotationMetadataProvider.class, "getAnnotationMetadata") + ).build((aThis, methodParameters) -> aThis.type().getStaticField(annotationMetadataField).returning()) ); - } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { - pushAnnotationMetadataReference(generatorAdapter, reference); - } else { - throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); - } - } - - /** - * Writes out the byte code necessary to instantiate the given {@link MutableAnnotationMetadata}. - * - * @param annotationMetadata The annotation metadata - * @param classWriter The class writer - * @param owningType The owning type - * @param defaultsStorage The annotation defaults - * @param loadTypeMethods The generated load type methods - */ - @Internal - public static void writeAnnotationDefaults(MutableAnnotationMetadata annotationMetadata, ClassWriter classWriter, Type owningType, Map defaultsStorage, Map loadTypeMethods) { - final Map> annotationDefaultValues = annotationMetadata.annotationDefaultValues; - if (CollectionUtils.isNotEmpty(annotationDefaultValues)) { - - MethodVisitor si = classWriter.visitMethod(ACC_STATIC, "", "()V", null, null); - GeneratorAdapter staticInit = new GeneratorAdapter(si, ACC_STATIC, "", "()V"); - - writeAnnotationDefaults(owningType, classWriter, staticInit, annotationMetadata, defaultsStorage, loadTypeMethods); - staticInit.visitInsn(RETURN); - - staticInit.visitMaxs(1, 1); - staticInit.visitEnd(); - } - } - - /** - * Write annotation defaults into the given static init block. - * - * @param owningType The owning type - * @param classWriter The class writer - * @param staticInit The staitc init - * @param annotationMetadata The annotation metadata - * @param defaultsStorage The annotation defaults - * @param loadTypeMethods The load type methods - */ - @Internal - public static void writeAnnotationDefaults( - Type owningType, - ClassWriter classWriter, - GeneratorAdapter staticInit, - MutableAnnotationMetadata annotationMetadata, - Map defaultsStorage, - Map loadTypeMethods) { - final Map> annotationDefaultValues = annotationMetadata.annotationDefaultValues; - if (CollectionUtils.isNotEmpty(annotationDefaultValues)) { - writeAnnotationDefaultsInternal(owningType, classWriter, staticInit, defaultsStorage, loadTypeMethods, annotationDefaultValues, new HashSet<>()); - } - if (annotationMetadata.annotationRepeatableContainer != null && !annotationMetadata.annotationRepeatableContainer.isEmpty()) { - Map annotationRepeatableContainer = new LinkedHashMap<>(annotationMetadata.annotationRepeatableContainer); - AnnotationMetadataSupport.getCoreRepeatableAnnotationsContainers().forEach(annotationRepeatableContainer::remove); - AnnotationMetadataSupport.registerRepeatableAnnotations(annotationRepeatableContainer); - if (!annotationRepeatableContainer.isEmpty()) { - pushStringMapOf(staticInit, annotationRepeatableContainer, true, null, v -> pushValue(owningType, classWriter, staticInit, v, defaultsStorage, loadTypeMethods, true)); - staticInit.invokeStatic(TYPE_DEFAULT_ANNOTATION_METADATA, METHOD_REGISTER_REPEATABLE_ANNOTATIONS); - } - } - } - - private static void writeAnnotationDefaultsInternal(Type owningType, - ClassWriter classWriter, - GeneratorAdapter staticInit, - Map defaultsStorage, - Map loadTypeMethods, - Map> annotationDefaultValues, - Set writtenAnnotations) { - for (Map.Entry> entry : annotationDefaultValues.entrySet()) { - writeAnnotationDefaultsInternal(owningType, - classWriter, - staticInit, - defaultsStorage, - loadTypeMethods, - writtenAnnotations, - entry.getKey(), - entry.getValue()); - } - } - - private static void writeAnnotationDefaultsInternal(Type owningType, - ClassWriter classWriter, - GeneratorAdapter staticInit, - Map defaultsStorage, - Map loadTypeMethods, - Set writtenAnnotations, - String annotationName, - Map annotationValues) { - final boolean typeOnly = CollectionUtils.isEmpty(annotationValues); - - // skip already registered - if (typeOnly && AnnotationMetadataSupport.getRegisteredAnnotationType(annotationName).isPresent() || AnnotationMetadataSupport.getCoreAnnotationDefaults().containsKey(annotationName)) { - return; - } - - if (!writtenAnnotations.add(annotationName)) { - return; - } - - for (Map.Entry values : annotationValues.entrySet()) { - Object value = values.getValue(); - if (value instanceof AnnotationValue annotationValue && CollectionUtils.isNotEmpty(annotationValue.getDefaultValues())) { - writeAnnotationDefaultsInternal(owningType, - classWriter, - staticInit, - defaultsStorage, - loadTypeMethods, - writtenAnnotations, - annotationValue.getAnnotationName(), - annotationValue.getDefaultValues()); - } - } - - invokeLoadClassValueMethod(owningType, classWriter, staticInit, loadTypeMethods, new AnnotationClassValue<>(annotationName)); - - if (!typeOnly) { - pushStringMapOf(staticInit, annotationValues, true, null, v -> pushValue(owningType, classWriter, staticInit, v, defaultsStorage, loadTypeMethods, true)); - staticInit.invokeStatic(TYPE_DEFAULT_ANNOTATION_METADATA, METHOD_REGISTER_ANNOTATION_DEFAULTS); - } else { - staticInit.invokeStatic(TYPE_DEFAULT_ANNOTATION_METADATA, METHOD_REGISTER_ANNOTATION_TYPE); - } - - writtenAnnotations.add(annotationName); - } - - private static void instantiateInternal( - Type owningType, - ClassWriter declaringClassWriter, - GeneratorAdapter generatorAdapter, - MutableAnnotationMetadata annotationMetadata, - boolean isNew, - Map defaultsStorage, - Map loadTypeMethods) { - if (isNew) { - generatorAdapter.visitTypeInsn(NEW, TYPE_DEFAULT_ANNOTATION_METADATA.getInternalName()); - generatorAdapter.visitInsn(DUP); - } else { - generatorAdapter.loadThis(); - } - // 1st argument: the declared annotations - pushCreateAnnotationData(owningType, declaringClassWriter, generatorAdapter, annotationMetadata.declaredAnnotations, defaultsStorage, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()); - // 2nd argument: the declared stereotypes - pushCreateAnnotationData(owningType, declaringClassWriter, generatorAdapter, annotationMetadata.declaredStereotypes, defaultsStorage, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()); - // 3rd argument: all stereotypes - pushCreateAnnotationData(owningType, declaringClassWriter, generatorAdapter, annotationMetadata.allStereotypes, defaultsStorage, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()); - // 4th argument: all annotations - pushCreateAnnotationData(owningType, declaringClassWriter, generatorAdapter, annotationMetadata.allAnnotations, defaultsStorage, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()); - // 5th argument: annotations by stereotype - Map> annotationsByStereotype = annotationMetadata.annotationsByStereotype; - if (annotationMetadata.getSourceRetentionAnnotations() != null && annotationsByStereotype != null) { - annotationsByStereotype = new LinkedHashMap<>(annotationsByStereotype); - for (String sourceRetentionAnnotation : annotationMetadata.getSourceRetentionAnnotations()) { - annotationsByStereotype.remove(sourceRetentionAnnotation); - } - } - pushStringMapOf(generatorAdapter, annotationsByStereotype, false, Collections.emptyList(), list -> pushListOfString(generatorAdapter, list)); - // 6th argument: has property expressions - generatorAdapter.push(annotationMetadata.hasPropertyExpressions()); - // 7th argument: has evaluated expressions - generatorAdapter.push(annotationMetadata.hasEvaluatedExpressions()); - - // invoke the constructor - generatorAdapter.invokeConstructor(TYPE_DEFAULT_ANNOTATION_METADATA, CONSTRUCTOR_ANNOTATION_METADATA); - - } - - private ClassWriter generateClassBytes() { - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - final Type owningType = getTypeReferenceForName(className); - startClass(classWriter, getInternalName(className), TYPE_DEFAULT_ANNOTATION_METADATA); - - GeneratorAdapter constructor = startConstructor(classWriter); - MutableAnnotationMetadata annotationMetadata = (MutableAnnotationMetadata) this.annotationMetadata; - - Map defaultsStorage = new HashMap<>(3); - final HashMap loadTypeMethods = new HashMap<>(5); - instantiateInternal( - owningType, - classWriter, - constructor, - annotationMetadata, - false, - defaultsStorage, - loadTypeMethods); - constructor.visitInsn(RETURN); - constructor.visitMaxs(1, 1); - constructor.visitEnd(); - defaultsStorage.clear(); // Defaults were valid only in the constructor scope - - if (writeAnnotationDefaults) { - writeAnnotationDefaults(annotationMetadata, classWriter, owningType, defaultsStorage, loadTypeMethods); - } - for (GeneratorAdapter adapter : loadTypeMethods.values()) { - adapter.visitMaxs(3, 1); - adapter.visitEnd(); - } - classWriter.visitEnd(); - return classWriter; - } - - private static void pushCreateAnnotationData( - Type declaringType, - ClassWriter declaringClassWriter, - GeneratorAdapter methodVisitor, - Map> annotationData, - Map defaultsStorage, - Map loadTypeMethods, - Set sourceRetentionAnnotations) { - if (annotationData != null) { - annotationData = new LinkedHashMap<>(annotationData); - for (String sourceRetentionAnnotation : sourceRetentionAnnotations) { - annotationData.remove(sourceRetentionAnnotation); - } - } - - pushStringMapOf(methodVisitor, annotationData, false, Collections.emptyMap(), attributes -> - pushStringMapOf(methodVisitor, attributes, true, null, v -> - pushValue(declaringType, declaringClassWriter, methodVisitor, v, defaultsStorage, loadTypeMethods, true) - ) - ); - } - - private static void pushValue(Type declaringType, ClassVisitor declaringClassWriter, - GeneratorAdapter methodVisitor, - Object value, - Map defaultsStorage, - Map loadTypeMethods, - boolean boxValue) { - if (value == null) { - throw new IllegalStateException("Cannot map null value in: " + declaringType.getClassName()); - } else if (value instanceof Boolean boolean1) { - methodVisitor.push(boolean1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(boolean.class, methodVisitor); - } - } else if (value instanceof String) { - methodVisitor.push(value.toString()); - } else if (value instanceof AnnotationClassValue acv) { - if (acv.isInstantiated()) { - methodVisitor.visitTypeInsn(NEW, TYPE_ANNOTATION_CLASS_VALUE.getInternalName()); - methodVisitor.visitInsn(DUP); - methodVisitor.visitTypeInsn(NEW, getInternalName(acv.getName())); - methodVisitor.visitInsn(DUP); - methodVisitor.invokeConstructor(getTypeReferenceForName(acv.getName()), new Method(CONSTRUCTOR_NAME, getConstructorDescriptor())); - methodVisitor.invokeConstructor(TYPE_ANNOTATION_CLASS_VALUE, CONSTRUCTOR_CLASS_VALUE_WITH_INSTANCE); - } else { - invokeLoadClassValueMethod(declaringType, declaringClassWriter, methodVisitor, loadTypeMethods, acv); - } - } else if (value instanceof Enum enumObject) { - methodVisitor.push(enumObject.name()); // Always store enum values as string - } else if (value.getClass().isArray()) { - Class jt; - Class arrayComponentType = value.getClass().getComponentType(); - if (arrayComponentType.isEnum() || arrayComponentType.equals(java.lang.Enum.class)) { - jt = String.class; // Always store enum values as string - } else { - jt = ReflectionUtils.getPrimitiveType(arrayComponentType); - } - final Type componentType = Type.getType(jt); - int len = Array.getLength(value); - if (Object.class == jt && len == 0) { - pushEmptyObjectsArray(methodVisitor); - } else { - pushNewArray(methodVisitor, jt, len); - for (int i = 0; i < len; i++) { - final Object v = Array.get(value, i); - pushStoreInArray(methodVisitor, componentType, i, len, () -> - pushValue(declaringType, declaringClassWriter, methodVisitor, v, defaultsStorage, loadTypeMethods, !jt.isPrimitive()) - ); - } - } - } else if (value instanceof Collection collection) { - if (collection.isEmpty()) { - pushEmptyObjectsArray(methodVisitor); - } else { - List array = CollectionUtils.iterableToList(collection); - int len = array.size(); - boolean first = true; - Class arrayType = Object.class; - for (int i = 0; i < len; i++) { - Object v = array.get(i); - - if (first) { - arrayType = v == null ? Object.class : v.getClass(); - pushNewArray(methodVisitor, arrayType, len); - first = false; - } - Class finalArrayType = arrayType; - pushStoreInArray(methodVisitor, Type.getType(arrayType), i, len, () -> - pushValue(declaringType, declaringClassWriter, methodVisitor, v, defaultsStorage, loadTypeMethods, !finalArrayType.isPrimitive()) - ); - } - } - } else if (value instanceof Long long1) { - methodVisitor.push(long1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(long.class, methodVisitor); - } - } else if (value instanceof Double double1) { - methodVisitor.push(double1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(double.class, methodVisitor); - } - } else if (value instanceof Float float1) { - methodVisitor.push(float1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(float.class, methodVisitor); - } - } else if (value instanceof Byte byte1) { - methodVisitor.push(byte1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(byte.class, methodVisitor); - } - } else if (value instanceof Short short1) { - methodVisitor.push(short1); - if (boxValue) { - pushBoxPrimitiveIfNecessary(short.class, methodVisitor); - } - } else if (value instanceof Character character) { - methodVisitor.push(character); - if (boxValue) { - pushBoxPrimitiveIfNecessary(char.class, methodVisitor); - } - } else if (value instanceof Number number) { - methodVisitor.push(number.intValue()); - if (boxValue) { - pushBoxPrimitiveIfNecessary(ReflectionUtils.getPrimitiveType(value.getClass()), methodVisitor); - } - } else if (value instanceof io.micronaut.core.annotation.AnnotationValue data) { - String annotationName = data.getAnnotationName(); - Map values = data.getValues(); - Type annotationValueType = Type.getType(io.micronaut.core.annotation.AnnotationValue.class); - methodVisitor.newInstance(annotationValueType); - methodVisitor.dup(); - methodVisitor.push(annotationName); - - pushStringMapOf(methodVisitor, values, true, null, v -> pushValue(declaringType, declaringClassWriter, methodVisitor, v, defaultsStorage, loadTypeMethods, true)); - - methodVisitor.getStatic(Type.getType(AnnotationMetadataSupport.class), "ANNOTATION_DEFAULT_VALUES_PROVIDER", Type.getType(AnnotationDefaultValuesProvider.class)); - methodVisitor.invokeConstructor(annotationValueType, CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP); - } else if (value instanceof EvaluatedExpressionReference expressionReference) { - Type type = Type.getType(getTypeDescriptor(expressionReference.expressionClassName())); - - methodVisitor.visitTypeInsn(NEW, type.getInternalName()); - methodVisitor.visitInsn(DUP); - - Object annotationValue = expressionReference.annotationValue(); - if (annotationValue instanceof String str) { - methodVisitor.push(str); - } else if (annotationValue instanceof String[] strings) { - int len = Array.getLength(strings); - pushNewArray(methodVisitor, String.class, len); - for (int i = 0; i < len; i++) { - final Object v = Array.get(strings, i); - pushStoreInArray(methodVisitor, Type.getType(String.class), i, len, - () -> pushValue(declaringType, declaringClassWriter, methodVisitor, v, - defaultsStorage, loadTypeMethods, false)); - } - } else { - throw new IllegalStateException(); - } - - methodVisitor.invokeConstructor(type, CONSTRUCTOR_CONTEXT_EVALUATED_EXPRESSION); - } else { - throw new IllegalStateException("Unsupported Map value: " + value + " " + value.getClass().getName()); - } - } - - public static boolean isSupportedMapValue(Object value) { - if (value == null) { - return false; - } else if (value instanceof Boolean) { - return true; - } else if (value instanceof String) { - return true; - } else if (value instanceof AnnotationClassValue) { - return true; - } else if (value instanceof Enum) { - return true; - } else if (value.getClass().isArray()) { - return true; - } else if (value instanceof Collection) { - return true; - } else if (value instanceof Map) { - return true; - } else if (value instanceof Long) { - return true; - } else if (value instanceof Double) { - return true; - } else if (value instanceof Float) { - return true; - } else if (value instanceof Byte) { - return true; - } else if (value instanceof Short) { - return true; - } else if (value instanceof Character) { - return true; - } else if (value instanceof Number) { - return true; - } else if (value instanceof io.micronaut.core.annotation.AnnotationValue) { - return true; - } else if (value instanceof EvaluatedExpressionReference) { - return true; - } else if (value instanceof Class) { - // The class should be added as AnnotationClassValue - return false; - } - return false; - } - - private static void pushEmptyObjectsArray(GeneratorAdapter methodVisitor) { - methodVisitor.getStatic(Type.getType(ArrayUtils.class), "EMPTY_OBJECT_ARRAY", Type.getType(Object[].class)); - } - - public static void invokeLoadClassValueMethod( - Type declaringType, - ClassVisitor declaringClassWriter, - GeneratorAdapter methodVisitor, - Map loadTypeMethods, - AnnotationClassValue acv) { - final String typeName = acv.getName(); - final String desc = getMethodDescriptor(AnnotationClassValue.class, Collections.emptyList()); - final GeneratorAdapter loadTypeGeneratorMethod = loadTypeMethods.computeIfAbsent(typeName, type -> { - final String methodName = LOAD_CLASS_PREFIX + loadTypeMethods.size(); - final GeneratorAdapter loadTypeGenerator = new GeneratorAdapter(declaringClassWriter.visitMethod( - ACC_STATIC | ACC_SYNTHETIC, - methodName, - desc, - null, - null - - ), ACC_STATIC | ACC_SYNTHETIC, methodName, desc); - - loadTypeGenerator.visitCode(); - Label tryStart = new Label(); - Label tryEnd = new Label(); - Label exceptionHandler = new Label(); - - // This logic will generate a method such as the following, allowing non-dynamic classloading: - // - // AnnotationClassValue $micronaut_load_class_value_0() { - // try { - // return new AnnotationClassValue(test.MyClass.class); - // } catch(Throwable e) { - // return new AnnotationClassValue("test.MyClass"); - // } - // } - - loadTypeGenerator.visitTryCatchBlock(tryStart, tryEnd, exceptionHandler, Type.getInternalName(Throwable.class)); - loadTypeGenerator.visitLabel(tryStart); - loadTypeGenerator.visitTypeInsn(NEW, TYPE_ANNOTATION_CLASS_VALUE.getInternalName()); - loadTypeGenerator.visitInsn(DUP); - loadTypeGenerator.push(getTypeReferenceForName(typeName)); - loadTypeGenerator.invokeConstructor(TYPE_ANNOTATION_CLASS_VALUE, CONSTRUCTOR_CLASS_VALUE_WITH_CLASS); - loadTypeGenerator.visitLabel(tryEnd); - loadTypeGenerator.returnValue(); - loadTypeGenerator.visitLabel(exceptionHandler); - loadTypeGenerator.visitFrame(F_NEW, 0, new Object[]{}, 1, new Object[]{"java/lang/Throwable"}); - // Try load the class - - // fallback to return a class value that is just a string - loadTypeGenerator.visitVarInsn(ASTORE, 0); - loadTypeGenerator.visitTypeInsn(NEW, TYPE_ANNOTATION_CLASS_VALUE.getInternalName()); - loadTypeGenerator.visitInsn(DUP); - loadTypeGenerator.push(typeName); - loadTypeGenerator.invokeConstructor(TYPE_ANNOTATION_CLASS_VALUE, CONSTRUCTOR_CLASS_VALUE); - loadTypeGenerator.returnValue(); - return loadTypeGenerator; - }); - - methodVisitor.visitMethodInsn(INVOKESTATIC, declaringType.getInternalName(), loadTypeGeneratorMethod.getName(), desc, false); + loadTypeMethods.values().forEach(classDefBuilder::addMethod); + ClassDef classDef = classDefBuilder.build(); + return new ByteCodeWriter().write(classDef); } } diff --git a/core-processor/src/main/java/io/micronaut/inject/ast/ReflectClassElement.java b/core-processor/src/main/java/io/micronaut/inject/ast/ReflectClassElement.java index bb069392771..92ed0bc74ee 100644 --- a/core-processor/src/main/java/io/micronaut/inject/ast/ReflectClassElement.java +++ b/core-processor/src/main/java/io/micronaut/inject/ast/ReflectClassElement.java @@ -40,6 +40,21 @@ class ReflectClassElement extends ReflectTypeElement> { super(type); } + @Override + public boolean isInner() { + return type.isMemberClass(); + } + + @Override + public boolean isInterface() { + return type.isInterface(); + } + + @Override + public boolean isEnum() { + return type.isEnum(); + } + @Override public boolean isArray() { return type.isArray(); diff --git a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/AptClassWriter.java b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/AptClassWriter.java deleted file mode 100644 index d44ae724377..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/AptClassWriter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2017-2023 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.beans.visitor; - -import io.micronaut.inject.ast.ClassElement; -import io.micronaut.inject.visitor.VisitorContext; -import org.objectweb.asm.ClassWriter; - -/** - * ClassWriter implementation that uses the visitor context for {@link #getCommonSuperClass(String, String)}. - */ -final class AptClassWriter extends ClassWriter { - private final VisitorContext visitorContext; - - public AptClassWriter(int flags, VisitorContext visitorContext) { - super(flags); - this.visitorContext = visitorContext; - } - - @Override - protected String getCommonSuperClass(String type1, String type2) { - // this is basically the same as the supermethod, just with Class.forName replaced - - ClassElement cl1 = loadClass(type1); - ClassElement cl2 = loadClass(type1); - if (cl2.isAssignable(cl1)) { - return type1; - } - if (cl1.isAssignable(cl2)) { - return type2; - } - if (cl1.isInterface() || cl2.isInterface()) { - return "java/lang/Object"; - } else { - do { - // type2 should always be assignable to Object, the only type where this can be empty - cl1 = cl1.getSuperType().orElseThrow(); - } while (!cl2.isAssignable(cl1)); - return cl1.getName().replace('.', '/'); - } - } - - private ClassElement loadClass(String binaryName) { - return visitorContext.getClassElement(binaryName.replace('/', '.')) - .orElseThrow(() -> new TypeNotPresentException(binaryName, null)); - } -} diff --git a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java index 6dedd1354a1..f53a65ac625 100644 --- a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java @@ -17,6 +17,7 @@ import io.micronaut.core.annotation.AnnotationClassValue; import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; @@ -27,11 +28,12 @@ import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArrayUtils; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; import io.micronaut.inject.annotation.AnnotationMetadataReference; -import io.micronaut.inject.annotation.AnnotationMetadataWriter; import io.micronaut.inject.annotation.MutableAnnotationMetadata; import io.micronaut.inject.ast.ClassElement; +import io.micronaut.inject.ast.Element; import io.micronaut.inject.ast.ElementQuery; import io.micronaut.inject.ast.EnumConstantElement; import io.micronaut.inject.ast.EnumElement; @@ -44,37 +46,41 @@ import io.micronaut.inject.beans.AbstractEnumBeanIntrospectionAndReference; import io.micronaut.inject.beans.AbstractInitializableBeanIntrospection; import io.micronaut.inject.beans.AbstractInitializableBeanIntrospectionAndReference; -import io.micronaut.inject.processing.JavaModelUtils; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.inject.writer.AbstractClassFileWriter; +import io.micronaut.inject.writer.ArgumentExpUtils; +import io.micronaut.inject.writer.ClassOutputWriter; import io.micronaut.inject.writer.ClassWriterOutputVisitor; import io.micronaut.inject.writer.DispatchWriter; import io.micronaut.inject.writer.EvaluatedExpressionProcessor; -import io.micronaut.inject.writer.StringSwitchWriter; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; - +import io.micronaut.inject.writer.OriginatingElements; +import io.micronaut.inject.writer.MethodGenUtils; +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.AnnotationDef; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; +import io.micronaut.sourcegen.model.VariableDef; + +import javax.lang.model.element.Modifier; import java.io.IOException; import java.io.OutputStream; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static io.micronaut.inject.writer.AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA; -import static io.micronaut.inject.writer.AbstractAnnotationMetadataWriter.initializeAnnotationMetadata; -import static io.micronaut.inject.writer.AbstractAnnotationMetadataWriter.writeAnnotationDefault; -import static io.micronaut.inject.writer.WriterUtils.invokeBeanConstructor; +import java.util.stream.IntStream; /** * A class file writer that writes a {@link BeanIntrospectionReference} and associated @@ -85,7 +91,7 @@ * @since 1.1 */ @Internal -final class BeanIntrospectionWriter extends AbstractClassFileWriter { +final class BeanIntrospectionWriter implements OriginatingElements, ClassOutputWriter { private static final String INTROSPECTION_SUFFIX = "$Introspection"; private static final String FIELD_CONSTRUCTOR_ANNOTATION_METADATA = "$FIELD_CONSTRUCTOR_ANNOTATION_METADATA"; @@ -93,31 +99,127 @@ final class BeanIntrospectionWriter extends AbstractClassFileWriter { private static final String FIELD_BEAN_PROPERTIES_REFERENCES = "$PROPERTIES_REFERENCES"; private static final String FIELD_BEAN_METHODS_REFERENCES = "$METHODS_REFERENCES"; private static final String FIELD_ENUM_CONSTANTS_REFERENCES = "$ENUM_CONSTANTS_REFERENCES"; - private static final Method FIND_PROPERTY_BY_INDEX_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getPropertyByIndex", int.class) + private static final java.lang.reflect.Method FIND_PROPERTY_BY_INDEX_METHOD = + ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getPropertyByIndex", int.class); + + private static final java.lang.reflect.Method FIND_INDEXED_PROPERTY_METHOD = + ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "findIndexedProperty", Class.class, String.class); + + private static final java.lang.reflect.Method GET_INDEXED_PROPERTIES = + ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getIndexedProperties", Class.class); + + private static final java.lang.reflect.Method GET_BP_INDEXED_SUBSET_METHOD = + ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getBeanPropertiesIndexedSubset", int[].class); + + private static final java.lang.reflect.Constructor BEAN_METHOD_REF_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractInitializableBeanIntrospection.BeanMethodRef.class, + Argument.class, + String.class, + AnnotationMetadata.class, + Argument[].class, + int.class ); - private static final Method FIND_INDEXED_PROPERTY_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "findIndexedProperty", Class.class, String.class) + + private static final java.lang.reflect.Constructor ENUM_CONSTANT_DYNAMIC_REF_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class, + AnnotationClassValue.class, + String.class, + AnnotationMetadata.class ); - private static final Method GET_INDEXED_PROPERTIES = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getIndexedProperties", Class.class) + + private static final java.lang.reflect.Constructor INTROSPECTION_SUPER_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractInitializableBeanIntrospectionAndReference.class, + Class.class, + AnnotationMetadata.class, + AnnotationMetadata.class, + Argument[].class, + AbstractInitializableBeanIntrospection.BeanPropertyRef[].class, + AbstractInitializableBeanIntrospection.BeanMethodRef[].class ); - private static final Method GET_BP_INDEXED_SUBSET_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanIntrospection.class, "getBeanPropertiesIndexedSubset", int[].class) + + private static final java.lang.reflect.Constructor ENUM_INTROSPECTION_SUPER_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractEnumBeanIntrospectionAndReference.class, + Class.class, + AnnotationMetadata.class, + AnnotationMetadata.class, + Argument[].class, + AbstractInitializableBeanIntrospection.BeanPropertyRef[].class, + AbstractInitializableBeanIntrospection.BeanMethodRef[].class, + AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef[].class ); - private static final Method COLLECTIONS_EMPTY_LIST = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(Collections.class, "emptyList") + + private static final java.lang.reflect.Constructor BEAN_PROPERTY_REF_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractInitializableBeanIntrospection.BeanPropertyRef.class, + Argument.class, + Argument.class, + Argument.class, + int.class, + int.class, + int.class, + boolean.class, + boolean.class + ); + + private static final java.lang.reflect.Method BEAN_PROPERTY_REF_READ_ONLY_STATIC = ReflectionUtils.getRequiredMethod( + AbstractInitializableBeanIntrospection.BeanPropertyRef.class, + "readOnly", + Argument.class, + int.class, + int.class, + int.class, + boolean.class, + boolean.class + ); + + private static final java.lang.reflect.Method BEAN_PROPERTY_REF_WRITE_ONLY_STATIC = ReflectionUtils.getRequiredMethod( + AbstractInitializableBeanIntrospection.BeanPropertyRef.class, + "writeOnly", + Argument.class, + int.class, + int.class, + int.class, + boolean.class, + boolean.class + ); + + private static final java.lang.reflect.Method BEAN_PROPERTY_REF_READ_WRITE_STATIC = ReflectionUtils.getRequiredMethod( + AbstractInitializableBeanIntrospection.BeanPropertyRef.class, + "readWrite", + Argument.class, + int.class, + int.class, + int.class, + boolean.class, + boolean.class + ); + + private static final java.lang.reflect.Method INSTANTIATE_METHOD = ReflectionUtils.getRequiredMethod( + AbstractInitializableBeanIntrospection.class, + "instantiate" + ); + + private static final java.lang.reflect.Method INSTANTIATE_INTERNAL_METHOD = ReflectionUtils.getRequiredMethod( + AbstractInitializableBeanIntrospection.class, + "instantiateInternal", Object[].class + ); + + private static final java.lang.reflect.Method HAS_BUILDER_METHOD = ReflectionUtils.getRequiredMethod( + BeanIntrospection.class, + "hasBuilder" + ); + + private static final java.lang.reflect.Method IS_BUILDABLE_METHOD = ReflectionUtils.getRequiredMethod( + BeanIntrospection.class, + "isBuildable" ); - private static final String METHOD_IS_BUILDABLE = "isBuildable"; - private final VisitorContext visitorContext; private final String introspectionName; - private final Type introspectionType; - private final Type beanType; + private final ClassTypeDef introspectionTypeDef; private final Map indexByAnnotationAndValue = new HashMap<>(2); private final Map> indexByAnnotations = new HashMap<>(2); - private final Map annotationIndexFields = new HashMap<>(2); - private final ClassElement classElement; + private final Map annotationIndexFields = new HashMap<>(2); + private final ClassTypeDef beanType; + private final ClassElement beanClassElement; private boolean executed = false; private MethodElement constructor; private MethodElement defaultConstructor; @@ -128,59 +230,59 @@ final class BeanIntrospectionWriter extends AbstractClassFileWriter { private final DispatchWriter dispatchWriter; private final EvaluatedExpressionProcessor evaluatedExpressionProcessor; private final AnnotationMetadata annotationMetadata; - private final Map loadTypeMethods = new HashMap<>(); - private final Map defaults = new HashMap<>(); + + private final OriginatingElements originatingElements; + + private CopyConstructorDispatchTarget copyConstructorDispatchTarget; /** * Default constructor. * - * @param classElement The class element + * @param beanClassElement The class element * @param annotationMetadata The bean annotation metadata - * @param visitorContext The visitor context + * @param visitorContext The visitor context */ - BeanIntrospectionWriter(String targetPackage, ClassElement classElement, AnnotationMetadata annotationMetadata, + BeanIntrospectionWriter(String targetPackage, ClassElement beanClassElement, AnnotationMetadata annotationMetadata, VisitorContext visitorContext) { - super(classElement); - this.visitorContext = visitorContext; - final String name = classElement.getName(); - this.classElement = classElement; + final String name = beanClassElement.getName(); + this.beanClassElement = beanClassElement; + this.beanType = ClassTypeDef.of(beanClassElement); this.introspectionName = computeShortIntrospectionName(targetPackage, name); - this.introspectionType = getTypeReferenceForName(introspectionName); - this.beanType = getTypeReferenceForName(name); - this.dispatchWriter = new DispatchWriter(introspectionType, Type.getType(AbstractInitializableBeanIntrospection.class)); + this.introspectionTypeDef = ClassTypeDef.of(introspectionName); + this.dispatchWriter = new DispatchWriter(); this.annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, getOriginatingElement()); + this.originatingElements = OriginatingElements.of(beanClassElement); + evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, beanClassElement); evaluatedExpressionProcessor.processEvaluatedExpressions(annotationMetadata, null); } /** * Constructor used to generate a reference for already compiled classes. * - * @param generatingType The originating type - * @param index A unique index - * @param originatingElement The originating element - * @param classElement The class element + * @param generatingType The originating type + * @param index A unique index + * @param originatingElement The originating element + * @param beanClassElement The class element * @param annotationMetadata The bean annotation metadata - * @param visitorContext The visitor context + * @param visitorContext The visitor context */ BeanIntrospectionWriter( String targetPackage, String generatingType, int index, ClassElement originatingElement, - ClassElement classElement, + ClassElement beanClassElement, AnnotationMetadata annotationMetadata, VisitorContext visitorContext) { - super(originatingElement); - this.visitorContext = visitorContext; - final String className = classElement.getName(); - this.classElement = classElement; + final String className = beanClassElement.getName(); + this.beanClassElement = beanClassElement; + this.beanType = ClassTypeDef.of(beanClassElement); this.introspectionName = computeIntrospectionName(targetPackage, className); - this.introspectionType = getTypeReferenceForName(introspectionName); - this.beanType = getTypeReferenceForName(className); - this.dispatchWriter = new DispatchWriter(introspectionType); + this.introspectionTypeDef = ClassTypeDef.of(introspectionName); + this.dispatchWriter = new DispatchWriter(); this.annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, getOriginatingElement()); + this.originatingElements = OriginatingElements.of(originatingElement); + evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, beanClassElement); evaluatedExpressionProcessor.processEvaluatedExpressions(annotationMetadata, null); } @@ -204,21 +306,21 @@ public MethodElement getConstructor() { * * @return The bean type */ - public Type getBeanType() { + public ClassTypeDef getBeanType() { return beanType; } /** * Visit a property. * - * @param type The property type - * @param genericType The generic type - * @param name The property name - * @param readMember The read method - * @param readType The read type - * @param writeMember The write member - * @param writeType The write type - * @param isReadOnly Is read only + * @param type The property type + * @param genericType The generic type + * @param name The property name + * @param readMember The read method + * @param readType The read type + * @param writeMember The write member + * @param writeType The write type + * @param isReadOnly Is read only */ void visitProperty( @NonNull ClassElement type, @@ -229,11 +331,11 @@ void visitProperty( @Nullable ClassElement readType, @Nullable ClassElement writeType, boolean isReadOnly) { - this.evaluatedExpressionProcessor.processEvaluatedExpressions(genericType.getAnnotationMetadata(), classElement); + this.evaluatedExpressionProcessor.processEvaluatedExpressions(genericType.getAnnotationMetadata(), beanClassElement); int readDispatchIndex = -1; if (readMember != null) { if (readMember instanceof MethodElement element) { - readDispatchIndex = dispatchWriter.addMethod(classElement, element, true); + readDispatchIndex = dispatchWriter.addMethod(beanClassElement, element, true); } else if (readMember instanceof FieldElement element) { readDispatchIndex = dispatchWriter.addGetField(element); } else { @@ -244,7 +346,7 @@ void visitProperty( int withMethodIndex = -1; if (writeMember != null) { if (writeMember instanceof MethodElement element) { - writeDispatchIndex = dispatchWriter.addMethod(classElement, element, true); + writeDispatchIndex = dispatchWriter.addMethod(beanClassElement, element, true); } else if (writeMember instanceof FieldElement element) { writeDispatchIndex = dispatchWriter.addSetField(element); } else { @@ -264,16 +366,20 @@ void visitProperty( String methodName = methodElement.getName(); return methodName.startsWith(prefix) && methodName.equals(prefix + NameUtils.capitalize(name)) && parameters.length == 1 - && methodElement.getGenericReturnType().getName().equals(classElement.getName()) + && methodElement.getGenericReturnType().getName().equals(beanClassElement.getName()) && type.getType().isAssignable(parameters[0].getType()); })); - MethodElement withMethod = classElement.getEnclosedElement(elementQuery).orElse(null); + MethodElement withMethod = beanClassElement.getEnclosedElement(elementQuery).orElse(null); if (withMethod != null) { - withMethodIndex = dispatchWriter.addMethod(classElement, withMethod, true); + withMethodIndex = dispatchWriter.addMethod(beanClassElement, withMethod, true); } else { MethodElement constructor = this.constructor == null ? defaultConstructor : this.constructor; if (constructor != null) { - withMethodIndex = dispatchWriter.addDispatchTarget(new CopyConstructorDispatchTarget(constructor, name)); + if (copyConstructorDispatchTarget == null) { + copyConstructorDispatchTarget = new CopyConstructorDispatchTarget(beanType, beanProperties, dispatchWriter, constructor); + } + copyConstructorDispatchTarget.propertyNames.put(name, dispatchWriter.getDispatchTargets().size()); + withMethodIndex = dispatchWriter.addDispatchTarget(copyConstructorDispatchTarget); } } } @@ -281,7 +387,7 @@ void visitProperty( } else { withMethodIndex = dispatchWriter.addDispatchTarget(new ExceptionDispatchTarget( UnsupportedOperationException.class, - "Cannot mutate property [" + name + "] that is not mutable via a setter method, field or constructor argument for type: " + beanType.getClassName() + "Cannot mutate property [" + name + "] that is not mutable via a setter method, field or constructor argument for type: " + beanType.getName() )); } @@ -304,11 +410,11 @@ void visitProperty( */ public void visitBeanMethod(MethodElement element) { if (element != null && !element.isPrivate()) { - int dispatchIndex = dispatchWriter.addMethod(classElement, element); + int dispatchIndex = dispatchWriter.addMethod(beanClassElement, element); beanMethods.add(new BeanMethodData(element, dispatchIndex)); - this.evaluatedExpressionProcessor.processEvaluatedExpressions(element.getAnnotationMetadata(), classElement); + this.evaluatedExpressionProcessor.processEvaluatedExpressions(element.getAnnotationMetadata(), beanClassElement); for (ParameterElement parameter : element.getParameters()) { - this.evaluatedExpressionProcessor.processEvaluatedExpressions(parameter.getAnnotationMetadata(), classElement); + this.evaluatedExpressionProcessor.processEvaluatedExpressions(parameter.getAnnotationMetadata(), beanClassElement); } } } @@ -335,272 +441,149 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE // First write the introspection for the annotation metadata can be populated with defaults that reference will contain writeIntrospectionClass(classWriterOutputVisitor); this.evaluatedExpressionProcessor.writeEvaluatedExpressions(classWriterOutputVisitor); - - loadTypeMethods.clear(); } } - private void buildStaticInit(ClassWriter classWriter, boolean isEnum) { - GeneratorAdapter staticInit = visitStaticInitializer(classWriter); - Map defaults = new HashMap<>(); + private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyData, Map loadTypeMethods) { + ClassTypeDef beanPropertyRefDef = ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class); - if (constructor != null) { - if (!constructor.getAnnotationMetadata().isEmpty()) { - Type am = Type.getType(AnnotationMetadata.class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_CONSTRUCTOR_ANNOTATION_METADATA, am.getDescriptor(), null, null); - pushAnnotationMetadata(classWriter, staticInit, constructor.getAnnotationMetadata()); - staticInit.putStatic(introspectionType, FIELD_CONSTRUCTOR_ANNOTATION_METADATA, am); - } - if (ArrayUtils.isNotEmpty(constructor.getParameters())) { - Type args = Type.getType(Argument[].class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_CONSTRUCTOR_ARGUMENTS, args.getDescriptor(), null, null); - pushBuildArgumentsForMethod( - annotationMetadata, - introspectionType.getClassName(), - introspectionType, - classWriter, - staticInit, - Arrays.asList(constructor.getParameters()), - defaults, - loadTypeMethods - ); - staticInit.putStatic(introspectionType, FIELD_CONSTRUCTOR_ARGUMENTS, args); - } - } - if (!beanProperties.isEmpty()) { - Type beanPropertiesRefs = Type.getType(AbstractInitializableBeanIntrospection.BeanPropertyRef[].class); + boolean mutable = !beanPropertyData.isReadOnly || hasAssociatedConstructorArgument(beanPropertyData.name, beanPropertyData.type); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_BEAN_PROPERTIES_REFERENCES, beanPropertiesRefs.getDescriptor(), null, null); + if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.type.equals(beanPropertyData.writeType)) { + return beanPropertyRefDef.invokeStatic( + BEAN_PROPERTY_REF_READ_WRITE_STATIC, - pushNewArray(staticInit, AbstractInitializableBeanIntrospection.BeanPropertyRef.class, beanProperties, beanPropertyData -> { - pushBeanPropertyReference( - classWriter, - staticInit, - beanPropertyData - ); - }); - staticInit.putStatic(introspectionType, FIELD_BEAN_PROPERTIES_REFERENCES, beanPropertiesRefs); - } - if (!beanMethods.isEmpty()) { - Type beanMethodsRefs = Type.getType(AbstractInitializableBeanIntrospection.BeanMethodRef[].class); - - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_BEAN_METHODS_REFERENCES, beanMethodsRefs.getDescriptor(), null, null); - pushNewArray(staticInit, AbstractInitializableBeanIntrospection.BeanMethodRef.class, beanMethods, beanMethodData -> { - pushBeanMethodReference( - classWriter, - staticInit, - beanMethodData - ); - }); - staticInit.putStatic(introspectionType, FIELD_BEAN_METHODS_REFERENCES, beanMethodsRefs); - } - if (isEnum) { - Type type = Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef[].class); - classWriter.visitField( - ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_ENUM_CONSTANTS_REFERENCES, - type.getDescriptor(), - null, - null + ArgumentExpUtils.pushCreateArgument( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + beanPropertyData.name, + beanPropertyData.type, + loadTypeMethods + ), + ExpressionDef.constant(beanPropertyData.getDispatchIndex), + ExpressionDef.constant(beanPropertyData.setDispatchIndex), + ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), + ExpressionDef.constant(beanPropertyData.isReadOnly), + ExpressionDef.constant(mutable) ); - pushNewArray(staticInit, AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class, ((EnumElement) classElement).elements(), enumConstantElement -> { - pushEnumConstantReference( - classWriter, - staticInit, - enumConstantElement - ); - }); - staticInit.putStatic(introspectionType, FIELD_ENUM_CONSTANTS_REFERENCES, type); } + if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.writeType == null) { + return beanPropertyRefDef.invokeStatic( + BEAN_PROPERTY_REF_READ_ONLY_STATIC, - int indexesIndex = 0; - for (String annotationName : indexByAnnotations.keySet()) { - int[] indexes = indexByAnnotations.get(annotationName) - .stream() - .mapToInt(this::getPropertyIndex) - .toArray(); - - String newIndexField = "INDEX_" + (++indexesIndex); - Type type = Type.getType(int[].class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, newIndexField, type.getDescriptor(), null, null); - pushNewArray(staticInit, int.class, indexes.length); - int i = 0; - for (int index : indexes) { - pushStoreInArray(staticInit, Type.INT_TYPE, i++, indexes.length, () -> staticInit.push(index)); - } - staticInit.putStatic(introspectionType, newIndexField, type); - annotationIndexFields.put(annotationName, newIndexField); - } - - writeAnnotationDefault(classWriter, staticInit, introspectionType, annotationMetadata, defaults, loadTypeMethods); - initializeAnnotationMetadata(staticInit, classWriter, introspectionType, annotationMetadata, defaults, loadTypeMethods); - - staticInit.returnValue(); - staticInit.visitMaxs(DEFAULT_MAX_STACK, 1); - staticInit.visitEnd(); - } - - private void pushBeanPropertyReference(ClassWriter classWriter, - GeneratorAdapter staticInit, - BeanPropertyData beanPropertyData) { - - Runnable pushTypeArgument = () -> pushCreateArgument( - annotationMetadata, - classElement.getName(), - introspectionType, - classWriter, - staticInit, - beanPropertyData.name, - beanPropertyData.type, - defaults, - loadTypeMethods - ); - - int typeLocal = -1; - int readTypeLocal = -1; - int writeTypeLocal = -1; - - Type argumentType = Type.getType(Argument.class); - if (beanPropertyData.type.equals(beanPropertyData.readType)) { - typeLocal = staticInit.newLocal(argumentType); - pushTypeArgument.run(); - staticInit.storeLocal(typeLocal, argumentType); - readTypeLocal = typeLocal; - } - if (beanPropertyData.type.equals(beanPropertyData.writeType)) { - if (typeLocal == -1) { - typeLocal = staticInit.newLocal(argumentType); - pushTypeArgument.run(); - staticInit.storeLocal(typeLocal, argumentType); - } - writeTypeLocal = typeLocal; + ArgumentExpUtils.pushCreateArgument( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + beanPropertyData.name, + beanPropertyData.type, + loadTypeMethods + ), + ExpressionDef.constant(beanPropertyData.getDispatchIndex), + ExpressionDef.constant(beanPropertyData.setDispatchIndex), + ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), + ExpressionDef.constant(beanPropertyData.isReadOnly), + ExpressionDef.constant(mutable) + ); } + if (beanPropertyData.type.equals(beanPropertyData.writeType) && beanPropertyData.readType == null) { + return beanPropertyRefDef.invokeStatic( + BEAN_PROPERTY_REF_WRITE_ONLY_STATIC, - staticInit.newInstance(Type.getType(AbstractInitializableBeanIntrospection.BeanPropertyRef.class)); - staticInit.dup(); - - if (typeLocal != -1) { - staticInit.loadLocal(typeLocal, argumentType); - } else { - pushTypeArgument.run(); + ArgumentExpUtils.pushCreateArgument( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + beanPropertyData.name, + beanPropertyData.type, + loadTypeMethods + ), + ExpressionDef.constant(beanPropertyData.getDispatchIndex), + ExpressionDef.constant(beanPropertyData.setDispatchIndex), + ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), + ExpressionDef.constant(beanPropertyData.isReadOnly), + ExpressionDef.constant(mutable) + ); } + return beanPropertyRefDef.instantiate( + BEAN_PROPERTY_REF_CONSTRUCTOR, - if (beanPropertyData.readType == null) { - staticInit.push((String) null); - } else if (readTypeLocal != -1) { - staticInit.loadLocal(readTypeLocal, argumentType); - } else { - pushCreateArgument( + ArgumentExpUtils.pushCreateArgument( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + beanPropertyData.name, + beanPropertyData.type, + loadTypeMethods + ), + beanPropertyData.readType == null ? ExpressionDef.nullValue() : ArgumentExpUtils.pushCreateArgument( annotationMetadata, - classElement.getName(), - introspectionType, - classWriter, - staticInit, + beanClassElement, + introspectionTypeDef, beanPropertyData.name, beanPropertyData.readType, - defaults, loadTypeMethods - ); - } - - if (beanPropertyData.writeType == null) { - staticInit.push((String) null); - } else if (writeTypeLocal != -1) { - staticInit.loadLocal(writeTypeLocal, argumentType); - } else { - pushCreateArgument( + ), + beanPropertyData.writeType == null ? ExpressionDef.nullValue() : ArgumentExpUtils.pushCreateArgument( annotationMetadata, - classElement.getName(), - introspectionType, - classWriter, - staticInit, + beanClassElement, + introspectionTypeDef, beanPropertyData.name, beanPropertyData.writeType, - defaults, loadTypeMethods - ); - } - staticInit.push(beanPropertyData.getDispatchIndex); - staticInit.push(beanPropertyData.setDispatchIndex); - staticInit.push(beanPropertyData.withMethodDispatchIndex); - staticInit.push(beanPropertyData.isReadOnly); - staticInit.push(!beanPropertyData.isReadOnly || hasAssociatedConstructorArgument(beanPropertyData.name, beanPropertyData.type)); - - invokeConstructor( - staticInit, - AbstractInitializableBeanIntrospection.BeanPropertyRef.class, - Argument.class, - Argument.class, - Argument.class, - int.class, - int.class, - int.class, - boolean.class, - boolean.class); + ), + ExpressionDef.constant(beanPropertyData.getDispatchIndex), + ExpressionDef.constant(beanPropertyData.setDispatchIndex), + ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), + ExpressionDef.constant(beanPropertyData.isReadOnly), + ExpressionDef.constant(mutable) + ); } - private void pushBeanMethodReference(ClassWriter classWriter, - GeneratorAdapter staticInit, - BeanMethodData beanMethodData) { - staticInit.newInstance(Type.getType(AbstractInitializableBeanIntrospection.BeanMethodRef.class)); - staticInit.dup(); - // 1: return argument - ClassElement genericReturnType = beanMethodData.methodElement.getGenericReturnType(); - pushReturnTypeArgument(annotationMetadata, introspectionType, classWriter, staticInit, classElement.getName(), genericReturnType, defaults, loadTypeMethods); - // 2: name - staticInit.push(beanMethodData.methodElement.getName()); - // 3: annotation metadata - pushAnnotationMetadata(classWriter, staticInit, beanMethodData.methodElement.getAnnotationMetadata()); - // 4: arguments - if (beanMethodData.methodElement.getParameters().length == 0) { - staticInit.push((String) null); - } else { - pushBuildArgumentsForMethod( - annotationMetadata, - beanType.getClassName(), - introspectionType, - classWriter, - staticInit, - Arrays.asList(beanMethodData.methodElement.getParameters()), - new HashMap<>(), - loadTypeMethods + private ExpressionDef newBeanMethodRef(BeanMethodData beanMethodData, Map loadTypeMethods) { + return ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanMethodRef.class) + .instantiate( + BEAN_METHOD_REF_CONSTRUCTOR, + + // 1: return argument + ArgumentExpUtils.pushReturnTypeArgument( + annotationMetadata, + introspectionTypeDef, + beanMethodData.methodElement.getOwningType(), + beanMethodData.methodElement.getGenericReturnType(), + loadTypeMethods), + // 2: name + ExpressionDef.constant(beanMethodData.methodElement.getName()), + // 3: annotation metadata + getAnnotationMetadataExpression(beanMethodData.methodElement.getAnnotationMetadata(), loadTypeMethods), + // 4: arguments + beanMethodData.methodElement.getParameters().length == 0 ? ExpressionDef.nullValue() : ArgumentExpUtils.pushBuildArgumentsForMethod( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + Arrays.asList(beanMethodData.methodElement.getParameters()), + loadTypeMethods + ), + // 5: method index + ExpressionDef.constant(beanMethodData.dispatchIndex) ); - } - // 5: method index - staticInit.push(beanMethodData.dispatchIndex); - - invokeConstructor( - staticInit, - AbstractInitializableBeanIntrospection.BeanMethodRef.class, - Argument.class, - String.class, - AnnotationMetadata.class, - Argument[].class, - int.class); } - private void pushEnumConstantReference(ClassWriter classWriter, - GeneratorAdapter staticInit, - EnumConstantElement enumConstantElement) { - staticInit.newInstance(Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class)); - staticInit.dup(); - // 1: push annotation class value - AnnotationMetadataWriter.invokeLoadClassValueMethod(introspectionType, classWriter, staticInit, loadTypeMethods, new AnnotationClassValue<>(enumConstantElement.getOwningType().getName())); - // 2: push enum name - staticInit.push(enumConstantElement.getName()); - // 3: annotation metadata - AnnotationMetadata annotationMetadata = enumConstantElement.getAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - staticInit.getStatic(Type.getType(AnnotationMetadata.class), "EMPTY_METADATA", Type.getType(AnnotationMetadata.class)); - } else { - pushAnnotationMetadata(classWriter, staticInit, annotationMetadata); - } - - invokeConstructor( - staticInit, - AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class, - AnnotationClassValue.class, - String.class, - AnnotationMetadata.class + private ExpressionDef newEnumConstantRef(EnumConstantElement enumConstantElement, Map loadTypeMethods) { + return ClassTypeDef.of( + AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class + ).instantiate( + ENUM_CONSTANT_DYNAMIC_REF_CONSTRUCTOR, + + // 1: push annotation class value + AnnotationMetadataGenUtils.invokeLoadClassValueMethod(introspectionTypeDef, loadTypeMethods, new AnnotationClassValue<>(enumConstantElement.getOwningType().getName())), + // 2: push enum name + ExpressionDef.constant(enumConstantElement.getName()), + // 3: annotation metadata + enumConstantElement.getAnnotationMetadata() == null ? ( + ClassTypeDef.of(AnnotationMetadata.class).getStaticField("EMPTY_METADATA", TypeDef.of(AnnotationMetadata.class)) + ) : getAnnotationMetadataExpression(enumConstantElement.getAnnotationMetadata(), loadTypeMethods) ); } @@ -617,308 +600,338 @@ private boolean hasAssociatedConstructorArgument(String name, TypedElement typed } private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException { - boolean isEnum = classElement.isEnum(); - final Type superType = isEnum ? Type.getType(AbstractEnumBeanIntrospectionAndReference.class) : Type.getType(AbstractInitializableBeanIntrospectionAndReference.class); - - ClassWriter classWriter = new AptClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES, visitorContext); - classWriter.visit( - V17, - ACC_SYNTHETIC | ACC_FINAL | ACC_PUBLIC, - introspectionType.getInternalName(), - null, - superType.getInternalName(), - null - ); - - classWriterOutputVisitor.visitServiceDescriptor(BeanIntrospectionReference.class, introspectionName, getOriginatingElement()); + boolean isEnum = beanClassElement.isEnum(); - annotateAsGeneratedAndService(classWriter, introspectionName); - // init expressions at build time - evaluatedExpressionProcessor.registerExpressionForBuildTimeInit(classWriter); + Map loadTypeMethods = new LinkedHashMap<>(); - buildStaticInit(classWriter, isEnum); + ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(introspectionName).addModifiers(Modifier.FINAL, Modifier.PUBLIC); + classDefBuilder.superclass(isEnum ? ClassTypeDef.of(AbstractEnumBeanIntrospectionAndReference.class) : ClassTypeDef.of(AbstractInitializableBeanIntrospectionAndReference.class)); - final GeneratorAdapter constructorWriter = startConstructor(classWriter); + classWriterOutputVisitor.visitServiceDescriptor(BeanIntrospectionReference.class, introspectionName, beanClassElement); - // writer the constructor - constructorWriter.loadThis(); - // 1st argument: The bean type - constructorWriter.push(beanType); + classDefBuilder.addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", introspectionName).build()); + // init expressions at build time + evaluatedExpressionProcessor.registerExpressionForBuildTimeInit(classDefBuilder); - // 2nd argument: The annotation metadata - if (annotationMetadata == null || annotationMetadata.isEmpty()) { - constructorWriter.visitInsn(ACONST_NULL); - } else { - constructorWriter.getStatic(introspectionType, FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } + FieldDef constructorAnnotationMetadataField; + FieldDef constructorArgumentsField; + FieldDef beanPropertiesField; + FieldDef beanMethodsField; + FieldDef enumsField; if (constructor != null) { - // 3rd argument: constructor metadata if (!constructor.getAnnotationMetadata().isEmpty()) { - constructorWriter.getStatic(introspectionType, FIELD_CONSTRUCTOR_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); + constructorAnnotationMetadataField = FieldDef.builder(FIELD_CONSTRUCTOR_ANNOTATION_METADATA, AnnotationMetadata.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer(getAnnotationMetadataExpression(constructor.getAnnotationMetadata(), loadTypeMethods)) + .build(); + classDefBuilder.addField( + constructorAnnotationMetadataField + ); } else { - constructorWriter.push((String) null); + constructorAnnotationMetadataField = null; } - // 4th argument: constructor arguments if (ArrayUtils.isNotEmpty(constructor.getParameters())) { - constructorWriter.getStatic(introspectionType, FIELD_CONSTRUCTOR_ARGUMENTS, Type.getType(Argument[].class)); + constructorArgumentsField = FieldDef.builder(FIELD_CONSTRUCTOR_ARGUMENTS, Argument[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer( + ArgumentExpUtils.pushBuildArgumentsForMethod( + annotationMetadata, + constructor.getOwningType(), + introspectionTypeDef, + Arrays.asList(constructor.getParameters()), + loadTypeMethods + ) + ) + .build(); + classDefBuilder.addField( + constructorArgumentsField + ); } else { - constructorWriter.push((String) null); + constructorArgumentsField = null; } } else { - constructorWriter.push((String) null); - constructorWriter.push((String) null); + constructorArgumentsField = null; + constructorAnnotationMetadataField = null; } - - if (beanProperties.isEmpty()) { - constructorWriter.push((String) null); + if (!beanProperties.isEmpty()) { + beanPropertiesField = FieldDef.builder(FIELD_BEAN_PROPERTIES_REFERENCES, AbstractInitializableBeanIntrospection.BeanPropertyRef[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer( + ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class).array() + .instantiate( + beanProperties.stream() + .map(e -> pushBeanPropertyReference(e, loadTypeMethods)) + .toList() + ) + ) + .build(); + classDefBuilder.addField(beanPropertiesField); } else { - constructorWriter.getStatic(introspectionType, - FIELD_BEAN_PROPERTIES_REFERENCES, - Type.getType(AbstractInitializableBeanIntrospection.BeanPropertyRef[].class)); + beanPropertiesField = null; } - if (beanMethods.isEmpty()) { - constructorWriter.push((String) null); + if (!beanMethods.isEmpty()) { + beanMethodsField = FieldDef.builder(FIELD_BEAN_METHODS_REFERENCES, AbstractInitializableBeanIntrospection.BeanMethodRef[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer( + ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanMethodRef.class).array() + .instantiate( + beanMethods.stream() + .map(e -> newBeanMethodRef(e, loadTypeMethods)) + .toList() + ) + ) + .build(); + classDefBuilder.addField(beanMethodsField); } else { - constructorWriter.getStatic(introspectionType, - FIELD_BEAN_METHODS_REFERENCES, - Type.getType(AbstractInitializableBeanIntrospection.BeanMethodRef[].class)); + beanMethodsField = null; } if (isEnum) { - constructorWriter.getStatic(introspectionType, - FIELD_ENUM_CONSTANTS_REFERENCES, - Type.getType(AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef[].class) - ); - invokeConstructor( - constructorWriter, - AbstractEnumBeanIntrospectionAndReference.class, - Class.class, - AnnotationMetadata.class, - AnnotationMetadata.class, - Argument[].class, - AbstractInitializableBeanIntrospection.BeanPropertyRef[].class, - AbstractInitializableBeanIntrospection.BeanMethodRef[].class, - AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef[].class - ); + enumsField = FieldDef.builder(FIELD_ENUM_CONSTANTS_REFERENCES, AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer( + ClassTypeDef.of(AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class).array() + .instantiate( + ((EnumElement) beanClassElement).elements().stream() + .map(e -> newEnumConstantRef(e, loadTypeMethods)) + .toList() + ) + ) + .build(); + classDefBuilder.addField(enumsField); } else { - invokeConstructor( - constructorWriter, - AbstractInitializableBeanIntrospectionAndReference.class, - Class.class, - AnnotationMetadata.class, - AnnotationMetadata.class, - Argument[].class, - AbstractInitializableBeanIntrospection.BeanPropertyRef[].class, - AbstractInitializableBeanIntrospection.BeanMethodRef[].class + enumsField = null; + } + + int indexesIndex = 0; + for (String annotationName : indexByAnnotations.keySet()) { + int[] indexes = indexByAnnotations.get(annotationName) + .stream() + .mapToInt(this::getPropertyIndex) + .toArray(); + + FieldDef field = FieldDef.builder("INDEX_" + (++indexesIndex), int[].class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .initializer( + TypeDef.Primitive.INT.array() + .instantiate( + Arrays.stream(indexes).mapToObj(TypeDef.Primitive.INT::constant).toList() + ) + ) + .build(); + classDefBuilder.addField( + field + ); + + annotationIndexFields.put(annotationName, field); + } + + List statements = new ArrayList<>(); + AnnotationMetadataGenUtils.writeAnnotationDefault(statements, introspectionTypeDef, annotationMetadata, loadTypeMethods); + + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(introspectionTypeDef, annotationMetadata, loadTypeMethods); + if (annotationMetadataField != null) { + classDefBuilder.addField( + annotationMetadataField ); } - constructorWriter.returnValue(); - constructorWriter.visitMaxs(2, 1); - constructorWriter.visitEnd(); + if (!statements.isEmpty()) { + classDefBuilder.addStaticInitializer(StatementDef.multi(statements)); + } + + classDefBuilder.addMethod( + MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) -> { + List values = new ArrayList<>(); + // 1st argument: The bean type + values.add(ExpressionDef.constant(beanType)); + // 2nd argument: The annotation metadata + if (annotationMetadataField == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(introspectionTypeDef.getStaticField(annotationMetadataField)); + } + // 3rd argument: constructor metadata + values.add(constructorAnnotationMetadataField != null ? introspectionTypeDef.getStaticField(constructorAnnotationMetadataField) : ExpressionDef.nullValue()); + // 4th argument: constructor arguments + values.add(constructorArgumentsField != null ? introspectionTypeDef.getStaticField(constructorArgumentsField) : ExpressionDef.nullValue()); + + values.add(beanPropertiesField == null ? ExpressionDef.nullValue() : introspectionTypeDef.getStaticField(beanPropertiesField)); + values.add(beanMethodsField == null ? ExpressionDef.nullValue() : introspectionTypeDef.getStaticField(beanMethodsField)); + + if (enumsField != null) { + values.add(introspectionTypeDef.getStaticField(enumsField)); + return aThis.superRef().invokeConstructor(ENUM_INTROSPECTION_SUPER_CONSTRUCTOR, values); + } else { + return aThis.superRef().invokeConstructor(INTROSPECTION_SUPER_CONSTRUCTOR, values); + } + }) + ); + + MethodDef dispatchOneMethod = dispatchWriter.buildDispatchOneMethod(); + if (dispatchOneMethod != null) { + classDefBuilder.addMethod(dispatchOneMethod); + } + MethodDef dispatchMethod = dispatchWriter.buildDispatchMethod(); + if (dispatchMethod != null) { + classDefBuilder.addMethod(dispatchMethod); + } + MethodDef buildGetTargetMethodByIndex = dispatchWriter.buildGetTargetMethodByIndex(); + if (buildGetTargetMethodByIndex != null) { + classDefBuilder.addMethod(buildGetTargetMethodByIndex); + } + + MethodDef findIndexedProperty = getFindIndexedProperty(); + if (findIndexedProperty != null) { + classDefBuilder.addMethod(findIndexedProperty); + } + MethodDef getIndexedProperties = getGetIndexedProperties(); + if (getIndexedProperties != null) { + classDefBuilder.addMethod(getIndexedProperties); + } - dispatchWriter.buildDispatchOneMethod(classWriter); - dispatchWriter.buildDispatchMethod(classWriter); - dispatchWriter.buildGetTargetMethodByIndex(classWriter); - buildFindIndexedProperty(classWriter); - buildGetIndexedProperties(classWriter); boolean hasBuilder = annotationMetadata != null && - (annotationMetadata.isPresent(Introspected.class, "builder") || annotationMetadata.hasDeclaredAnnotation("lombok.Builder")); - if (defaultConstructor != null) { - writeInstantiateMethod(classWriter, defaultConstructor, "instantiate"); + (annotationMetadata.isPresent(Introspected.class, "builder") || annotationMetadata.hasDeclaredAnnotation("lombok.Builder")); if (defaultConstructor != null) { + classDefBuilder.addMethod( + getInstantiateMethod(defaultConstructor, INSTANTIATE_METHOD) + ); // in case invoked directly or via instantiateUnsafe if (constructor == null) { - writeInstantiateMethod(classWriter, defaultConstructor, "instantiateInternal", Object[].class); - writeBooleanMethod(classWriter, METHOD_IS_BUILDABLE, true); + classDefBuilder.addMethod( + getInstantiateMethod(defaultConstructor, INSTANTIATE_INTERNAL_METHOD) + ); + classDefBuilder.addMethod( + getBooleanMethod(IS_BUILDABLE_METHOD, true) + ); } } if (constructor != null) { if (defaultConstructor == null) { if (ArrayUtils.isEmpty(constructor.getParameters())) { - writeInstantiateMethod(classWriter, constructor, "instantiate"); + classDefBuilder.addMethod( + getInstantiateMethod(constructor, INSTANTIATE_METHOD) + ); } else { - List constructorArguments = Arrays.asList(constructor.getParameters()); - boolean kotlinAllDefault = constructorArguments.stream().allMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); + boolean kotlinAllDefault = Arrays.stream(constructor.getParameters()) + .allMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); if (kotlinAllDefault) { - writeInstantiateMethod(classWriter, constructor, "instantiate"); + classDefBuilder.addMethod( + getInstantiateMethod(constructor, INSTANTIATE_METHOD) + ); } } } - writeInstantiateMethod(classWriter, constructor, "instantiateInternal", Object[].class); - writeBooleanMethod(classWriter, METHOD_IS_BUILDABLE, true); + classDefBuilder.addMethod( + getInstantiateMethod(constructor, INSTANTIATE_INTERNAL_METHOD) + ); + classDefBuilder.addMethod( + getBooleanMethod(IS_BUILDABLE_METHOD, true) + ); } else if (defaultConstructor == null) { - writeBooleanMethod(classWriter, METHOD_IS_BUILDABLE, hasBuilder); - } - - writeBooleanMethod(classWriter, "hasBuilder", hasBuilder); - - for (GeneratorAdapter method : loadTypeMethods.values()) { - method.visitMaxs(3, 1); - method.visitEnd(); + classDefBuilder.addMethod( + getBooleanMethod(IS_BUILDABLE_METHOD, hasBuilder) + ); } + classDefBuilder.addMethod( + getBooleanMethod(HAS_BUILDER_METHOD, hasBuilder) + ); - classWriter.visitEnd(); + loadTypeMethods.values().forEach(classDefBuilder::addMethod); try (OutputStream outputStream = classWriterOutputVisitor.visitClass(introspectionName, getOriginatingElements())) { - outputStream.write(classWriter.toByteArray()); + outputStream.write(new ByteCodeWriter(false, true).write(classDefBuilder.build())); } } - private void writeBooleanMethod(ClassWriter classWriter, String methodName, boolean state) { - GeneratorAdapter booleanMethod = startPublicMethodZeroArgs(classWriter, boolean.class, methodName); - booleanMethod.push(state); - booleanMethod.returnValue(); - booleanMethod.visitMaxs(2, 1); - booleanMethod.endMethod(); + private MethodDef getBooleanMethod(Method method, boolean state) { + return MethodDef.override(method) + .build((aThis, methodParameters) -> ExpressionDef.constant(state).returning()); } - private void buildFindIndexedProperty(ClassWriter classWriter) { + @Nullable + private MethodDef getFindIndexedProperty() { if (indexByAnnotationAndValue.isEmpty()) { - return; + return null; } - GeneratorAdapter writer = new GeneratorAdapter(classWriter.visitMethod( - ACC_PROTECTED | ACC_FINAL, - FIND_INDEXED_PROPERTY_METHOD.getName(), - FIND_INDEXED_PROPERTY_METHOD.getDescriptor(), - null, - null), - ACC_PROTECTED | ACC_FINAL, - FIND_INDEXED_PROPERTY_METHOD.getName(), - FIND_INDEXED_PROPERTY_METHOD.getDescriptor() - ); - writer.loadThis(); - writer.loadArg(0); - writer.invokeVirtual(Type.getType(Class.class), new Method("getName", Type.getType(String.class), new Type[]{})); - int classNameLocal = writer.newLocal(Type.getType(String.class)); - writer.storeLocal(classNameLocal); - writer.loadLocal(classNameLocal); - - new StringSwitchWriter() { - - @Override - protected Set getKeys() { - return indexByAnnotationAndValue.keySet() - .stream() - .map(s -> s.annotationName) - .collect(Collectors.toSet()); - } - - @Override - protected void pushStringValue() { - writer.loadLocal(classNameLocal); - } - - @Override - protected void onMatch(String annotationName, Label end) { - if (indexByAnnotationAndValue.keySet().stream().anyMatch(s -> s.annotationName.equals(annotationName) && s.value == null)) { - Label falseLabel = new Label(); - writer.loadArg(1); - writer.ifNonNull(falseLabel); + TypeDef returnType = TypeDef.of(FIND_INDEXED_PROPERTY_METHOD.getReturnType()); + Set keys = indexByAnnotationAndValue.keySet() + .stream() + .map(s -> s.annotationName) + .collect(Collectors.toSet()); + return MethodDef.builder(FIND_INDEXED_PROPERTY_METHOD.getName()) + .addParameters(FIND_INDEXED_PROPERTY_METHOD.getParameterTypes()) + .addModifiers(Modifier.PUBLIC) + .returns(returnType) + .build((aThis, methodParameters) -> + methodParameters.get(0).invoke("getName", TypeDef.STRING) + .asStatementSwitch(returnType, keys.stream() + .collect(Collectors.toMap( + ExpressionDef::constant, + annotationName -> onMatch(aThis, methodParameters, annotationName, returnType) + )), ExpressionDef.nullValue().returning())); + } - String propertyName = indexByAnnotationAndValue.get(new AnnotationWithValue(annotationName, null)); + private StatementDef onMatch(VariableDef.This aThis, List parameters, String annotationName, TypeDef returnType) { + List statements = new ArrayList<>(); + VariableDef.MethodParameter annotationValueParameter = parameters.get(1); + if (indexByAnnotationAndValue.keySet().stream().anyMatch(s -> s.annotationName.equals(annotationName) && s.value == null)) { + String propertyName = indexByAnnotationAndValue.get(new AnnotationWithValue(annotationName, null)); + int propertyIndex = getPropertyIndex(propertyName); + statements.add( + annotationValueParameter.ifNonNull( + aThis.invoke(FIND_PROPERTY_BY_INDEX_METHOD, ExpressionDef.constant(propertyIndex)).returning() + ) + ); + } else { + statements.add( + annotationValueParameter.ifNull( + ExpressionDef.nullValue().returning() + ) + ); + } + Set valueMatches = indexByAnnotationAndValue.keySet() + .stream() + .filter(s -> s.annotationName.equals(annotationName) && s.value != null) + .map(s -> s.value) + .collect(Collectors.toSet()); + if (!valueMatches.isEmpty()) { + statements.add(annotationValueParameter.asExpressionSwitch(returnType, valueMatches.stream() + .collect(Collectors.toMap(ExpressionDef::constant, e -> { + String propertyName = indexByAnnotationAndValue.get(new AnnotationWithValue(annotationName, e)); int propertyIndex = getPropertyIndex(propertyName); - writer.loadThis(); - writer.push(propertyIndex); - writer.invokeVirtual(introspectionType, FIND_PROPERTY_BY_INDEX_METHOD); - writer.returnValue(); - - writer.visitLabel(falseLabel); - } else { - Label falseLabel = new Label(); - writer.loadArg(1); - writer.ifNonNull(falseLabel); - writer.goTo(end); - writer.visitLabel(falseLabel); - } - Set valueMatches = indexByAnnotationAndValue.keySet() - .stream() - .filter(s -> s.annotationName.equals(annotationName) && s.value != null) - .map(s -> s.value) - .collect(Collectors.toSet()); - if (!valueMatches.isEmpty()) { - new StringSwitchWriter() { - - @Override - protected Set getKeys() { - return valueMatches; - } - - @Override - protected void pushStringValue() { - writer.loadArg(1); - } - - @Override - protected void onMatch(String value, Label end) { - String propertyName = indexByAnnotationAndValue.get(new AnnotationWithValue(annotationName, value)); - int propertyIndex = getPropertyIndex(propertyName); - writer.loadThis(); - writer.push(propertyIndex); - writer.invokeVirtual(introspectionType, FIND_PROPERTY_BY_INDEX_METHOD); - writer.returnValue(); - } - - }.write(writer); - } - writer.goTo(end); - } - - }.write(writer); - - writer.push((String) null); - writer.returnValue(); - writer.visitMaxs(DEFAULT_MAX_STACK, 1); - writer.visitEnd(); + return aThis.invoke(FIND_PROPERTY_BY_INDEX_METHOD, ExpressionDef.constant(propertyIndex)); + })), ExpressionDef.nullValue()).returning() + ); + } + statements.add(ExpressionDef.nullValue().returning()); + return StatementDef.multi(statements); } - private void buildGetIndexedProperties(ClassWriter classWriter) { + @Nullable + private MethodDef getGetIndexedProperties() { if (indexByAnnotations.isEmpty()) { - return; + return null; } - GeneratorAdapter writer = new GeneratorAdapter(classWriter.visitMethod( - ACC_PUBLIC | ACC_FINAL, - GET_INDEXED_PROPERTIES.getName(), - GET_INDEXED_PROPERTIES.getDescriptor(), - null, - null), - ACC_PUBLIC | ACC_FINAL, - GET_INDEXED_PROPERTIES.getName(), - GET_INDEXED_PROPERTIES.getDescriptor() - ); - writer.loadThis(); - writer.loadArg(0); - writer.invokeVirtual(Type.getType(Class.class), new Method("getName", Type.getType(String.class), new Type[]{})); - int classNameLocal = writer.newLocal(Type.getType(String.class)); - writer.storeLocal(classNameLocal); - writer.loadLocal(classNameLocal); - - new StringSwitchWriter() { - - @Override - protected Set getKeys() { - return indexByAnnotations.keySet(); - } - - @Override - protected void pushStringValue() { - writer.loadLocal(classNameLocal); - } - - @Override - protected void onMatch(String annotationName, Label end) { - writer.loadThis(); - writer.getStatic(introspectionType, annotationIndexFields.get(annotationName), Type.getType(int[].class)); - writer.invokeVirtual(introspectionType, GET_BP_INDEXED_SUBSET_METHOD); - writer.returnValue(); - } - - }.write(writer); - - writer.invokeStatic(Type.getType(Collections.class), COLLECTIONS_EMPTY_LIST); - writer.returnValue(); - writer.visitMaxs(DEFAULT_MAX_STACK, 1); - writer.visitEnd(); + TypeDef returnType = TypeDef.of(GET_INDEXED_PROPERTIES.getReturnType()); + return MethodDef.builder(GET_INDEXED_PROPERTIES.getName()) + .returns(returnType) + .addModifiers(Modifier.PUBLIC) + .addParameters(GET_INDEXED_PROPERTIES.getParameterTypes()) + .build((aThis, methodParameters) -> + methodParameters.get(0).invoke("getName", TypeDef.STRING) + .asExpressionSwitch(returnType, indexByAnnotations.keySet().stream() + .collect(Collectors.toMap( + ExpressionDef::constant, + annotationName -> + aThis.invoke( + GET_BP_INDEXED_SUBSET_METHOD, + introspectionTypeDef.getStaticField(annotationIndexFields.get(annotationName)) + ) + )), ExpressionDef.nullValue()) + .returning()); } private int getPropertyIndex(String propertyName) { @@ -926,38 +939,24 @@ private int getPropertyIndex(String propertyName) { if (beanPropertyData != null) { return beanProperties.indexOf(beanPropertyData); } - throw new IllegalStateException("Property not found: " + propertyName + " " + classElement.getName()); + throw new IllegalStateException("Property not found: " + propertyName + " " + beanClassElement.getName()); } - private void writeInstantiateMethod(ClassWriter classWriter, MethodElement constructor, String methodName, Class... args) { - final String desc = getMethodDescriptor(Object.class, Arrays.asList(args)); - final GeneratorAdapter instantiateInternal = new GeneratorAdapter(classWriter.visitMethod( - ACC_PUBLIC, - methodName, - desc, - null, - null - ), ACC_PUBLIC, - methodName, - desc); - - if (args.length == 0) { - invokeBeanConstructor(instantiateInternal, constructor, true, null); - } else { - invokeBeanConstructor(instantiateInternal, constructor, true, (index, parameter) -> { - instantiateInternal.loadArg(0); - instantiateInternal.push(index); - instantiateInternal.arrayLoad(TYPE_OBJECT); - pushCastToType(instantiateInternal, JavaModelUtils.getTypeReference(parameter)); + private MethodDef getInstantiateMethod(MethodElement constructor, Method method) { + return MethodDef.override(method) + .build((aThis, methodParameters) -> { + if (method.getParameters().length == 0) { + return MethodGenUtils.invokeBeanConstructor(constructor, true, null).returning(); + } else { + List values = IntStream.range(0, constructor.getSuspendParameters().length) + .mapToObj(index -> methodParameters.get(0).arrayElement(index)) + .toList(); + return MethodGenUtils.invokeBeanConstructor(constructor, true, values).returning(); + } }); - } - - instantiateInternal.returnValue(); - instantiateInternal.visitMaxs(3, 1); - instantiateInternal.visitEnd(); } - private void pushAnnotationMetadata(ClassWriter classWriter, GeneratorAdapter staticInit, AnnotationMetadata annotationMetadata) { + private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotationMetadata, Map loadTypeMethods) { MutableAnnotationMetadata.contributeDefaults( this.annotationMetadata, annotationMetadata @@ -965,25 +964,18 @@ private void pushAnnotationMetadata(ClassWriter classWriter, GeneratorAdapter st annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); if (annotationMetadata.isEmpty()) { - staticInit.push((String) null); + return ExpressionDef.nullValue(); } else if (annotationMetadata instanceof AnnotationMetadataReference annotationMetadataReference) { - String className = annotationMetadataReference.getClassName(); - staticInit.getStatic(getTypeReferenceForName(className), FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); + return AnnotationMetadataGenUtils.annotationMetadataReference(annotationMetadataReference); } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - AnnotationMetadataWriter.instantiateNewMetadataHierarchy( - introspectionType, - classWriter, - staticInit, + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy( + introspectionTypeDef, annotationMetadataHierarchy, - defaults, loadTypeMethods); } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - AnnotationMetadataWriter.instantiateNewMetadata( - introspectionType, - classWriter, - staticInit, + return AnnotationMetadataGenUtils.instantiateNewMetadata( + introspectionTypeDef, mutableAnnotationMetadata, - defaults, loadTypeMethods); } else { throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); @@ -1028,7 +1020,19 @@ private void processConstructorEvaluatedMetadata(MethodElement constructor) { } } - private record ExceptionDispatchTarget(Class exceptionType, String message) implements DispatchWriter.DispatchTarget { + @NonNull + @Override + public Element[] getOriginatingElements() { + return originatingElements.getOriginatingElements(); + } + + @Override + public void addOriginatingElement(@NonNull Element element) { + originatingElements.addOriginatingElement(element); + } + + private record ExceptionDispatchTarget(Class exceptionType, + String message) implements DispatchWriter.DispatchTarget { @Override public boolean supportsDispatchOne() { @@ -1036,22 +1040,51 @@ public boolean supportsDispatchOne() { } @Override - public void writeDispatchOne(GeneratorAdapter writer, int index) { - writer.throwException(Type.getType(exceptionType), message); + public boolean supportsDispatchMulti() { + return false; + } + + @Override + public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) { + return ClassTypeDef.of(exceptionType).instantiate(ExpressionDef.constant(message)).doThrow(); + } + + @Override + public StatementDef dispatchOne(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { + return ClassTypeDef.of(exceptionType).instantiate(ExpressionDef.constant(message)).doThrow(); + } + + @Override + public MethodElement getMethodElement() { + return null; + } + + @Override + public TypedElement getDeclaringType() { + return null; } } /** * Copy constructor "with" method writer. */ - private final class CopyConstructorDispatchTarget implements DispatchWriter.DispatchTarget { + private static final class CopyConstructorDispatchTarget implements DispatchWriter.DispatchTarget { + private final ClassTypeDef beanType; + private final List beanProperties; + private final DispatchWriter dispatchWriter; private final MethodElement constructor; - private final String parameterName; - - private CopyConstructorDispatchTarget(MethodElement constructor, String name) { + private final Map propertyNames = new HashMap<>(); + private StatementDef statement; + + private CopyConstructorDispatchTarget(ClassTypeDef beanType, + List beanProperties, + DispatchWriter dispatchWriter, + MethodElement constructor) { + this.beanType = beanType; + this.beanProperties = beanProperties; + this.dispatchWriter = dispatchWriter; this.constructor = constructor; - this.parameterName = name; } @Override @@ -1060,40 +1093,20 @@ public boolean supportsDispatchOne() { } @Override - public boolean writeDispatchOne(GeneratorAdapter writer, int methodIndex, Map stateMap) { - CopyConstructorDispatchState state = (CopyConstructorDispatchState) stateMap.computeIfAbsent(CopyConstructorDispatchState.KEY, k -> new CopyConstructorDispatchState(constructor, writer.newLabel())); - state.propertyNames.put(parameterName, methodIndex); - writer.goTo(state.label); + public boolean supportsDispatchMulti() { return false; } - } - - /** - * Shared implementation of {@link CopyConstructorDispatchTarget#writeDispatchOne}.
- * - * A non-shared copy constructor implementation would be O(n²) in the number of properties: For - * every property we generate a constructor call, and that constructor call has that many - * parameters too that all have to be loaded.
- * - * This shared implementation instead only generates one constructor call, and branches on each - * loaded property to figure out whether to copy it or to use the replacement from the - * {@code dispatchOne} parameter. - */ - private class CopyConstructorDispatchState implements DispatchWriter.DispatchTargetState { - static final String KEY = CopyConstructorDispatchState.class.getName(); - final MethodElement constructor; - final Label label; - final Map propertyNames = new HashMap<>(); - - CopyConstructorDispatchState(MethodElement constructor, Label label) { - this.constructor = constructor; - this.label = label; + @Override + public StatementDef dispatchOne(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { + if (statement == null) { + // The unique statement provided for the switch case should produce one case + statement = createStatement(caseExpression, target, value); + } + return statement; } - @Override - public void complete(GeneratorAdapter writer) { - writer.visitLabel(label); + private StatementDef createStatement(ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { // In this case we have to do the copy constructor approach Set constructorProps = new HashSet<>(); @@ -1131,136 +1144,121 @@ public void complete(GeneratorAdapter writer) { constructorProps.add(prop); } else { isMutable = false; - nonMutableMessage = "Cannot create copy of type [" + beanType.getClassName() + "]. Property of type [" + propertyType.getName() + "] is not assignable to constructor argument [" + parameterName + "]"; + nonMutableMessage = "Cannot create copy of type [" + beanType.getName() + "]. Property of type [" + propertyType.getName() + "] is not assignable to constructor argument [" + parameterName + "]"; } } else { isMutable = false; - nonMutableMessage = "Cannot create copy of type [" + beanType.getClassName() + "]. Constructor contains argument [" + parameterName + "] that is not a readable property"; + nonMutableMessage = "Cannot create copy of type [" + beanType.getName() + "]. Constructor contains argument [" + parameterName + "] that is not a readable property"; break; } } if (isMutable) { - - writer.loadArg(1); - pushCastToType(writer, beanType); - int prevBeanTypeLocal = writer.newLocal(beanType); - writer.storeLocal(prevBeanTypeLocal, beanType); - - // NOTE: It doesn't make sense to check defaults for the copy constructor - - invokeBeanConstructor(writer, constructor, false, (paramIndex, parameter) -> { - Object constructorArgument = constructorArguments[paramIndex]; - TypedElement supplierType; - if (constructorArgument instanceof MethodElement readMethod) { - supplierType = readMethod.getReturnType(); - } else if (constructorArgument instanceof FieldElement fieldElement) { - supplierType = fieldElement; - } else { - throw new IllegalStateException(); - } - - Label endOfProperty = null; - Integer target = propertyNames.get(parameter.getName()); - if (target != null) { - if (propertyNames.size() == 1) { - writer.loadArg(2); // Load new property value - pushCastFromObjectToType(writer, parameter); - // if we're the only replaceable property, we can skip the second branch - return; - } - // replace property with new value - Label nonReplaceBranch = writer.newLabel(); - writer.loadArg(0); - writer.push(target); - writer.ifICmp(GeneratorAdapter.NE, nonReplaceBranch); - writer.loadArg(2); // Load new property value - pushCastFromObjectToType(writer, parameter); - endOfProperty = writer.newLabel(); - writer.goTo(endOfProperty); - writer.visitLabel(nonReplaceBranch); - } - - if (constructorArgument instanceof MethodElement readMethod) { - writer.loadLocal(prevBeanTypeLocal, beanType); - invokeMethod(writer, readMethod); - } else { - writer.loadLocal(prevBeanTypeLocal, beanType); - invokeGetField(writer, (FieldElement) constructorArgument); - } - pushCastToType(writer, supplierType, parameter); - - if (endOfProperty != null) { - writer.visitLabel(endOfProperty); - } - - }); - - List readWriteProps = beanProperties.stream() - .filter(bp -> bp.setDispatchIndex != -1 && bp.getDispatchIndex != -1 && !constructorProps.contains(bp)).toList(); - - if (!readWriteProps.isEmpty()) { - int beanTypeLocal = writer.newLocal(beanType); - writer.storeLocal(beanTypeLocal, beanType); - - for (BeanPropertyData readWriteProp : readWriteProps) { - DispatchWriter.DispatchTarget readDispatch = dispatchWriter.getDispatchTargets().get(readWriteProp.getDispatchIndex); - if (readDispatch instanceof DispatchWriter.MethodDispatchTarget methodDispatchTarget) { - MethodElement readMethod = methodDispatchTarget.getMethodElement(); - writer.loadLocal(beanTypeLocal, beanType); - writer.loadLocal(prevBeanTypeLocal, beanType); - invokeMethod(writer, readMethod); - } else if (readDispatch instanceof DispatchWriter.FieldGetDispatchTarget fieldGetDispatchTarget) { - FieldElement fieldElement = fieldGetDispatchTarget.getField(); - writer.loadLocal(beanTypeLocal, beanType); - writer.loadLocal(prevBeanTypeLocal, beanType); - invokeGetField(writer, fieldElement); + return target.cast(beanType).newLocal("prevBean", prevBeanVar -> { + List values = new ArrayList<>(constructorArguments.length); + for (int i = 0; i < parameters.length; i++) { + ParameterElement parameter = parameters[i]; + Object constructorArgument = constructorArguments[i]; + + ExpressionDef oldValueExp; + if (constructorArgument instanceof MethodElement readMethod) { + oldValueExp = prevBeanVar.invoke(readMethod); } else { - throw new IllegalStateException(); + oldValueExp = prevBeanVar.field((FieldElement) constructorArgument); } - DispatchWriter.DispatchTarget writeDispatch = dispatchWriter.getDispatchTargets().get(readWriteProp.setDispatchIndex); - if (writeDispatch instanceof DispatchWriter.MethodDispatchTarget methodDispatchTarget) { - MethodElement writeMethod = methodDispatchTarget.getMethodElement(); - ClassElement writeReturnType = invokeMethod(writer, writeMethod); - if (!writeReturnType.isVoid()) { - writer.pop(); + Integer propertyIndex = propertyNames.get(parameter.getName()); + ExpressionDef paramExp; + if (propertyIndex != null) { + ExpressionDef.Cast newPropertyValue = value.cast(TypeDef.erasure(parameter.getType())); + if (propertyNames.size() == 1) { + paramExp = newPropertyValue; + } else { + paramExp = caseExpression.equalsStructurally(ExpressionDef.constant((int) propertyIndex)).doIfElse( + newPropertyValue, + oldValueExp + ); } - } else if (writeDispatch instanceof DispatchWriter.FieldSetDispatchTarget fieldSetDispatchTarget) { - FieldElement fieldElement = fieldSetDispatchTarget.getField(); - invokeSetField(writer, fieldElement); } else { - throw new IllegalStateException(); + paramExp = oldValueExp; } - + values.add(paramExp); } - writer.loadLocal(beanTypeLocal, beanType); - } - writer.returnValue(); + + // NOTE: It doesn't make sense to check defaults for the copy constructor + ExpressionDef newInstance = MethodGenUtils.invokeBeanConstructor(constructor, false, values); + return withSetSettersAndFields(newInstance, prevBeanVar, constructorProps); + }); } else { // In this case the bean cannot be mutated via either copy constructor or setter so simply throw an exception - writer.throwException(Type.getType(UnsupportedOperationException.class), nonMutableMessage); + return ClassTypeDef.of(UnsupportedOperationException.class).instantiate(ExpressionDef.constant(nonMutableMessage)).doThrow(); } } - @NonNull - private ClassElement invokeMethod(GeneratorAdapter mutateMethod, MethodElement method) { - ClassElement returnType = method.getReturnType(); - if (classElement.isInterface()) { - mutateMethod.invokeInterface(beanType, new Method(method.getName(), getMethodDescriptor(returnType, Arrays.asList(method.getParameters())))); - } else { - mutateMethod.invokeVirtual(beanType, new Method(method.getName(), getMethodDescriptor(returnType, Arrays.asList(method.getParameters())))); + @Override + public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) { + throw new IllegalStateException("Not supported"); + } + + private StatementDef withSetSettersAndFields(ExpressionDef newInstance, + VariableDef prevBeanVar, + Set constructorProps) { + List readWriteProps = beanProperties.stream() + .filter(bp -> bp.setDispatchIndex != -1 && bp.getDispatchIndex != -1 && !constructorProps.contains(bp)).toList(); + + if (readWriteProps.isEmpty()) { + return newInstance.returning(); } - return returnType; + return newInstance + .newLocal("newBean", newBeanVar -> { + List statements = new ArrayList<>(); + for (BeanPropertyData readWriteProp : readWriteProps) { + DispatchWriter.DispatchTarget readDispatch = dispatchWriter.getDispatchTargets().get(readWriteProp.getDispatchIndex); + ExpressionDef oldValueExp; + if (readDispatch instanceof DispatchWriter.MethodDispatchTarget methodDispatchTarget) { + MethodElement readMethod = methodDispatchTarget.getMethodElement(); + oldValueExp = prevBeanVar.invoke(readMethod); + } else if (readDispatch instanceof DispatchWriter.FieldGetDispatchTarget fieldGetDispatchTarget) { + FieldElement fieldElement = fieldGetDispatchTarget.getField(); + oldValueExp = prevBeanVar.field(fieldElement); + } else { + throw new IllegalStateException(); + } + + DispatchWriter.DispatchTarget writeDispatch = dispatchWriter.getDispatchTargets().get(readWriteProp.setDispatchIndex); + if (writeDispatch instanceof DispatchWriter.MethodDispatchTarget methodDispatchTarget) { + MethodElement writeMethod = methodDispatchTarget.getMethodElement(); + statements.add( + newBeanVar.invoke( + writeMethod, + oldValueExp + ) + ); + } else if (writeDispatch instanceof DispatchWriter.FieldSetDispatchTarget fieldSetDispatchTarget) { + FieldElement fieldElement = fieldSetDispatchTarget.getField(); + statements.add( + newBeanVar.field(fieldElement).assign(oldValueExp) + ); + } else { + throw new IllegalStateException(); + } + + } + statements.add(newBeanVar.returning()); + return StatementDef.multi(statements); + }); } - private void invokeGetField(GeneratorAdapter mutateMethod, FieldElement field) { - mutateMethod.getField(beanType, field.getName(), JavaModelUtils.getTypeReference(field.getType())); + @Override + public MethodElement getMethodElement() { + return null; } - private void invokeSetField(GeneratorAdapter mutateMethod, FieldElement field) { - mutateMethod.putField(beanType, field.getName(), JavaModelUtils.getTypeReference(field.getType())); + @Override + public TypedElement getDeclaringType() { + return constructor.getDeclaringType(); } + } private record BeanMethodData(MethodElement methodElement, int dispatchIndex) { @@ -1290,7 +1288,7 @@ private record BeanPropertyData(@NonNull String name, * index to be created. * * @param annotationName The annotation name - * @param value The annotation value + * @param value The annotation value */ private record AnnotationWithValue(@NonNull String annotationName, @Nullable String value) { diff --git a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/IntrospectedTypeElementVisitor.java b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/IntrospectedTypeElementVisitor.java index 090ef931b10..629fd121d1f 100644 --- a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/IntrospectedTypeElementVisitor.java +++ b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/IntrospectedTypeElementVisitor.java @@ -303,7 +303,9 @@ public void finish(VisitorContext visitorContext) { } catch (ElementPostponedToNextRoundException ignore) { // Ignore, next round will redo } catch (IOException e) { - throw new ClassGenerationException("I/O error occurred during class generation: " + e.getMessage(), e); + throw new ClassGenerationException("I/O error occurred during class: '" + className + "' generation: " + e.getMessage(), e); + } catch (Throwable e) { + throw new RuntimeException("Failed to generate class: '" + className + "': " + e.getMessage(), e); } }); @@ -390,7 +392,7 @@ private void handleBuilder( ); builderType.getEnclosedElements(builderMethodQuery) .forEach(builderWriter::visitBeanMethod); - writers.put(builderWriter.getBeanType().getClassName(), builderWriter); + writers.put(builderWriter.getBeanType().getName(), builderWriter); } else { context.fail("No build method found in builder: " + builderType.getName(), classToBuild); } @@ -464,7 +466,7 @@ private void processElement(boolean metadata, } } - writers.put(writer.getBeanType().getClassName(), writer); + writers.put(writer.getBeanType().getName(), writer); addExecutableMethods(ce, writer, beanProperties); } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/AbstractAnnotationMetadataWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/AbstractAnnotationMetadataWriter.java deleted file mode 100644 index c7f3f3e8700..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/writer/AbstractAnnotationMetadataWriter.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.writer; - -import io.micronaut.core.annotation.AnnotationMetadata; -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; -import io.micronaut.inject.annotation.AnnotationMetadataReference; -import io.micronaut.inject.annotation.AnnotationMetadataWriter; -import io.micronaut.inject.annotation.MutableAnnotationMetadata; -import io.micronaut.inject.ast.Element; -import io.micronaut.inject.visitor.VisitorContext; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Base class for types that also write {@link io.micronaut.core.annotation.AnnotationMetadata}. - * - * @author Graeme Rocher - * @since 1.0 - */ -@Internal -public abstract class AbstractAnnotationMetadataWriter extends AbstractClassFileWriter { - - /** - * Field name for annotation metadata. - */ - public static final String FIELD_ANNOTATION_METADATA = "$ANNOTATION_METADATA"; - - /** - * Field name for empty metadata. - */ - public static final String FIELD_EMPTY_METADATA = "EMPTY_METADATA"; - - protected final Type targetClassType; - protected final AnnotationMetadata annotationMetadata; - protected final Map loadTypeMethods = new HashMap<>(); - protected final Map defaults = new HashMap<>(); - protected final EvaluatedExpressionProcessor evaluatedExpressionProcessor; - private final boolean writeAnnotationDefault; - - /** - * @param className The class name - * @param originatingElements The originating elements - * @param annotationMetadata The annotation metadata - * @param writeAnnotationDefaults Whether to write annotation defaults - * @param visitorContext The visitor context - */ - protected AbstractAnnotationMetadataWriter( - String className, - OriginatingElements originatingElements, - AnnotationMetadata annotationMetadata, - boolean writeAnnotationDefaults, - VisitorContext visitorContext) { - super(originatingElements); - this.targetClassType = getTypeReferenceForName(className); - this.annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - this.writeAnnotationDefault = writeAnnotationDefaults; - this.evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, getOriginatingElement()); - this.evaluatedExpressionProcessor.processEvaluatedExpressions(this.annotationMetadata, null); - } - - /** - * @param className The class name - * @param originatingElement The originating element - * @param annotationMetadata The annotation metadata - * @param writeAnnotationDefaults Whether to write annotation defaults - * @param visitorContext The visitor context - */ - protected AbstractAnnotationMetadataWriter( - String className, - Element originatingElement, - AnnotationMetadata annotationMetadata, - boolean writeAnnotationDefaults, - VisitorContext visitorContext) { - super(originatingElement); - this.targetClassType = getTypeReferenceForName(className); - this.annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - this.writeAnnotationDefault = writeAnnotationDefaults; - this.evaluatedExpressionProcessor = new EvaluatedExpressionProcessor(visitorContext, originatingElement); - this.evaluatedExpressionProcessor.processEvaluatedExpressions(this.annotationMetadata, null); - } - - /** - * @param classWriter The {@link ClassWriter} - */ - protected void writeGetAnnotationMetadataMethod(ClassWriter classWriter) { - GeneratorAdapter annotationMetadataMethod = beginAnnotationMetadataMethod(classWriter); - annotationMetadataMethod.loadThis(); - - // in order to save memory of a method doesn't have any annotations of its own but merely references class metadata - // then we set up an annotation metadata reference from the method to the class (or inherited method) metadata - AnnotationMetadata annotationMetadata = this.annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - annotationMetadataMethod.getStatic(Type.getType(AnnotationMetadata.class), FIELD_EMPTY_METADATA, Type.getType(AnnotationMetadata.class)); - } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { - String className = reference.getClassName(); - annotationMetadataMethod.getStatic(getTypeReferenceForName(className), AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } else { - annotationMetadataMethod.getStatic(targetClassType, AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } - annotationMetadataMethod.returnValue(); - annotationMetadataMethod.visitMaxs(1, 1); - annotationMetadataMethod.visitEnd(); - } - - /** - * Returns the generator adaptor for the method that resolves the annotation metadata. - * - * @param classWriter The class writer - * @return The generator adapter - */ - protected @NonNull - GeneratorAdapter beginAnnotationMetadataMethod(ClassWriter classWriter) { - return startPublicMethod(classWriter, "getAnnotationMetadata", AnnotationMetadata.class.getName()); - } - - /** - * @param classWriter The {@link ClassWriter} - */ - protected void writeAnnotationMetadataStaticInitializer(ClassWriter classWriter) { - writeAnnotationMetadataStaticInitializer(classWriter, defaults); - } - - /** - * @param classWriter The {@link ClassWriter} - * @param defaults The annotation defaults - */ - protected void writeAnnotationMetadataStaticInitializer(ClassWriter classWriter, Map defaults) { - if (!(annotationMetadata instanceof AnnotationMetadataReference)) { - - // write the static initializers for the annotation metadata - GeneratorAdapter staticInit = visitStaticInitializer(classWriter); - staticInit.visitCode(); - if (writeAnnotationDefault) { - writeAnnotationDefault(classWriter, staticInit, targetClassType, annotationMetadata, defaults, loadTypeMethods); - } - staticInit.visitLabel(new Label()); - initializeAnnotationMetadata(staticInit, classWriter, targetClassType, annotationMetadata, defaults, loadTypeMethods); - staticInit.visitInsn(RETURN); - staticInit.visitMaxs(1, 1); - staticInit.visitEnd(); - } - } - - /** - * @param classWriter The class writer - * @param staticInit The static init - * @param targetClassType The targetClassType - * @param annotationMetadata The annotation metadata - * @param defaults The defaults - * @param loadTypeMethods The loadTypeMethods - */ - public static void writeAnnotationDefault(ClassWriter classWriter, - GeneratorAdapter staticInit, - Type targetClassType, - AnnotationMetadata annotationMetadata, - Map defaults, - Map loadTypeMethods) { - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - return; - } - if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - annotationMetadata = annotationMetadataHierarchy.merge(); - } - if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - AnnotationMetadataWriter.writeAnnotationDefaults( - targetClassType, - classWriter, - staticInit, - mutableAnnotationMetadata, - defaults, - loadTypeMethods - ); - } else { - throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); - } - } - - /** - * @param staticInit The {@link GeneratorAdapter} - * @param classWriter The {@link ClassWriter} - * @param targetClassType The targetClassType - * @param annotationMetadata The annotation metadata - * @param defaults The annotation defaults - * @param loadTypeMethods The loadTypeMethods - */ - public static void initializeAnnotationMetadata(GeneratorAdapter staticInit, - ClassWriter classWriter, - Type targetClassType, - AnnotationMetadata annotationMetadata, - Map defaults, - Map loadTypeMethods) { - Type annotationMetadataType = Type.getType(AnnotationMetadata.class); - classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, FIELD_ANNOTATION_METADATA, annotationMetadataType.getDescriptor(), null, null); - - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - staticInit.getStatic(Type.getType(AnnotationMetadata.class), FIELD_EMPTY_METADATA, Type.getType(AnnotationMetadata.class)); - } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - AnnotationMetadataWriter.instantiateNewMetadata( - targetClassType, - classWriter, - staticInit, - mutableAnnotationMetadata, - defaults, - loadTypeMethods - ); - } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - AnnotationMetadataWriter.instantiateNewMetadataHierarchy( - targetClassType, - classWriter, - staticInit, - annotationMetadataHierarchy, - defaults, - loadTypeMethods - ); - } else { - throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); - } - - staticInit.putStatic(targetClassType, FIELD_ANNOTATION_METADATA, annotationMetadataType); - } - -} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/AbstractClassFileWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/AbstractClassFileWriter.java deleted file mode 100644 index 1be4de04e81..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/writer/AbstractClassFileWriter.java +++ /dev/null @@ -1,1684 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.writer; - -import io.micronaut.core.annotation.AnnotationMetadata; -import io.micronaut.core.annotation.AnnotationUtil; -import io.micronaut.core.annotation.Generated; -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.reflect.ClassUtils; -import io.micronaut.core.reflect.ReflectionUtils; -import io.micronaut.core.type.Argument; -import io.micronaut.core.util.ArrayUtils; -import io.micronaut.core.util.CollectionUtils; -import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; -import io.micronaut.inject.annotation.AnnotationMetadataReference; -import io.micronaut.inject.annotation.AnnotationMetadataWriter; -import io.micronaut.inject.annotation.MutableAnnotationMetadata; -import io.micronaut.inject.ast.ClassElement; -import io.micronaut.inject.ast.Element; -import io.micronaut.inject.ast.GenericPlaceholderElement; -import io.micronaut.inject.ast.KotlinParameterElement; -import io.micronaut.inject.ast.ParameterElement; -import io.micronaut.inject.ast.TypedElement; -import io.micronaut.inject.processing.JavaModelUtils; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Array; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static io.micronaut.core.util.StringUtils.EMPTY_STRING_ARRAY; -import static io.micronaut.inject.annotation.AnnotationMetadataWriter.isSupportedMapValue; - -/** - * Abstract class that writes generated classes to disk and provides convenience methods for building classes. - * - * @author Graeme Rocher - * @since 1.0 - */ -@Internal -public abstract class AbstractClassFileWriter implements Opcodes, OriginatingElements, ClassOutputWriter { - - protected static final Type TYPE_ARGUMENT = Type.getType(Argument.class); - protected static final Type TYPE_ARGUMENT_ARRAY = Type.getType(Argument[].class); - protected static final String ZERO_ARGUMENTS_CONSTANT = "ZERO_ARGUMENTS"; - protected static final String CONSTRUCTOR_NAME = ""; - protected static final String DESCRIPTOR_DEFAULT_CONSTRUCTOR = "()V"; - protected static final Method METHOD_DEFAULT_CONSTRUCTOR = new Method(CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR); - protected static final Type TYPE_OBJECT = Type.getType(Object.class); - protected static final Type TYPE_CLASS = Type.getType(Class.class); - protected static final int DEFAULT_MAX_STACK = 23; - protected static final Type TYPE_GENERATED = Type.getType(Generated.class); - protected static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[\\])+$"); - - protected static final Method METHOD_CREATE_ARGUMENT_SIMPLE = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "of", - Class.class, - String.class - ) - ); - protected static final Method METHOD_GENERIC_PLACEHOLDER_SIMPLE = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "ofTypeVariable", - Class.class, - String.class, - String.class - ) - ); - protected static final Method METHOD_CREATE_TYPE_VARIABLE_SIMPLE = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "ofTypeVariable", - Class.class, - String.class - ) - ); - private static final Method METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "of", - Class.class, - String.class, - AnnotationMetadata.class, - Argument[].class - ) - ); - private static final Method METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "ofTypeVariable", - Class.class, - String.class, - AnnotationMetadata.class, - Argument[].class - ) - ); - private static final Method METHOD_CREATE_GENERIC_PLACEHOLDER_WITH_ANNOTATION_METADATA_GENERICS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "ofTypeVariable", - Class.class, - String.class, - String.class, - AnnotationMetadata.class, - Argument[].class - ) - ); - private static final Method METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_CLASS_GENERICS = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - Argument.class, - "of", - Class.class, - AnnotationMetadata.class, - Class[].class - ) - ); - - private static final Type MAP_TYPE = Type.getType(Map.class); - private static final Type LIST_TYPE = Type.getType(List.class); - - private static final org.objectweb.asm.commons.Method[] MAP_OF; - private static final org.objectweb.asm.commons.Method[] LIST_OF; - private static final org.objectweb.asm.commons.Method MAP_BY_ARRAY; - private static final org.objectweb.asm.commons.Method MAP_ENTRY; - private static final org.objectweb.asm.commons.Method LIST_BY_ARRAY; - - static { - MAP_OF = new Method[11]; - for (int i = 0; i < MAP_OF.length; i++) { - Class[] mapArgs = new Class[i * 2]; - for (int k = 0; k < i * 2; k += 2) { - mapArgs[k] = Object.class; - mapArgs[k + 1] = Object.class; - } - MAP_OF[i] = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(Map.class, "of", mapArgs)); - } - MAP_ENTRY = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(Map.class, "entry", Object.class, Object.class)); - MAP_BY_ARRAY = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(Map.class, "ofEntries", Map.Entry[].class)); - } - - static { - LIST_OF = new Method[11]; - for (int i = 0; i < LIST_OF.length; i++) { - Class[] listArgs = new Class[i]; - for (int k = 0; k < i; k += 1) { - listArgs[k] = Object.class; - } - LIST_OF[i] = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(List.class, "of", listArgs)); - } - LIST_BY_ARRAY = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(List.class, "of", Object[].class)); - } - - protected final OriginatingElements originatingElements; - - /** - * @param originatingElements The originating elements - */ - protected AbstractClassFileWriter(Element... originatingElements) { - this(OriginatingElements.of(originatingElements)); - } - - /** - * @param originatingElements The originating elements - */ - protected AbstractClassFileWriter(OriginatingElements originatingElements) { - this.originatingElements = Objects.requireNonNull(originatingElements, "The originating elements cannot be null"); - } - - @NonNull - @Override - public Element[] getOriginatingElements() { - return originatingElements.getOriginatingElements(); - } - - @Override - public void addOriginatingElement(@NonNull Element element) { - originatingElements.addOriginatingElement(element); - } - - /** - * Pushes type arguments onto the stack. - * - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param owningType The owning type - * @param owningTypeWriter The declaring class writer - * @param generatorAdapter The generator adapter - * @param declaringElementName The declaring class element of the generics - * @param types The type references - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected static void pushTypeArgumentElements( - AnnotationMetadata annotationMetadataWithDefaults, - Type owningType, - ClassWriter owningTypeWriter, - GeneratorAdapter generatorAdapter, - String declaringElementName, - Map types, - Map defaults, - Map loadTypeMethods) { - if (types == null || types.isEmpty()) { - pushNewArray(generatorAdapter, Argument.class, 0); - return; - } - pushTypeArgumentElements(annotationMetadataWithDefaults, owningType, owningTypeWriter, generatorAdapter, declaringElementName, null, types, new HashSet<>(5), defaults, loadTypeMethods); - } - - @SuppressWarnings("java:S1872") - private static void pushTypeArgumentElements( - AnnotationMetadata annotationMetadataWithDefaults, - Type owningType, - ClassWriter declaringClassWriter, - GeneratorAdapter generatorAdapter, - String declaringElementName, - @Nullable - ClassElement element, - Map types, - Set visitedTypes, - Map defaults, - Map loadTypeMethods) { - if (element == null) { - if (visitedTypes.contains(declaringElementName)) { - generatorAdapter.getStatic( - TYPE_ARGUMENT, - ZERO_ARGUMENTS_CONSTANT, - TYPE_ARGUMENT_ARRAY - ); - return; - } else { - visitedTypes.add(declaringElementName); - } - } - - // Build calls to Argument.create(...) - pushNewArray(generatorAdapter, Argument.class, types.entrySet(), entry -> { - String argumentName = entry.getKey(); - ClassElement classElement = entry.getValue(); - Type classReference = JavaModelUtils.getTypeReference(classElement); - Map typeArguments = classElement.getTypeArguments(); - if (CollectionUtils.isNotEmpty(typeArguments) || !classElement.getAnnotationMetadata().isEmpty()) { - buildArgumentWithGenerics( - annotationMetadataWithDefaults, - owningType, - declaringClassWriter, - generatorAdapter, - argumentName, - classReference, - classElement, - typeArguments, - visitedTypes, - defaults, - loadTypeMethods - ); - } else { - buildArgument(generatorAdapter, argumentName, classElement); - } - }); - } - - /** - * Builds an argument instance. - * - * @param generatorAdapter The generator adapter. - * @param argumentName The argument name - * @param objectType The object type - */ - protected static void buildArgument(GeneratorAdapter generatorAdapter, String argumentName, Type objectType) { - // 1st argument: the type - generatorAdapter.push(objectType); - // 2nd argument: the name - generatorAdapter.push(argumentName); - - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_SIMPLE - ); - } - - /** - * Builds an argument instance. - * - * @param generatorAdapter The generator adapter. - * @param argumentName The argument name - * @param objectType The object type - */ - protected static void buildArgument(GeneratorAdapter generatorAdapter, String argumentName, ClassElement objectType) { - // 1st argument: the type - generatorAdapter.push(getTypeReference(objectType)); - // 2nd argument: the name - generatorAdapter.push(argumentName); - - if (objectType instanceof GenericPlaceholderElement placeholderElement) { - // Persist resolved placeholder for backward compatibility - objectType = placeholderElement.getResolved().orElse(placeholderElement); - } - - if (objectType instanceof GenericPlaceholderElement || objectType.isTypeVariable()) { - String variableName = argumentName; - if (objectType instanceof GenericPlaceholderElement placeholderElement) { - variableName = placeholderElement.getVariableName(); - } - boolean hasVariable = !variableName.equals(argumentName); - if (hasVariable) { - generatorAdapter.push(variableName); - } - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - hasVariable ? METHOD_GENERIC_PLACEHOLDER_SIMPLE : METHOD_CREATE_TYPE_VARIABLE_SIMPLE - ); - } else { - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_SIMPLE - ); - } - } - - /** - * Builds generic type arguments recursively. - * - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param owningType The owning type - * @param owningClassWriter The declaring writer - * @param generatorAdapter The generator adapter to use - * @param argumentName The argument name - * @param typeReference The type name - * @param classElement The class element that declares the generics - * @param typeArguments The nested type arguments - * @param visitedTypes The visited types - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected static void buildArgumentWithGenerics( - AnnotationMetadata annotationMetadataWithDefaults, - Type owningType, - ClassWriter owningClassWriter, - GeneratorAdapter generatorAdapter, - String argumentName, - Type typeReference, - ClassElement classElement, - Map typeArguments, - Set visitedTypes, - Map defaults, - Map loadTypeMethods) { - // 1st argument: the type - generatorAdapter.push(typeReference); - // 2nd argument: the name - generatorAdapter.push(argumentName); - - if (classElement instanceof GenericPlaceholderElement placeholderElement) { - // Persist resolved placeholder for backward compatibility - classElement = placeholderElement.getResolved().orElse(classElement); - } - - // Persist only type annotations added to the type argument - AnnotationMetadata annotationMetadata = MutableAnnotationMetadata.of(classElement.getTypeAnnotationMetadata()); - boolean hasAnnotationMetadata = !annotationMetadata.isEmpty(); - - boolean isRecursiveType = false; - if (classElement instanceof GenericPlaceholderElement placeholderElement) { - // Prevent placeholder recursion - Object genericNativeType = placeholderElement.getGenericNativeType(); - if (visitedTypes.contains(genericNativeType)) { - isRecursiveType = true; - } else { - visitedTypes.add(genericNativeType); - } - } - - boolean typeVariable = classElement.isTypeVariable(); - - if (isRecursiveType || !typeVariable && !hasAnnotationMetadata && typeArguments.isEmpty()) { - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_SIMPLE - ); - return; - } - - // 3rd argument: annotation metadata - if (!hasAnnotationMetadata) { - generatorAdapter.push((String) null); - } else { - MutableAnnotationMetadata.contributeDefaults( - annotationMetadataWithDefaults, - annotationMetadata - ); - - AnnotationMetadataWriter.instantiateNewMetadata( - owningType, - owningClassWriter, - generatorAdapter, - (MutableAnnotationMetadata) annotationMetadata, - defaults, - loadTypeMethods - ); - } - - // 4th argument, more generics - pushTypeArgumentElements( - annotationMetadataWithDefaults, - owningType, - owningClassWriter, - generatorAdapter, - classElement.getName(), - classElement, - typeArguments, - visitedTypes, - defaults, - loadTypeMethods - ); - - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - typeVariable ? METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS : METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS - ); - } - - /** - * Builds generic type arguments recursively. - * - * @param generatorAdapter The generator adapter to use - * @param type The type that declares the generics - * @param annotationMetadata The annotation metadata reference - * @param generics The generics - * @since 3.0.0 - */ - protected static void buildArgumentWithGenerics( - GeneratorAdapter generatorAdapter, - Type type, - AnnotationMetadataReference annotationMetadata, - ClassElement[] generics) { - // 1st argument: the type - generatorAdapter.push(type); - // 2nd argument: the annotation metadata - AnnotationMetadataWriter.pushAnnotationMetadataReference( - generatorAdapter, - annotationMetadata - ); - - // 3rd argument, the generics - pushNewArray(generatorAdapter, Class.class, generics, g -> generatorAdapter.push(getTypeReference(g))); - - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_CLASS_GENERICS - ); - } - - /** - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param declaringElementName The declaring element name - * @param owningType The owning type - * @param declaringClassWriter The declaring class writer - * @param generatorAdapter The {@link GeneratorAdapter} - * @param argumentTypes The argument types - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected static void pushBuildArgumentsForMethod( - AnnotationMetadata annotationMetadataWithDefaults, - String declaringElementName, - Type owningType, - ClassWriter declaringClassWriter, - GeneratorAdapter generatorAdapter, - Collection argumentTypes, - Map defaults, - Map loadTypeMethods) { - pushNewArray(generatorAdapter, Argument.class, argumentTypes, entry -> { - - ClassElement genericType = entry.getGenericType(); - - MutableAnnotationMetadata.contributeDefaults( - annotationMetadataWithDefaults, - entry.getAnnotationMetadata() - ); - MutableAnnotationMetadata.contributeDefaults( - annotationMetadataWithDefaults, - genericType.getTypeAnnotationMetadata() - ); - - String argumentName = entry.getName(); - MutableAnnotationMetadata annotationMetadata = new AnnotationMetadataHierarchy( - entry.getAnnotationMetadata(), - genericType.getTypeAnnotationMetadata() - ).merge(); - - if (entry instanceof KotlinParameterElement kp && kp.hasDefault()) { - annotationMetadata.removeAnnotation(AnnotationUtil.NON_NULL); - annotationMetadata.addAnnotation(AnnotationUtil.NULLABLE, Map.of()); - annotationMetadata.addDeclaredAnnotation(AnnotationUtil.NULLABLE, Map.of()); - } - - Map typeArguments = genericType.getTypeArguments(); - pushCreateArgument( - annotationMetadataWithDefaults, - declaringElementName, - owningType, - declaringClassWriter, - generatorAdapter, - argumentName, - genericType, - annotationMetadata, - typeArguments, defaults, loadTypeMethods - ); - }); - } - - /** - * Pushes an argument. - * - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param owningType The owning type - * @param classWriter The declaring class writer - * @param generatorAdapter The generator adapter - * @param declaringTypeName The declaring type name - * @param argument The argument - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected void pushReturnTypeArgument(AnnotationMetadata annotationMetadataWithDefaults, - Type owningType, - ClassWriter classWriter, - GeneratorAdapter generatorAdapter, - String declaringTypeName, - ClassElement argument, - Map defaults, - Map loadTypeMethods) { - // Persist only type annotations added - AnnotationMetadata annotationMetadata = argument.getTypeAnnotationMetadata(); - - Type argumentType = Type.getType(Argument.class); - if (argument.isVoid()) { - generatorAdapter.getStatic(argumentType, "VOID", argumentType); - return; - } - if (argument.isPrimitive() && !argument.isArray()) { - String constantName = argument.getName().toUpperCase(Locale.ENGLISH); - // refer to constant for primitives - generatorAdapter.getStatic(argumentType, constantName, argumentType); - return; - } - - if (annotationMetadata.isEmpty() - && !argument.isArray() - && String.class.getName().equals(argument.getType().getName()) - && argument.getName().equals(argument.getType().getName()) - && argument.getAnnotationMetadata().isEmpty()) { - generatorAdapter.getStatic(argumentType, "STRING", argumentType); - return; - } - - pushCreateArgument( - annotationMetadataWithDefaults, - declaringTypeName, - owningType, - classWriter, - generatorAdapter, - argument.getName(), - argument, - annotationMetadata, - argument.getTypeArguments(), - defaults, - loadTypeMethods - ); - } - - /** - * Pushes a new Argument creation. - * - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param declaringTypeName The declaring type name - * @param owningType The owning type - * @param classWriter The class writer - * @param generatorAdapter The generator adapter - * @param argumentName The argument name - * @param classElement The typed element - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected static void pushCreateArgument( - AnnotationMetadata annotationMetadataWithDefaults, - String declaringTypeName, - Type owningType, - ClassWriter classWriter, - GeneratorAdapter generatorAdapter, - String argumentName, - ClassElement classElement, - Map defaults, - Map loadTypeMethods) { - - pushCreateArgument( - annotationMetadataWithDefaults, - declaringTypeName, - owningType, - classWriter, - generatorAdapter, - argumentName, - classElement, - classElement.getAnnotationMetadata(), - classElement.getTypeArguments(), - defaults, - loadTypeMethods - ); - } - - /** - * Pushes a new Argument creation. - * - * @param annotationMetadataWithDefaults The annotation metadata with defaults - * @param declaringTypeName The declaring type name - * @param owningType The owning type - * @param declaringClassWriter The declaring class writer - * @param generatorAdapter The generator adapter - * @param argumentName The argument name - * @param typedElement The typed element - * @param annotationMetadata The annotation metadata - * @param typeArguments The type arguments - * @param defaults The annotation defaults - * @param loadTypeMethods The load type methods - */ - protected static void pushCreateArgument( - AnnotationMetadata annotationMetadataWithDefaults, - String declaringTypeName, - Type owningType, - ClassWriter declaringClassWriter, - GeneratorAdapter generatorAdapter, - String argumentName, - TypedElement typedElement, - AnnotationMetadata annotationMetadata, - Map typeArguments, - Map defaults, - Map loadTypeMethods) { - annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); - - Type argumentType = JavaModelUtils.getTypeReference(typedElement); - - // 1st argument: The type - generatorAdapter.push(argumentType); - - // 2nd argument: The argument name - generatorAdapter.push(argumentName); - - boolean hasAnnotations = !annotationMetadata.isEmpty(); - boolean hasTypeArguments = typeArguments != null && !typeArguments.isEmpty(); - if (typedElement instanceof GenericPlaceholderElement placeholderElement) { - // Persist resolved placeholder for backward compatibility - typedElement = placeholderElement.getResolved().orElse(placeholderElement); - } - boolean isGenericPlaceholder = typedElement instanceof GenericPlaceholderElement; - boolean isTypeVariable = isGenericPlaceholder || ((typedElement instanceof ClassElement classElement) && classElement.isTypeVariable()); - String variableName = argumentName; - if (isGenericPlaceholder) { - variableName = ((GenericPlaceholderElement) typedElement).getVariableName(); - } - boolean hasVariableName = !variableName.equals(argumentName); - - if (!hasAnnotations && !hasTypeArguments && !isTypeVariable) { - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_SIMPLE - ); - return; - } - - if (isTypeVariable && hasVariableName) { - generatorAdapter.push(variableName); - } - - // 3rd argument: The annotation metadata - if (hasAnnotations) { - MutableAnnotationMetadata.contributeDefaults( - annotationMetadataWithDefaults, - annotationMetadata - ); - - AnnotationMetadataWriter.instantiateNewMetadata( - owningType, - declaringClassWriter, - generatorAdapter, - (MutableAnnotationMetadata) annotationMetadata, - defaults, - loadTypeMethods - ); - } else { - generatorAdapter.push((String) null); - } - - // 4th argument: The generic types - if (hasTypeArguments) { - pushTypeArgumentElements( - annotationMetadataWithDefaults, - owningType, - declaringClassWriter, - generatorAdapter, - declaringTypeName, - typeArguments, - defaults, - loadTypeMethods - ); - } else { - generatorAdapter.visitInsn(ACONST_NULL); - } - - if (isTypeVariable) { - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - hasVariableName ? METHOD_CREATE_GENERIC_PLACEHOLDER_WITH_ANNOTATION_METADATA_GENERICS : METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS - ); - } else { - - // Argument.create( .. ) - invokeInterfaceStaticMethod( - generatorAdapter, - Argument.class, - METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS - ); - } - } - - /** - * Write the class to the target directory. - * - * @param targetDir The target directory - * @throws IOException if there is an error writing the file - */ - public void writeTo(File targetDir) throws IOException { - accept(newClassWriterOutputVisitor(targetDir)); - } - - /** - * @return The originating element - */ - public @Nullable - Element getOriginatingElement() { - Element[] originatingElements = getOriginatingElements(); - if (ArrayUtils.isNotEmpty(originatingElements)) { - return originatingElements[0]; - } else { - return null; - } - } - - /** - * Implements a method called "getInterceptedType" for the given type and class writer. - * - * @param interceptedType The intercepted type - * @param classWriter The class writer - */ - protected void implementInterceptedTypeMethod(Type interceptedType, ClassWriter classWriter) { - GeneratorAdapter getTargetTypeMethod = startPublicMethodZeroArgs( - classWriter, - Class.class, - "getInterceptedType" - ); - getTargetTypeMethod.loadThis(); - getTargetTypeMethod.push(interceptedType); - getTargetTypeMethod.returnValue(); - getTargetTypeMethod.endMethod(); - } - - /** - * Returns the descriptor corresponding to the given class. - * - * @param type The type - * @return The descriptor for the class - */ - protected static String getTypeDescriptor(TypedElement type) { - return JavaModelUtils.getTypeReference(type).getDescriptor(); - } - - /** - * Returns the descriptor corresponding to the given class. - * - * @param type The type - * @return The descriptor for the class - */ - protected static String getTypeDescriptor(Class type) { - return Type.getDescriptor(type); - } - - /** - * Returns the descriptor corresponding to the given class. - * - * @param type The type - * @return The descriptor for the class - */ - protected static String getTypeDescriptor(String type) { - return getTypeDescriptor(type, EMPTY_STRING_ARRAY); - } - - /** - * Returns the Type reference corresponding to the given class. - * - * @param className The class name - * @param genericTypes The generic types - * @return The {@link Type} - */ - protected static Type getTypeReferenceForName(String className, String... genericTypes) { - String referenceString = getTypeDescriptor(className, genericTypes); - return Type.getType(referenceString); - } - - /** - * Return the type reference for a class. - * - * @param type The type - * @return The {@link Type} - */ - protected static Type getTypeReference(TypedElement type) { - return JavaModelUtils.getTypeReference(type); - } - - /** - * @param fieldType The field type - * @param injectMethodVisitor The {@link GeneratorAdapter} - */ - protected static void pushBoxPrimitiveIfNecessary(Type fieldType, GeneratorAdapter injectMethodVisitor) { - if (JavaModelUtils.isPrimitive(fieldType)) { - injectMethodVisitor.valueOf(fieldType); - } - } - - /** - * @param fieldType The field type - * @param injectMethodVisitor The {@link MethodVisitor} - */ - protected static void pushBoxPrimitiveIfNecessary(Class fieldType, GeneratorAdapter injectMethodVisitor) { - pushBoxPrimitiveIfNecessary(Type.getType(fieldType), injectMethodVisitor); - } - - /** - * @param fieldType The field type - * @param injectMethodVisitor The {@link GeneratorAdapter} - */ - protected static void pushBoxPrimitiveIfNecessary(TypedElement fieldType, GeneratorAdapter injectMethodVisitor) { - pushBoxPrimitiveIfNecessary(JavaModelUtils.getTypeReference(fieldType), injectMethodVisitor); - } - - /** - * @param ga The {@link GeneratorAdapter} - * @param type The type - */ - protected static void pushCastToType(GeneratorAdapter ga, Type type) { - if (JavaModelUtils.isPrimitive(type)) { - ga.unbox(type); - } else { - ga.checkCast(type); - } - } - - /** - * @param ga The {@link MethodVisitor} - * @param type The type - */ - protected static void pushCastToType(GeneratorAdapter ga, TypedElement type) { - pushCastToType(ga, JavaModelUtils.getTypeReference(type)); - } - - /** - * @param ga The {@link MethodVisitor} - * @param to The type - */ - protected static void pushCastFromObjectToType(GeneratorAdapter ga, TypedElement to) { - Type toType = JavaModelUtils.getTypeReference(to); - if (JavaModelUtils.isPrimitive(toType)) { - ga.unbox(toType); - } else if (!to.getName().equals(Object.class.getName())) { - ga.checkCast(toType); - } - } - - /** - * Cast from one type to another. - * - * @param ga The {@link MethodVisitor} - * @param from The from type - * @param to The to type - */ - protected static void pushCastToType(GeneratorAdapter ga, TypedElement from, TypedElement to) { - Type toType = JavaModelUtils.getTypeReference(to); - if (from.isPrimitive() && to.isPrimitive()) { - if (!from.getType().equals(to.getType())) { - ga.cast(JavaModelUtils.getTypeReference(from), toType); - } - } else if (from.isPrimitive()) { - ga.box(JavaModelUtils.getTypeReference(from)); - } else if (!to.getType().getName().equals(Object.class.getName())) { - pushCastToType(ga, toType); - } - } - - /** - * @param ga The {@link MethodVisitor} - * @param type The type - */ - protected static void pushCastToType(GeneratorAdapter ga, Class type) { - pushCastToType(ga, Type.getType(type)); - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param methodName The method name - * @param argumentTypes The argument types - */ - protected static void pushMethodNameAndTypesArguments(GeneratorAdapter methodVisitor, String methodName, Collection argumentTypes) { - // and the method name - methodVisitor.push(methodName); - pushNewArray(methodVisitor, Class.class, argumentTypes, item -> pushType(methodVisitor, item)); - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param arrayType The array class - * @param collection The collection - * @param itemConsumer The item consumer - * @param The type - */ - protected static void pushNewArray(GeneratorAdapter methodVisitor, - Class arrayType, - Collection collection, - Consumer itemConsumer) { - final Type type = Type.getType(arrayType); - // the size of the array - int size = collection.size(); - methodVisitor.push(size); - // define the array - methodVisitor.newArray(type); - // add a reference to the array on the stack - if (size > 0) { - methodVisitor.dup(); - int index = 0; - for (T item : collection) { - // the array index position - methodVisitor.push(index); - // Push value - itemConsumer.accept(item); - // store the value in the position - methodVisitor.arrayStore(type); - if (index != (size - 1)) { - // if we are not at the end of the array duplicate array onto the stack - methodVisitor.dup(); - } - index++; - } - } - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param arrayType The array class - * @param collection The collection - * @param itemConsumer The item consumer - * @param The type - */ - protected static void pushNewArrayIndexed(GeneratorAdapter methodVisitor, - Class arrayType, - Collection collection, - BiConsumer itemConsumer) { - final Type type = Type.getType(arrayType); - // the size of the array - int size = collection.size(); - methodVisitor.push(size); - // define the array - methodVisitor.newArray(type); - // add a reference to the array on the stack - if (size > 0) { - methodVisitor.dup(); - int index = 0; - for (T item : collection) { - // the array index position - methodVisitor.push(index); - // Push value - itemConsumer.accept(index, item); - // store the value in the position - methodVisitor.arrayStore(type); - if (index != (size - 1)) { - // if we are not at the end of the array duplicate array onto the stack - methodVisitor.dup(); - } - index++; - } - } - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param arrayType The array class - * @param array The collection - * @param itemConsumer The item consumer - * @param The type - */ - protected static void pushNewArray(GeneratorAdapter methodVisitor, - Class arrayType, - T[] array, - Consumer itemConsumer) { - pushNewArray(methodVisitor, arrayType, Arrays.asList(array), itemConsumer); - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param arrayType The array class - * @param size The size - */ - protected static void pushNewArray(GeneratorAdapter methodVisitor, Class arrayType, int size) { - final Type t = Type.getType(arrayType); - pushNewArray(methodVisitor, t, size); - } - - /** - * @param methodVisitor The method visitor as {@link org.objectweb.asm.commons.GeneratorAdapter} - * @param arrayType The array class - * @param size The size - */ - protected static void pushNewArray(GeneratorAdapter methodVisitor, Type arrayType, int size) { - // the size of the array - methodVisitor.push(size); - // define the array - methodVisitor.newArray(arrayType); - // add a reference to the array on the stack - if (size > 0) { - methodVisitor.dup(); - } - } - - /** - * @param methodVisitor The method visitor as {@link org.objectweb.asm.commons.GeneratorAdapter} - * @param arrayType The array class - * @param size The size - * @param itemConsumer The item consumer - */ - protected static void pushNewArray(GeneratorAdapter methodVisitor, Type arrayType, int size, Consumer itemConsumer) { - // the size of the array - methodVisitor.push(size); - // define the array - methodVisitor.newArray(arrayType); - // add a reference to the array on the stack - if (size > 0) { - methodVisitor.dup(); - for (int i = 0; i < size; i++) { - // the array index position - methodVisitor.push(i); - // Push value - itemConsumer.accept(i); - // store the value in the position - methodVisitor.arrayStore(arrayType); - if (i != (size - 1)) { - // if we are not at the end of the array duplicate array onto the stack - methodVisitor.dup(); - } - } - } - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param index The index - * @param size The size - * @param runnable The runnable - */ - protected static void pushStoreInArray(GeneratorAdapter methodVisitor, int index, int size, Runnable runnable) { - pushStoreInArray(methodVisitor, TYPE_OBJECT, index, size, runnable); - } - - /** - * @param methodVisitor The method visitor as {@link GeneratorAdapter} - * @param type The type of the array - * @param index The index - * @param size The size - * @param runnable The runnable - */ - protected static void pushStoreInArray(GeneratorAdapter methodVisitor, Type type, int index, int size, Runnable runnable) { - // the array index position - methodVisitor.push(index); - // load the constant string - runnable.run(); - // store the string in the position - methodVisitor.arrayStore(type); - if (index != (size - 1)) { - // if we are not at the end of the array duplicate array onto the stack - methodVisitor.dup(); - } - } - - private static void pushType(GeneratorAdapter methodVisitor, ClassElement type) { - if (type.isPrimitive()) { - Class typeClass = ClassUtils.getPrimitiveType(type.getName()).orElse(null); - if (typeClass != null) { - if (type.isArray()) { - Type arrayType = Type.getType(Array.newInstance(typeClass, 0).getClass()); - methodVisitor.push(arrayType); - } else { - Type wrapperType = Type.getType(ReflectionUtils.getWrapperType(typeClass)); - methodVisitor.getStatic(wrapperType, "TYPE", Type.getType(Class.class)); - } - } else { - methodVisitor.push(JavaModelUtils.getTypeReference(type)); - } - } else { - methodVisitor.push(JavaModelUtils.getTypeReference(type)); - } - } - - /** - * @param type The type - * @return The {@link Type} for the object type - */ - protected static Type getObjectType(Object type) { - if (type instanceof TypedElement element) { - String name = element.getType().getName(); - String internalName = getTypeDescriptor(name); - return Type.getType(internalName); - } else if (type instanceof Class class1) { - return Type.getType(class1); - } else if (type instanceof String) { - String className = type.toString(); - - String internalName = getTypeDescriptor(className); - return Type.getType(internalName); - } else { - throw new IllegalArgumentException("Type reference [" + type + "] should be a Class or a String representing the class name"); - } - } - - /** - * @param className The class name - * @param genericTypes The generic types - * @return The type descriptor as String - */ - protected static String getTypeDescriptor(String className, String... genericTypes) { - if (JavaModelUtils.NAME_TO_TYPE_MAP.containsKey(className)) { - return JavaModelUtils.NAME_TO_TYPE_MAP.get(className); - } else { - String internalName = getInternalName(className); - StringBuilder start = new StringBuilder(40); - Matcher matcher = ARRAY_PATTERN.matcher(className); - if (matcher.find()) { - int dimensions = matcher.group(0).length() / 2; - for (int i = 0; i < dimensions; i++) { - start.append('['); - } - } - start.append('L').append(internalName); - if (genericTypes != null && genericTypes.length > 0) { - start.append('<'); - for (String genericType : genericTypes) { - start.append(getTypeDescriptor(genericType)); - } - start.append('>'); - } - return start.append(';').toString(); - } - } - - /** - * @param returnType The return type - * @param argumentTypes The argument types - * @return The method descriptor - */ - protected static String getMethodDescriptor(String returnType, String... argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (String argumentType : argumentTypes) { - builder.append(getTypeDescriptor(argumentType)); - } - - builder.append(')'); - - builder.append(getTypeDescriptor(returnType)); - return builder.toString(); - } - - /** - * @param returnType The return type - * @param argumentTypes The argument types - * @return The method descriptor - */ - protected static String getMethodDescriptor(TypedElement returnType, Collection argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (TypedElement argumentType : argumentTypes) { - builder.append(getTypeDescriptor(argumentType)); - } - - builder.append(')'); - - builder.append(getTypeDescriptor(returnType)); - return builder.toString(); - } - - /** - * @param returnType The return type - * @param argumentTypes The argument types - * @return The method descriptor - */ - protected static String getMethodDescriptorForReturnType(Type returnType, Collection argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (TypedElement argumentType : argumentTypes) { - builder.append(getTypeDescriptor(argumentType)); - } - - builder.append(')'); - - builder.append(returnType.getDescriptor()); - return builder.toString(); - } - - /** - * @param returnType The return type - * @param argumentTypes The argument types - * @return The method descriptor - */ - protected static String getMethodDescriptor(Class returnType, Collection> argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (Class argumentType : argumentTypes) { - builder.append(Type.getDescriptor(argumentType)); - } - - builder.append(')'); - - builder.append(Type.getDescriptor(returnType)); - return builder.toString(); - } - - /** - * @param returnType The return type - * @param argumentTypes The argument types - * @return The method descriptor - */ - protected static String getMethodDescriptor(Type returnType, Collection argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (Type argumentType : argumentTypes) { - builder.append(argumentType.getDescriptor()); - } - - builder.append(')'); - - builder.append(returnType.getDescriptor()); - return builder.toString(); - } - - /** - * @param returnTypeReference The return type reference - * @param argReferenceTypes The argument reference types - * @return The method signature - */ - protected static String getMethodSignature(String returnTypeReference, String... argReferenceTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (String argumentType : argReferenceTypes) { - builder.append(argumentType); - } - - builder.append(')'); - - builder.append(returnTypeReference); - return builder.toString(); - } - - /** - * @param argumentTypes The argument types - * @return The constructor descriptor - */ - protected static String getConstructorDescriptor(Class... argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (Class argumentType : argumentTypes) { - builder.append(getTypeDescriptor(argumentType)); - } - - return builder.append(")V").toString(); - } - - /** - * @param argumentTypes The argument types - * @return The constructor descriptor - */ - protected static String getConstructorDescriptor(Type[] argumentTypes) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (Type argumentType : argumentTypes) { - builder.append(argumentType.getDescriptor()); - } - - return builder.append(")V").toString(); - } - - /** - * @param argList The argument list - * @return The constructor descriptor - */ - protected static String getConstructorDescriptor(Collection argList) { - StringBuilder builder = new StringBuilder(); - builder.append('('); - - for (ParameterElement argumentType : argList) { - builder.append(getTypeDescriptor(argumentType)); - } - - return builder.append(")V").toString(); - } - - /** - * @param out The output stream - * @param classWriter The current class writer - * @throws IOException if there is a problem writing the class to disk - */ - protected void writeClassToDisk(OutputStream out, ClassWriter classWriter) throws IOException { - byte[] bytes = classWriter.toByteArray(); - out.write(bytes); - } - - /** - * @param classWriter The current class writer - * @return The {@link GeneratorAdapter} for the constructor - */ - protected GeneratorAdapter startConstructor(ClassVisitor classWriter) { - MethodVisitor defaultConstructor = classWriter.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR, null, null); - return new GeneratorAdapter(defaultConstructor, ACC_PUBLIC, CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR); - } - - /** - * @param classWriter The current class writer - * @param argumentTypes The argument types - * @return The {@link GeneratorAdapter} for the constructor - */ - protected GeneratorAdapter startConstructor(ClassVisitor classWriter, Class... argumentTypes) { - String descriptor = getConstructorDescriptor(argumentTypes); - return new GeneratorAdapter(classWriter.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, descriptor, null, null), ACC_PUBLIC, CONSTRUCTOR_NAME, descriptor); - } - - /** - * @param classWriter The current class writer - * @param className The class name - * @param superType The super type - */ - protected void startClass(ClassVisitor classWriter, String className, Type superType) { - classWriter.visit(V17, ACC_SYNTHETIC, className, null, superType.getInternalName(), null); - classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - } - - /** - * @param classWriter The current class writer - * @param className The class name - * @param superType The super type - */ - protected void startPublicClass(ClassVisitor classWriter, String className, Type superType) { - classWriter.visit(V17, ACC_PUBLIC | ACC_SYNTHETIC, className, null, superType.getInternalName(), null); - classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - } - - /** - * @param classWriter The current class writer - * @param serviceType The service type - * @param internalClassName The class name - * @param superType The super type - */ - protected void startService(ClassVisitor classWriter, Class serviceType, String internalClassName, Type superType) { - startService(classWriter, serviceType.getName(), internalClassName, superType); - } - - /** - * @param classWriter The current class writer - * @param serviceName The service name - * @param internalClassName The class name - * @param superType The super type - * @param interfaces The interfaces - */ - protected void startService(ClassVisitor classWriter, String serviceName, String internalClassName, Type superType, String... interfaces) { - classWriter.visit(V17, ACC_PUBLIC | ACC_FINAL | ACC_SYNTHETIC, internalClassName, null, superType.getInternalName(), interfaces); - annotateAsGeneratedAndService(classWriter, serviceName); - } - - protected final void annotateAsGeneratedAndService(ClassVisitor classWriter, String serviceName) { - AnnotationVisitor annotationVisitor = classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - annotationVisitor.visit("service", serviceName); - annotationVisitor.visitEnd(); - } - - /** - * @param cv The constructor visitor - * @param superClass The super class - * @param argumentTypes The argument types - */ - protected void invokeConstructor(GeneratorAdapter cv, Class superClass, Class... argumentTypes) { - cv.invokeConstructor(Type.getType(superClass), new Method(CONSTRUCTOR_NAME, getConstructorDescriptor(argumentTypes))); - } - - /** - * @param visitor The interface visitor - * @param targetType The target type - * @param method The method - */ - protected static void invokeInterfaceStaticMethod(MethodVisitor visitor, Class targetType, Method method) { - Type type = Type.getType(targetType); - String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() - : type.getInternalName(); - visitor.visitMethodInsn(Opcodes.INVOKESTATIC, owner, method.getName(), - method.getDescriptor(), true); - } - - /** - * @param classWriter The current class writer - * @param returnType The return type - * @param methodName The method name - * @return TheThe {@link GeneratorAdapter} for the method - */ - protected GeneratorAdapter startPublicMethodZeroArgs(ClassWriter classWriter, Class returnType, String methodName) { - Type methodType = Type.getMethodType(Type.getType(returnType)); - - return new GeneratorAdapter(classWriter.visitMethod(ACC_PUBLIC, methodName, methodType.getDescriptor(), null, null), ACC_PUBLIC, methodName, methodType.getDescriptor()); - } - - /** - * @param classWriter The current class writer - * @param returnType The return type - * @param methodName The method name - * @return TheThe {@link GeneratorAdapter} for the method - */ - protected GeneratorAdapter startPublicFinalMethodZeroArgs(ClassWriter classWriter, Class returnType, String methodName) { - Type methodType = Type.getMethodType(Type.getType(returnType)); - - return new GeneratorAdapter( - classWriter.visitMethod( - ACC_PUBLIC | ACC_FINAL, - methodName, - methodType.getDescriptor(), - null, - null - ), ACC_PUBLIC, methodName, methodType.getDescriptor()); - } - - /** - * @param className The class name - * @return The internal name - */ - protected static String getInternalName(String className) { - String newClassName = className.replace('.', '/'); - Matcher matcher = ARRAY_PATTERN.matcher(newClassName); - if (matcher.find()) { - newClassName = matcher.replaceFirst(""); - } - return newClassName; - } - - /** - * @param compilationDir The compilation directory - * @return The directory class writer output visitor - */ - protected ClassWriterOutputVisitor newClassWriterOutputVisitor(File compilationDir) { - return new DirectoryClassWriterOutputVisitor(compilationDir); - } - - /** - * @param classWriter The current class writer - * @return The {@link GeneratorAdapter} - */ - protected GeneratorAdapter visitStaticInitializer(ClassVisitor classWriter) { - MethodVisitor mv = classWriter.visitMethod(ACC_STATIC, "", DESCRIPTOR_DEFAULT_CONSTRUCTOR, null, null); - return new GeneratorAdapter(mv, ACC_STATIC, "", DESCRIPTOR_DEFAULT_CONSTRUCTOR); - } - - /** - * @param writer The class writer - * @param methodName The method name - * @param returnType The return type - * @param argumentTypes The argument types - * @return The {@link GeneratorAdapter} - */ - protected GeneratorAdapter startPublicMethod(ClassWriter writer, String methodName, String returnType, String... argumentTypes) { - return new GeneratorAdapter(writer.visitMethod( - ACC_PUBLIC, - methodName, - getMethodDescriptor(returnType, argumentTypes), - null, - null - ), ACC_PUBLIC, - methodName, - getMethodDescriptor(returnType, argumentTypes)); - } - - /** - * @param writer The class writer - * @param methodName The method name - * @param returnType The return type - * @param argumentTypes The argument types - * @return The {@link GeneratorAdapter} - */ - protected GeneratorAdapter startPublicMethod(ClassWriter writer, String methodName, Class returnType, Class... argumentTypes) { - return new GeneratorAdapter(writer.visitMethod( - ACC_PUBLIC, - methodName, - getMethodDescriptor(returnType, List.of(argumentTypes)), - null, - null - ), ACC_PUBLIC, - methodName, - getMethodDescriptor(returnType, List.of(argumentTypes))); - } - - /** - * @param writer The class writer - * @param asmMethod The asm method - * @return The {@link GeneratorAdapter} - * @since 2.3.0 - */ - protected GeneratorAdapter startPublicMethod(ClassWriter writer, Method asmMethod) { - String methodName = asmMethod.getName(); - return new GeneratorAdapter(writer.visitMethod( - ACC_PUBLIC, - methodName, - asmMethod.getDescriptor(), - null, - null - ), ACC_PUBLIC, - methodName, - asmMethod.getDescriptor()); - } - - /** - * @param writer The class writer - * @param methodName The method name - * @param returnType The return type - * @param argumentTypes The argument types - * @return The {@link GeneratorAdapter} - */ - protected GeneratorAdapter startProtectedMethod(ClassWriter writer, String methodName, String returnType, String... argumentTypes) { - return new GeneratorAdapter(writer.visitMethod( - ACC_PROTECTED, - methodName, - getMethodDescriptor(returnType, argumentTypes), - null, - null - ), ACC_PROTECTED, - methodName, - getMethodDescriptor(returnType, argumentTypes)); - } - - /** - * Push the instantiation of the given type. - * - * @param generatorAdapter The generator adaptor - * @param typeToInstantiate The type to instantiate. - */ - protected void pushNewInstance(GeneratorAdapter generatorAdapter, Type typeToInstantiate) { - generatorAdapter.newInstance(typeToInstantiate); - generatorAdapter.dup(); - generatorAdapter.invokeConstructor(typeToInstantiate, METHOD_DEFAULT_CONSTRUCTOR); - } - - public static void pushStringMapOf(GeneratorAdapter generatorAdapter, - Map annotationData, - boolean skipEmpty, - T empty, - Consumer pushValue) { - Set> entrySet = annotationData != null ? annotationData.entrySet() - .stream() - .filter(e -> !skipEmpty || (e.getKey() != null && isSupportedMapValue(e.getValue()))) - .map(e -> e.getValue() == null && empty != null ? new AbstractMap.SimpleEntry<>(e.getKey().toString(), empty) : new AbstractMap.SimpleEntry<>(e.getKey().toString(), e.getValue())) - .collect(Collectors.toCollection(() -> new TreeSet<>(Map.Entry.comparingByKey()))) : null; - if (entrySet == null || entrySet.isEmpty()) { - invokeInterfaceStatic(generatorAdapter, MAP_TYPE, MAP_OF[0]); - return; - } - if (entrySet.size() < MAP_OF.length) { - for (Map.Entry entry : entrySet) { - generatorAdapter.push(entry.getKey()); - pushValue.accept(entry.getValue()); - } - invokeInterfaceStatic(generatorAdapter, MAP_TYPE, MAP_OF[entrySet.size()]); - } else { - // start a new array - pushNewArray(generatorAdapter, Map.Entry.class, entrySet, entry -> { - generatorAdapter.push(entry.getKey()); - pushValue.accept(entry.getValue()); - invokeInterfaceStatic(generatorAdapter, MAP_TYPE, MAP_ENTRY); - }); - invokeInterfaceStatic(generatorAdapter, MAP_TYPE, MAP_BY_ARRAY); - } - } - - public static void pushListOfString(GeneratorAdapter methodVisitor, List names) { - if (names != null) { - names = names.stream().filter(Objects::nonNull).toList(); - } - if (names == null || names.isEmpty()) { - invokeInterfaceStatic(methodVisitor, LIST_TYPE, LIST_OF[0]); - return; - } - if (names.size() < LIST_OF.length) { - for (String name : names) { - methodVisitor.push(name); - } - invokeInterfaceStatic(methodVisitor, LIST_TYPE, LIST_OF[names.size()]); - } else { - pushNewArray(methodVisitor, String.class, names, methodVisitor::push); - invokeInterfaceStatic(methodVisitor, LIST_TYPE, LIST_BY_ARRAY); - } - } - - private static void invokeInterfaceStatic(GeneratorAdapter methodVisitor, Type type, org.objectweb.asm.commons.Method method) { - methodVisitor.visitMethodInsn(INVOKESTATIC, type.getInternalName(), method.getName(), method.getDescriptor(), true); - } - - /** - * @param p The class element - * @return The string representation - */ - protected static String toTypeString(ClassElement p) { - String name = p.getName(); - if (p.isArray()) { - return name + IntStream.range(0, p.getArrayDimensions()).mapToObj(ignore -> "[]").collect(Collectors.joining()); - } - return name; - } - -} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java new file mode 100644 index 00000000000..be65420bbc4 --- /dev/null +++ b/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java @@ -0,0 +1,631 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.inject.writer; + +import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.AnnotationUtil; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.reflect.ReflectionUtils; +import io.micronaut.core.type.Argument; +import io.micronaut.core.util.CollectionUtils; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; +import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; +import io.micronaut.inject.annotation.AnnotationMetadataReference; +import io.micronaut.inject.annotation.MutableAnnotationMetadata; +import io.micronaut.inject.ast.ArrayableClassElement; +import io.micronaut.inject.ast.ClassElement; +import io.micronaut.inject.ast.GenericPlaceholderElement; +import io.micronaut.inject.ast.KotlinParameterElement; +import io.micronaut.inject.ast.ParameterElement; +import io.micronaut.inject.ast.TypedElement; +import io.micronaut.inject.ast.WildcardElement; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.TypeDef; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * The argument expression utils. + * + * @author Denis Stepanov + * @since 4.8 + */ +@Internal +public final class ArgumentExpUtils { + + public static final ClassTypeDef TYPE_ARGUMENT = ClassTypeDef.of(Argument.class); + public static final TypeDef.Array TYPE_ARGUMENT_ARRAY = TYPE_ARGUMENT.array(); + public static final Method METHOD_CREATE_ARGUMENT_SIMPLE = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "of", + Class.class, + String.class + ); + + private static final String ZERO_ARGUMENTS_CONSTANT = "ZERO_ARGUMENTS"; + + private static final Method METHOD_GENERIC_PLACEHOLDER_SIMPLE = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "ofTypeVariable", + Class.class, + String.class, + String.class + ); + + private static final Method METHOD_CREATE_TYPE_VARIABLE_SIMPLE = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "ofTypeVariable", + Class.class, + String.class + ); + + private static final Method METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "of", + Class.class, + String.class, + AnnotationMetadata.class, + Argument[].class + ); + + private static final Method METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "ofTypeVariable", + Class.class, + String.class, + AnnotationMetadata.class, + Argument[].class + ); + + private static final Method METHOD_CREATE_GENERIC_PLACEHOLDER_WITH_ANNOTATION_METADATA_GENERICS = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "ofTypeVariable", + Class.class, + String.class, + String.class, + AnnotationMetadata.class, + Argument[].class + ); + + private static final Method METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_CLASS_GENERICS = ReflectionUtils.getRequiredInternalMethod( + Argument.class, + "of", + Class.class, + AnnotationMetadata.class, + Class[].class + ); + + /** + * Creates an argument. + * + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param owningType The owning type + * @param declaringType The declaring type name + * @param argument The argument + * @param loadTypeMethods The load type methods + * @return The expression + */ + public static ExpressionDef pushReturnTypeArgument(AnnotationMetadata annotationMetadataWithDefaults, + ClassTypeDef owningType, + ClassElement declaringType, + ClassElement argument, + Map loadTypeMethods) { + // Persist only type annotations added + AnnotationMetadata annotationMetadata = argument.getTypeAnnotationMetadata(); + + if (argument.isVoid()) { + return TYPE_ARGUMENT.getStaticField("VOID", TYPE_ARGUMENT); + } + if (argument.isPrimitive() && !argument.isArray()) { + String constantName = argument.getName().toUpperCase(Locale.ENGLISH); + // refer to constant for primitives + return TYPE_ARGUMENT.getStaticField(constantName, TYPE_ARGUMENT); + } + + if (annotationMetadata.isEmpty() + && !argument.isArray() + && String.class.getName().equals(argument.getType().getName()) + && argument.getName().equals(argument.getType().getName()) + && argument.getAnnotationMetadata().isEmpty()) { + return TYPE_ARGUMENT.getStaticField("STRING", TYPE_ARGUMENT); + } + + return pushCreateArgument( + annotationMetadataWithDefaults, + declaringType, + owningType, + argument.getName(), + argument, + annotationMetadata, + argument.getTypeArguments(), + loadTypeMethods + ); + } + + /** + * Create a new Argument creation. + * + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param declaringType The declaring type name + * @param owningType The owning type + * @param argumentName The argument name + * @param argument The argument + * @param loadTypeMethods The load type methods + * @return The expression + */ + public static ExpressionDef pushCreateArgument( + AnnotationMetadata annotationMetadataWithDefaults, + ClassElement declaringType, + ClassTypeDef owningType, + String argumentName, + ClassElement argument, + Map loadTypeMethods) { + + return pushCreateArgument( + annotationMetadataWithDefaults, + declaringType, + owningType, + argumentName, + argument, + argument.getAnnotationMetadata(), + argument.getTypeArguments(), + loadTypeMethods + ); + } + + /** + * Creates a new Argument creation. + * + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param declaringType The declaring type name + * @param owningType The owning type + * @param argumentName The argument name + * @param argumentType The argument type + * @param annotationMetadata The annotation metadata + * @param typeArguments The type arguments + * @param loadTypeMethods The load type methods + * @return The expression + */ + static ExpressionDef pushCreateArgument( + AnnotationMetadata annotationMetadataWithDefaults, + ClassElement declaringType, + ClassTypeDef owningType, + String argumentName, + TypedElement argumentType, + AnnotationMetadata annotationMetadata, + Map typeArguments, + Map loadTypeMethods) { + annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); + ExpressionDef.Constant argumentTypeConstant = ExpressionDef.constant(TypeDef.erasure(resolveArgument(argumentType))); + + boolean hasAnnotations = !annotationMetadata.isEmpty(); + boolean hasTypeArguments = typeArguments != null && !typeArguments.isEmpty(); + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + // Persist resolved placeholder for backward compatibility + argumentType = placeholderElement.getResolved().orElse(placeholderElement); + } + boolean isGenericPlaceholder = argumentType instanceof GenericPlaceholderElement; + boolean isTypeVariable = isGenericPlaceholder || ((argumentType instanceof ClassElement classElement) && classElement.isTypeVariable()); + String variableName = argumentName; + if (isGenericPlaceholder) { + variableName = ((GenericPlaceholderElement) argumentType).getVariableName(); + } + boolean hasVariableName = !variableName.equals(argumentName); + + List values = new ArrayList<>(); + + // 1st argument: The type + values.add(argumentTypeConstant); + // 2nd argument: The argument name + values.add(ExpressionDef.constant(argumentName)); + + if (!hasAnnotations && !hasTypeArguments && !isTypeVariable) { + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_ARGUMENT_SIMPLE, + values.stream().toList() + ); + } + + if (isTypeVariable && hasVariableName) { + values.add(ExpressionDef.constant(variableName)); + } + + // 3rd argument: The annotation metadata + if (hasAnnotations) { + MutableAnnotationMetadata.contributeDefaults( + annotationMetadataWithDefaults, + annotationMetadata + ); + + values.add(AnnotationMetadataGenUtils.instantiateNewMetadata( + owningType, + (MutableAnnotationMetadata) annotationMetadata, + loadTypeMethods + )); + } else { + values.add(ExpressionDef.nullValue()); + } + + // 4th argument: The generic types + if (hasTypeArguments) { + values.add(pushTypeArgumentElements( + annotationMetadataWithDefaults, + owningType, + declaringType, + typeArguments, + loadTypeMethods + )); + } else { + values.add(ExpressionDef.nullValue()); + } + + if (isTypeVariable) { + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + hasVariableName ? METHOD_CREATE_GENERIC_PLACEHOLDER_WITH_ANNOTATION_METADATA_GENERICS : METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS, + values + ); + } else { + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS, + values + ); + } + } + + private static TypedElement resolveArgument(TypedElement argumentType) { + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + ClassElement resolved = placeholderElement.getResolved().orElse( + placeholderElement.getBounds().get(0) + ); + TypedElement typedElement = resolveArgument( + resolved + ); + if (argumentType.isArray()) { + if (typedElement instanceof ArrayableClassElement arrayableClassElement) { + return arrayableClassElement.withArrayDimensions(argumentType.getArrayDimensions()); + } + return typedElement; + } + return typedElement; + } + if (argumentType instanceof WildcardElement wildcardElement) { + return resolveArgument( + wildcardElement.getResolved().orElseGet(() -> { + if (!wildcardElement.getLowerBounds().isEmpty()) { + return wildcardElement.getLowerBounds().get(0); + } + if (!wildcardElement.getUpperBounds().isEmpty()) { + return wildcardElement.getUpperBounds().get(0); + } + return ClassElement.of(Object.class); + } + ) + ); + } + return argumentType; + } + + /** + * Creates type arguments onto the stack. + * + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param owningType The owning type + * @param declaringType The declaring class element of the generics + * @param types The type references + * @param loadTypeMethods The load type methods + * @return The expression + */ + static ExpressionDef pushTypeArgumentElements( + AnnotationMetadata annotationMetadataWithDefaults, + ClassTypeDef owningType, + ClassElement declaringType, + Map types, + Map loadTypeMethods) { + if (types == null || types.isEmpty()) { + return TYPE_ARGUMENT_ARRAY.instantiate(); + } + return pushTypeArgumentElements( + annotationMetadataWithDefaults, + owningType, + declaringType, + null, + types, + new HashSet<>(5), + loadTypeMethods); + } + + @SuppressWarnings("java:S1872") + private static ExpressionDef pushTypeArgumentElements( + AnnotationMetadata annotationMetadataWithDefaults, + ClassTypeDef owningType, + ClassElement declaringType, + @Nullable + ClassElement element, + Map types, + Set visitedTypes, + Map loadTypeMethods) { + if (element == null) { + if (visitedTypes.contains(declaringType.getName())) { + return TYPE_ARGUMENT.getStaticField(ZERO_ARGUMENTS_CONSTANT, TYPE_ARGUMENT_ARRAY); + } else { + visitedTypes.add(declaringType.getName()); + } + } + + return TYPE_ARGUMENT_ARRAY.instantiate(types.entrySet().stream().map(entry -> { + String argumentName = entry.getKey(); + ClassElement classElement = entry.getValue(); + Map typeArguments = classElement.getTypeArguments(); + if (CollectionUtils.isNotEmpty(typeArguments) || !classElement.getAnnotationMetadata().isEmpty()) { + return buildArgumentWithGenerics( + annotationMetadataWithDefaults, + owningType, + argumentName, + classElement, + typeArguments, + visitedTypes, + loadTypeMethods + ); + } + return buildArgument(argumentName, classElement); + }).toList()); + } + + /** + * Builds generic type arguments recursively. + * + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param owningType The owning type + * @param argumentName The argument name + * @param argumentType The argument type + * @param typeArguments The nested type arguments + * @param visitedTypes The visited types + * @param loadTypeMethods The load type methods + * @return The expression + */ + static ExpressionDef buildArgumentWithGenerics( + AnnotationMetadata annotationMetadataWithDefaults, + ClassTypeDef owningType, + String argumentName, + ClassElement argumentType, + Map typeArguments, + Set visitedTypes, + Map loadTypeMethods) { + ExpressionDef.Constant argumentTypeConstant = ExpressionDef.constant(TypeDef.erasure(resolveArgument(argumentType))); + + List values = new ArrayList<>(); + + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + // Persist resolved placeholder for backward compatibility + argumentType = placeholderElement.getResolved().orElse(argumentType); + } + + // Persist only type annotations added to the type argument + AnnotationMetadata annotationMetadata = MutableAnnotationMetadata.of(argumentType.getTypeAnnotationMetadata()); + boolean hasAnnotationMetadata = !annotationMetadata.isEmpty(); + + boolean isRecursiveType = false; + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + // Prevent placeholder recursion + Object genericNativeType = placeholderElement.getGenericNativeType(); + if (visitedTypes.contains(genericNativeType)) { + isRecursiveType = true; + } else { + visitedTypes.add(genericNativeType); + } + } + + boolean typeVariable = argumentType.isTypeVariable(); + + // 1st argument: the type + values.add(argumentTypeConstant); + // 2nd argument: the name + values.add(ExpressionDef.constant(argumentName)); + + + if (isRecursiveType || !typeVariable && !hasAnnotationMetadata && typeArguments.isEmpty()) { + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_ARGUMENT_SIMPLE, + values + ); + } + + // 3rd argument: annotation metadata + if (hasAnnotationMetadata) { + MutableAnnotationMetadata.contributeDefaults( + annotationMetadataWithDefaults, + annotationMetadata + ); + + values.add( + AnnotationMetadataGenUtils.instantiateNewMetadata( + owningType, + (MutableAnnotationMetadata) annotationMetadata, + loadTypeMethods + ) + ); + } else { + values.add(ExpressionDef.nullValue()); + } + + // 4th argument, more generics + values.add( + pushTypeArgumentElements( + annotationMetadataWithDefaults, + owningType, + argumentType, + argumentType, + typeArguments, + visitedTypes, + loadTypeMethods + ) + ); + + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + typeVariable ? METHOD_CREATE_TYPE_VAR_WITH_ANNOTATION_METADATA_GENERICS : METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_GENERICS, + values + ); + } + + /** + * Builds an argument instance. + * + * @param argumentName The argument name + * @param argumentType The argument type + * @return The expression + */ + private static ExpressionDef buildArgument(String argumentName, ClassElement argumentType) { + ExpressionDef.Constant argumentTypeConstant = ExpressionDef.constant(TypeDef.erasure(resolveArgument(argumentType))); + ExpressionDef.Constant argumentNameConstant = ExpressionDef.constant(argumentName); + + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + // Persist resolved placeholder for backward compatibility + argumentType = placeholderElement.getResolved().orElse(placeholderElement); + } + + if (argumentType instanceof GenericPlaceholderElement || argumentType.isTypeVariable()) { + String variableName = argumentName; + if (argumentType instanceof GenericPlaceholderElement placeholderElement) { + variableName = placeholderElement.getVariableName(); + } + boolean hasVariable = !variableName.equals(argumentName); + if (hasVariable) { + return TYPE_ARGUMENT.invokeStatic( + METHOD_GENERIC_PLACEHOLDER_SIMPLE, + + // 1st argument: the type + argumentTypeConstant, + // 2nd argument: the name + argumentNameConstant, + // 3nd argument: the variable + ExpressionDef.constant(variableName) + ); + } + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_TYPE_VARIABLE_SIMPLE, + // 1st argument: the type + argumentTypeConstant, + // 2nd argument: the name + argumentNameConstant + ); + } + // Argument.create( .. ) + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_ARGUMENT_SIMPLE, + // 1st argument: the type + argumentTypeConstant, + // 2nd argument: the name + argumentNameConstant + ); + } + + /** + * Builds generic type arguments recursively. + * + * @param type The type that declares the generics + * @param annotationMetadata The annotation metadata reference + * @param generics The generics + * @return The expression + */ + public static ExpressionDef buildArgumentWithGenerics(TypeDef type, + AnnotationMetadataReference annotationMetadata, + ClassElement[] generics) { + + return TYPE_ARGUMENT.invokeStatic( + METHOD_CREATE_ARGUMENT_WITH_ANNOTATION_METADATA_CLASS_GENERICS, + + // 1st argument: the type + ExpressionDef.constant(type), + // 2nd argument: the annotation metadata + AnnotationMetadataGenUtils.annotationMetadataReference(annotationMetadata), + // 3rd argument: generics + ClassTypeDef.of(Class.class).array().instantiate( + Arrays.stream(generics).map(g -> ExpressionDef.constant(TypeDef.erasure(g))).toList() + ) + ); + } + + /** + * @param annotationMetadataWithDefaults The annotation metadata with defaults + * @param declaringElement The declaring element name + * @param owningType The owning type + * @param argumentTypes The argument types + * @param loadTypeMethods The load type methods + * @return The expression + */ + public static ExpressionDef pushBuildArgumentsForMethod( + AnnotationMetadata annotationMetadataWithDefaults, + ClassElement declaringElement, + ClassTypeDef owningType, + Collection argumentTypes, + Map loadTypeMethods) { + + return TYPE_ARGUMENT_ARRAY.instantiate(argumentTypes.stream().map(parameterElement -> { + ClassElement genericType = parameterElement.getGenericType(); + + MutableAnnotationMetadata.contributeDefaults( + annotationMetadataWithDefaults, + parameterElement.getAnnotationMetadata() + ); + MutableAnnotationMetadata.contributeDefaults( + annotationMetadataWithDefaults, + genericType.getTypeAnnotationMetadata() + ); + + String argumentName = parameterElement.getName(); + MutableAnnotationMetadata annotationMetadata = new AnnotationMetadataHierarchy( + parameterElement.getAnnotationMetadata(), + genericType.getTypeAnnotationMetadata() + ).merge(); + + if (parameterElement instanceof KotlinParameterElement kp && kp.hasDefault()) { + annotationMetadata.removeAnnotation(AnnotationUtil.NON_NULL); + annotationMetadata.addAnnotation(AnnotationUtil.NULLABLE, Map.of()); + annotationMetadata.addDeclaredAnnotation(AnnotationUtil.NULLABLE, Map.of()); + } + + Map typeArguments = genericType.getTypeArguments(); + return pushCreateArgument( + annotationMetadataWithDefaults, + declaringElement, + owningType, + argumentName, + genericType, + annotationMetadata, + typeArguments, + loadTypeMethods + ); + }).toList()); + + } + +} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ArrayAwareSignatureWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/ArrayAwareSignatureWriter.java deleted file mode 100644 index 0884bc20007..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/writer/ArrayAwareSignatureWriter.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2017-2021 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.writer; - -import io.micronaut.core.annotation.Internal; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.signature.SignatureVisitor; - -/** - * Fork of {@link org.objectweb.asm.signature.SignatureWriter} which doesn't handle primitive array generics correctly. - * - * @author graemerocher - * @since 3.1.0 - */ -@Internal -final class ArrayAwareSignatureWriter extends SignatureVisitor { - - /** The builder used to construct the visited signature. */ - private final StringBuilder stringBuilder = new StringBuilder(); - - /** Whether the visited signature contains formal type parameters. */ - private boolean hasFormals; - - /** Whether the visited signature contains method parameter types. */ - private boolean hasParameters; - - /** - * The stack used to keep track of class types that have arguments. Each element of this stack is - * a boolean encoded in one bit. The top of the stack is the least significant bit. Pushing false - * = *2, pushing true = *2+1, popping = /2. - * - *

Class type arguments must be surrounded with '<' and '>' and, because - * - *

    - *
  1. class types can be nested (because type arguments can themselves be class types), - *
  2. SignatureWriter always returns 'this' in each visit* method (to avoid allocating new - * SignatureWriter instances), - *
- * - *

we need a stack to properly balance these 'parentheses'. A new element is pushed on this - * stack for each new visited type, and popped when the visit of this type ends (either is - * visitEnd, or because visitInnerClassType is called). - */ - private int argumentStack; - - /** Constructs a new {@link org.objectweb.asm.signature.SignatureWriter}. */ - public ArrayAwareSignatureWriter() { - super(Opcodes.ASM7); - } - - // ----------------------------------------------------------------------------------------------- - // Implementation of the SignatureVisitor interface - // ----------------------------------------------------------------------------------------------- - - @Override - public void visitFormalTypeParameter(final String name) { - if (!hasFormals) { - hasFormals = true; - stringBuilder.append('<'); - } - stringBuilder.append(name); - stringBuilder.append(':'); - } - - @Override - public SignatureVisitor visitClassBound() { - return this; - } - - @Override - public SignatureVisitor visitInterfaceBound() { - stringBuilder.append(':'); - return this; - } - - @Override - public SignatureVisitor visitSuperclass() { - endFormals(); - return this; - } - - @Override - public SignatureVisitor visitInterface() { - return this; - } - - @Override - public SignatureVisitor visitParameterType() { - endFormals(); - if (!hasParameters) { - hasParameters = true; - stringBuilder.append('('); - } - return this; - } - - @Override - public SignatureVisitor visitReturnType() { - endFormals(); - if (!hasParameters) { - stringBuilder.append('('); - } - stringBuilder.append(')'); - return this; - } - - @Override - public SignatureVisitor visitExceptionType() { - stringBuilder.append('^'); - return this; - } - - @Override - public void visitBaseType(final char descriptor) { - stringBuilder.append(descriptor); - // Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as - // we can tell at this point). - argumentStack *= 2; - } - - @Override - public void visitTypeVariable(final String name) { - stringBuilder.append('T'); - stringBuilder.append(name); - stringBuilder.append(';'); - } - - @Override - public SignatureVisitor visitArrayType() { - stringBuilder.append('['); - return this; - } - - @Override - public void visitClassType(final String name) { - stringBuilder.append('L'); - stringBuilder.append(name); - // Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as - // we can tell at this point). - argumentStack *= 2; - } - - @Override - public void visitInnerClassType(final String name) { - endArguments(); - stringBuilder.append('.'); - stringBuilder.append(name); - // Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as - // we can tell at this point). - argumentStack *= 2; - } - - @Override - public void visitTypeArgument() { - // If the top of the stack is 'false', this means we are visiting the first type argument of the - // currently visited type. We therefore need to append a '<', and to replace the top stack - // element with 'true' (meaning that the current type does have type arguments). - if (argumentStack % 2 == 0) { - argumentStack |= 1; - stringBuilder.append('<'); - } - stringBuilder.append('*'); - } - - @Override - public ArrayAwareSignatureWriter visitTypeArgument(final char wildcard) { - // If the top of the stack is 'false', this means we are visiting the first type argument of the - // currently visited type. We therefore need to append a '<', and to replace the top stack - // element with 'true' (meaning that the current type does have type arguments). - if (argumentStack % 2 == 0) { - argumentStack |= 1; - stringBuilder.append('<'); - } - if (wildcard != '=') { - stringBuilder.append(wildcard); - } - return this; - } - - /** - * Ends an array. - */ - public void visitEndArray() { - endArguments(); - } - - @Override - public void visitEnd() { - endArguments(); - stringBuilder.append(';'); - } - - /** - * Returns the signature that was built by this signature writer. - * - * @return the signature that was built by this signature writer. - */ - @Override - public String toString() { - return stringBuilder.toString(); - } - - // ----------------------------------------------------------------------------------------------- - // Utility methods - // ----------------------------------------------------------------------------------------------- - - /** Ends the formal type parameters section of the signature. */ - private void endFormals() { - if (hasFormals) { - hasFormals = false; - stringBuilder.append('>'); - } - } - - /** Ends the type arguments of a class or inner class type. */ - private void endArguments() { - // If the top of the stack is 'true', this means that some type arguments have been visited for - // the type whose visit is now ending. We therefore need to append a '>', and to pop one element - // from the stack. - if (argumentStack % 2 == 1) { - stringBuilder.append('>'); - } - argumentStack /= 2; - } -} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java index 1e032b78ca5..fb063009093 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java @@ -17,16 +17,29 @@ import io.micronaut.context.AbstractBeanConfiguration; import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.inject.BeanConfiguration; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; import io.micronaut.inject.ast.Element; -import io.micronaut.inject.visitor.VisitorContext; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; - +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.AnnotationDef; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; + +import javax.lang.model.element.Modifier; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.getAnnotationMetadataMethodDef; /** * Writes configuration classes for configuration packages using ASM. @@ -37,7 +50,7 @@ * @since 1.0 */ @Internal -public class BeanConfigurationWriter extends AbstractAnnotationMetadataWriter { +public class BeanConfigurationWriter implements ClassOutputWriter { /** * Suffix for generated configuration classes. @@ -45,77 +58,72 @@ public class BeanConfigurationWriter extends AbstractAnnotationMetadataWriter { public static final String CLASS_SUFFIX = "$BeanConfiguration"; private final String packageName; private final String configurationClassName; - private final String configurationClassInternalName; + private final Element originatingElement; + private final AnnotationMetadata annotationMetadata; /** * @param packageName The package name * @param originatingElement The originating element * @param annotationMetadata The annotation metadata - * @param visitorContext The visitor context */ public BeanConfigurationWriter( String packageName, Element originatingElement, - AnnotationMetadata annotationMetadata, - VisitorContext visitorContext) { - super(packageName + '.' + CLASS_SUFFIX, originatingElement, annotationMetadata, true, visitorContext); + AnnotationMetadata annotationMetadata) { this.packageName = packageName; - this.configurationClassName = targetClassType.getClassName(); - this.configurationClassInternalName = targetClassType.getInternalName(); + this.configurationClassName = packageName + '.' + CLASS_SUFFIX; + this.originatingElement = originatingElement; + this.annotationMetadata = annotationMetadata; } @Override public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException { - try (OutputStream outputStream = classWriterOutputVisitor.visitClass(configurationClassName, getOriginatingElements())) { - ClassWriter classWriter = generateClassBytes(); - outputStream.write(classWriter.toByteArray()); + try (OutputStream outputStream = classWriterOutputVisitor.visitClass(configurationClassName, originatingElement)) { + outputStream.write(generateClassBytes()); } classWriterOutputVisitor.visitServiceDescriptor( - BeanConfiguration.class, - configurationClassName, - getOriginatingElement() + BeanConfiguration.class, + configurationClassName, + originatingElement ); } - private ClassWriter generateClassBytes() { - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + private byte[] generateClassBytes() { + ClassTypeDef targetType = ClassTypeDef.of(configurationClassName); - try { - Class superType = AbstractBeanConfiguration.class; - Type beanConfigurationType = Type.getType(superType); + ClassDef.ClassDefBuilder configurationClassBuilder = ClassDef.builder(configurationClassName) + .superclass(ClassTypeDef.of(AbstractBeanConfiguration.class)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", BeanConfiguration.class.getName()).build()); - startService(classWriter, BeanConfiguration.class, configurationClassInternalName, beanConfigurationType); - writeAnnotationMetadataStaticInitializer(classWriter); + ClassDef configurationClass = configurationClassBuilder + .addMethod(MethodDef.constructor().addModifiers(Modifier.PUBLIC).build((aThis, methodParameters) + -> aThis.superRef().invokeConstructor(ExpressionDef.constant(packageName)))) + .addMethod(getAnnotationMetadataMethodDef(targetType, annotationMetadata)) + .build(); - writeConstructor(classWriter); - writeGetAnnotationMetadataMethod(classWriter); - } catch (NoSuchMethodException e) { - throw new ClassGenerationException("Error generating configuration class. Incompatible JVM or Micronaut version?: " + e.getMessage(), e); - } - for (GeneratorAdapter method : loadTypeMethods.values()) { - method.visitMaxs(3, 1); - method.visitEnd(); - } + Map loadTypeMethods = new LinkedHashMap<>(); + // write the static initializers for the annotation metadata + List staticInit = new ArrayList<>(); + AnnotationMetadataGenUtils.writeAnnotationDefault(staticInit, targetType, annotationMetadata, loadTypeMethods); - return classWriter; - } - - private void writeConstructor(ClassWriter classWriter) throws NoSuchMethodException { - GeneratorAdapter cv = startConstructor(classWriter); + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField( + targetType, + annotationMetadata, + loadTypeMethods + ); - // ALOAD 0 - cv.loadThis(); - // LDC "..package name.." - cv.push(packageName); + loadTypeMethods.values().forEach(configurationClassBuilder::addMethod); - // INVOKESPECIAL AbstractBeanConfiguration. (Ljava/lang/Package;)V - invokeConstructor(cv, AbstractBeanConfiguration.class, String.class); + if (annotationMetadataField != null) { + configurationClassBuilder.addField(annotationMetadataField); + if (!staticInit.isEmpty()) { + configurationClassBuilder.addStaticInitializer(StatementDef.multi(staticInit)); + } + } - // RETURN - cv.visitInsn(RETURN); - // MAXSTACK = 2 - // MAXLOCALS = 1 - cv.visitMaxs(2, 1); + return new ByteCodeWriter().write(configurationClass); } + } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java index 472847a8e5d..f3657b1f5e8 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java @@ -207,7 +207,9 @@ void visitDefaultConstructor( * @param compilationDir The compilation directory * @throws IOException If an I/O error occurs */ - void writeTo(File compilationDir) throws IOException; + default void writeTo(File compilationDir) throws IOException { + accept(new DirectoryClassWriterOutputVisitor(compilationDir)); + } /** * Write the class to output via a visitor that manages output destination. diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java index 3416e533b3f..c5b66c15565 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java @@ -15,6 +15,8 @@ */ package io.micronaut.inject.writer; +import io.micronaut.aop.chain.ConstructorInterceptorChain; +import io.micronaut.aop.chain.MethodInterceptorChain; import io.micronaut.aop.writer.AopProxyWriter; import io.micronaut.context.AbstractBeanDefinitionBeanConstructor; import io.micronaut.context.AbstractExecutableMethod; @@ -70,6 +72,7 @@ import io.micronaut.core.annotation.AnnotationUtil; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.AnnotationValueBuilder; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NextMajorVersion; import io.micronaut.core.annotation.NonNull; @@ -80,7 +83,7 @@ import io.micronaut.core.expressions.EvaluatedExpressionReference; import io.micronaut.core.naming.NameUtils; import io.micronaut.core.order.OrderUtil; -import io.micronaut.core.reflect.ClassUtils; +import io.micronaut.core.order.Ordered; import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.type.Argument; import io.micronaut.core.type.DefaultArgument; @@ -90,6 +93,7 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.core.util.Toggleable; import io.micronaut.inject.AdvisedBeanType; +import io.micronaut.inject.BeanContextConditional; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.BeanDefinitionReference; import io.micronaut.inject.DisposableBeanDefinition; @@ -98,13 +102,14 @@ import io.micronaut.inject.InitializingBeanDefinition; import io.micronaut.inject.InjectableBeanDefinition; import io.micronaut.inject.InjectionPoint; +import io.micronaut.inject.InstantiatableBeanDefinition; import io.micronaut.inject.ParametrizedInstantiatableBeanDefinition; import io.micronaut.inject.ProxyBeanDefinition; import io.micronaut.inject.ValidatedBeanDefinition; import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; import io.micronaut.inject.annotation.AnnotationMetadataReference; -import io.micronaut.inject.annotation.AnnotationMetadataWriter; import io.micronaut.inject.annotation.MutableAnnotationMetadata; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.ConstructorElement; @@ -119,21 +124,26 @@ import io.micronaut.inject.ast.beans.BeanElement; import io.micronaut.inject.ast.beans.BeanElementBuilder; import io.micronaut.inject.configuration.ConfigurationMetadataBuilder; -import io.micronaut.inject.processing.JavaModelUtils; import io.micronaut.inject.qualifiers.AnyQualifier; import io.micronaut.inject.qualifiers.Qualifiers; import io.micronaut.inject.visitor.BeanElementVisitor; import io.micronaut.inject.visitor.BeanElementVisitorContext; import io.micronaut.inject.visitor.VisitorContext; +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.AnnotationDef; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.ObjectDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; +import io.micronaut.sourcegen.model.VariableDef; import jakarta.inject.Singleton; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.signature.SignatureVisitor; +import javax.lang.model.element.Modifier; import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; @@ -144,22 +154,21 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.IntStream; import java.util.stream.Stream; import static io.micronaut.core.util.StringUtils.EMPTY_STRING_ARRAY; @@ -189,22 +198,21 @@ * @since 1.0 */ @Internal -public class BeanDefinitionWriter extends AbstractClassFileWriter implements BeanDefinitionVisitor, BeanElement, Toggleable { +public final class BeanDefinitionWriter implements ClassOutputWriter, BeanDefinitionVisitor, BeanElement, Toggleable { @NextMajorVersion("Inline as true") public static final String OMIT_CONFPROP_INJECTION_POINTS = "micronaut.processing.omit.confprop.injectpoints"; public static final String CLASS_SUFFIX = "$Definition"; private static final Constructor CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP = ReflectionUtils.findConstructor( - AbstractBeanDefinitionBeanConstructor.class, - BeanDefinition.class) - .orElseThrow(() -> new ClassGenerationException("Invalid version of Micronaut present on the class path")); + AbstractBeanDefinitionBeanConstructor.class, + BeanDefinition.class) + .orElseThrow(() -> new ClassGenerationException("Invalid version of Micronaut present on the class path")); private static final Method POST_CONSTRUCT_METHOD = ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanDefinition.class, "postConstruct", BeanResolutionContext.class, BeanContext.class, Object.class); - private static final org.objectweb.asm.commons.Method INJECT_BEAN_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(InjectableBeanDefinition.class, "inject", BeanResolutionContext.class, BeanContext.class, Object.class) - ); + private static final Method INJECT_BEAN_METHOD = + ReflectionUtils.getRequiredInternalMethod(InjectableBeanDefinition.class, "inject", BeanResolutionContext.class, BeanContext.class, Object.class); private static final Method PRE_DESTROY_METHOD = ReflectionUtils.getRequiredInternalMethod(AbstractInitializableBeanDefinition.class, "preDestroy", BeanResolutionContext.class, BeanContext.class, Object.class); @@ -259,193 +267,187 @@ public class BeanDefinitionWriter extends AbstractClassFileWriter implements Bea private static final Method FIND_BEAN_FOR_METHOD_ARGUMENT = getBeanLookupMethodForArgument("findBeanForMethodArgument", true); private static final Method CHECK_INJECTED_BEAN_PROPERTY_VALUE = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "checkInjectedBeanPropertyValue", - String.class, - Object.class, - String.class, - String.class); + AbstractInitializableBeanDefinition.class, + "checkInjectedBeanPropertyValue", + String.class, + Object.class, + String.class, + String.class); private static final Method GET_PROPERTY_VALUE_FOR_METHOD_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyValueForMethodArgument", - BeanResolutionContext.class, - BeanContext.class, - int.class, - int.class, - String.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyValueForMethodArgument", + BeanResolutionContext.class, + BeanContext.class, + int.class, + int.class, + String.class, + String.class); private static final Method GET_PROPERTY_PLACEHOLDER_VALUE_FOR_METHOD_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyPlaceholderValueForMethodArgument", - BeanResolutionContext.class, - BeanContext.class, - int.class, - int.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyPlaceholderValueForMethodArgument", + BeanResolutionContext.class, + BeanContext.class, + int.class, + int.class, + String.class); private static final Method GET_EVALUATED_EXPRESSION_VALUE_FOR_METHOD_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getEvaluatedExpressionValueForMethodArgument", - int.class, - int.class); + AbstractInitializableBeanDefinition.class, + "getEvaluatedExpressionValueForMethodArgument", + int.class, + int.class); private static final Method GET_BEAN_FOR_SETTER = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getBeanForSetter", - BeanResolutionContext.class, - BeanContext.class, - String.class, - Argument.class, - Qualifier.class); + AbstractInitializableBeanDefinition.class, + "getBeanForSetter", + BeanResolutionContext.class, + BeanContext.class, + String.class, + Argument.class, + Qualifier.class); private static final Method GET_BEANS_OF_TYPE_FOR_SETTER = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getBeansOfTypeForSetter", - BeanResolutionContext.class, - BeanContext.class, - String.class, - Argument.class, - Argument.class, - Qualifier.class); + AbstractInitializableBeanDefinition.class, + "getBeansOfTypeForSetter", + BeanResolutionContext.class, + BeanContext.class, + String.class, + Argument.class, + Argument.class, + Qualifier.class); private static final Method GET_PROPERTY_VALUE_FOR_SETTER = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyValueForSetter", - BeanResolutionContext.class, - BeanContext.class, - String.class, - Argument.class, - String.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyValueForSetter", + BeanResolutionContext.class, + BeanContext.class, + String.class, + Argument.class, + String.class, + String.class); private static final Method GET_PROPERTY_PLACEHOLDER_VALUE_FOR_SETTER = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyPlaceholderValueForSetter", - BeanResolutionContext.class, - BeanContext.class, - String.class, - Argument.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyPlaceholderValueForSetter", + BeanResolutionContext.class, + BeanContext.class, + String.class, + Argument.class, + String.class); private static final Method GET_PROPERTY_VALUE_FOR_CONSTRUCTOR_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyValueForConstructorArgument", - BeanResolutionContext.class, - BeanContext.class, - int.class, - String.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyValueForConstructorArgument", + BeanResolutionContext.class, + BeanContext.class, + int.class, + String.class, + String.class); private static final Method GET_PROPERTY_PLACEHOLDER_VALUE_FOR_CONSTRUCTOR_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyPlaceholderValueForConstructorArgument", - BeanResolutionContext.class, - BeanContext.class, - int.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyPlaceholderValueForConstructorArgument", + BeanResolutionContext.class, + BeanContext.class, + int.class, + String.class); private static final Method GET_EVALUATED_EXPRESSION_VALUE_FOR_CONSTRUCTOR_ARGUMENT = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getEvaluatedExpressionValueForConstructorArgument", - int.class); + AbstractInitializableBeanDefinition.class, + "getEvaluatedExpressionValueForConstructorArgument", + int.class); private static final Method GET_PROPERTY_VALUE_FOR_FIELD = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyValueForField", - BeanResolutionContext.class, - BeanContext.class, - Argument.class, - String.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyValueForField", + BeanResolutionContext.class, + BeanContext.class, + Argument.class, + String.class, + String.class); private static final Method GET_PROPERTY_PLACEHOLDER_VALUE_FOR_FIELD = ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "getPropertyPlaceholderValueForField", - BeanResolutionContext.class, - BeanContext.class, - Argument.class, - String.class); + AbstractInitializableBeanDefinition.class, + "getPropertyPlaceholderValueForField", + BeanResolutionContext.class, + BeanContext.class, + Argument.class, + String.class); + + private static final Method CONTAINS_PROPERTIES_VALUE_METHOD = ReflectionUtils.getRequiredInternalMethod( + AbstractInitializableBeanDefinition.class, + "containsPropertiesValue", + BeanResolutionContext.class, + BeanContext.class, + String.class); + + private static final Method CONTAINS_PROPERTY_VALUE_METHOD = ReflectionUtils.getRequiredInternalMethod( + AbstractInitializableBeanDefinition.class, + "containsPropertyValue", + BeanResolutionContext.class, + BeanContext.class, + String.class); + + private static final ClassTypeDef TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE = ClassTypeDef.of(AbstractInitializableBeanDefinitionAndReference.class); + + private static final Method METHOD_OPTIONAL_EMPTY = ReflectionUtils.getRequiredMethod(Optional.class, "empty"); + private static final ClassTypeDef TYPE_OPTIONAL = ClassTypeDef.of(Optional.class); + private static final Method METHOD_OPTIONAL_OF = ReflectionUtils.getRequiredMethod(Optional.class, "of", Object.class); - private static final org.objectweb.asm.commons.Method CONTAINS_PROPERTIES_VALUE_METHOD = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "containsPropertiesValue", - BeanResolutionContext.class, - BeanContext.class, - String.class)); + private static final String METHOD_NAME_INSTANTIATE = "instantiate"; + private static final Method METHOD_BEAN_CONSTRUCTOR_INSTANTIATE = ReflectionUtils.getRequiredMethod( + BeanConstructor.class, + METHOD_NAME_INSTANTIATE, + Object[].class + ); - private static final org.objectweb.asm.commons.Method CONTAINS_PROPERTY_VALUE_METHOD = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - "containsPropertyValue", - BeanResolutionContext.class, - BeanContext.class, - String.class)); + private static final Method METHOD_DESCRIPTOR_CONSTRUCTOR_INSTANTIATE = ReflectionUtils.getRequiredMethod(ConstructorInterceptorChain.class, METHOD_NAME_INSTANTIATE, + BeanResolutionContext.class, + BeanContext.class, + List.class, + BeanDefinition.class, + BeanConstructor.class, + int.class, + Object[].class + ); - private static final Type TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE = Type.getType(AbstractInitializableBeanDefinitionAndReference.class); + private static final Method METHOD_GET_BEAN = ReflectionUtils.getRequiredInternalMethod(DefaultBeanContext.class, "getBean", BeanResolutionContext.class, Class.class, Qualifier.class); + private static final Method COLLECTION_TO_ARRAY = ReflectionUtils.getRequiredInternalMethod(Collection.class, "toArray", Object[].class); - private static final org.objectweb.asm.commons.Method METHOD_OPTIONAL_EMPTY = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Optional.class, "empty") - ); - private static final Type TYPE_OPTIONAL = Type.getType(Optional.class); - private static final org.objectweb.asm.commons.Method METHOD_OPTIONAL_OF = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Optional.class, "of", Object.class) - ); - private static final String METHOD_NAME_INSTANTIATE = "instantiate"; - private static final org.objectweb.asm.commons.Method METHOD_BEAN_CONSTRUCTOR_INSTANTIATE = org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod( - BeanConstructor.class, - METHOD_NAME_INSTANTIATE, - Object[].class - )); - private static final String METHOD_DESCRIPTOR_CONSTRUCTOR_INSTANTIATE = getMethodDescriptor(Object.class, Arrays.asList( + private static final Method DISPOSE_INTERCEPTOR_METHOD = + ReflectionUtils.getRequiredInternalMethod(MethodInterceptorChain.class, "dispose", BeanResolutionContext.class, BeanContext.class, - List.class, BeanDefinition.class, - BeanConstructor.class, - int.class, - Object[].class - )); - private static final String METHOD_DESCRIPTOR_INTERCEPTED_LIFECYCLE = getMethodDescriptor(Object.class, Arrays.asList( + ExecutableMethod.class, + Object.class); + + private static final Method INITIALIZE_INTERCEPTOR_METHOD = + ReflectionUtils.getRequiredInternalMethod(MethodInterceptorChain.class, "initialize", BeanResolutionContext.class, BeanContext.class, BeanDefinition.class, ExecutableMethod.class, - Object.class - )); - private static final Method METHOD_GET_BEAN = ReflectionUtils.getRequiredInternalMethod(DefaultBeanContext.class, "getBean", BeanResolutionContext.class, Class.class, Qualifier.class); - private static final org.objectweb.asm.commons.Method COLLECTION_TO_ARRAY = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(Collection.class, "toArray", Object[].class) - ); - private static final Type TYPE_RESOLUTION_CONTEXT = Type.getType(BeanResolutionContext.class); - private static final Type TYPE_BEAN_CONTEXT = Type.getType(BeanContext.class); - private static final Type TYPE_BEAN_DEFINITION = Type.getType(BeanDefinition.class); - private static final String METHOD_DESCRIPTOR_INITIALIZE = Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(BeanResolutionContext.class), Type.getType(BeanContext.class), Type.getType(Object.class)); - - private static final org.objectweb.asm.commons.Method PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor( - Class.class, // beanType - AbstractInitializableBeanDefinition.MethodOrFieldReference.class // constructor - )); - - private static final org.objectweb.asm.commons.Method SET_FIELD_WITH_REFLECTION_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "setFieldWithReflection", BeanResolutionContext.class, BeanContext.class, int.class, Object.class, Object.class) - ); + Object.class); - private static final org.objectweb.asm.commons.Method INVOKE_WITH_REFLECTION_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "invokeMethodWithReflection", BeanResolutionContext.class, BeanContext.class, int.class, Object.class, Object[].class) - ); + private static final Method SET_FIELD_WITH_REFLECTION_METHOD = + ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "setFieldWithReflection", BeanResolutionContext.class, BeanContext.class, int.class, Object.class, Object.class); - private static final org.objectweb.asm.commons.Method IS_METHOD_RESOLVED = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "isMethodResolved", int.class, Object[].class) - ); + private static final Method INVOKE_WITH_REFLECTION_METHOD = + ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "invokeMethodWithReflection", BeanResolutionContext.class, BeanContext.class, int.class, Object.class, Object[].class); - private static final Type TYPE_REFLECTION_UTILS = Type.getType(ReflectionUtils.class); + private static final Method IS_METHOD_RESOLVED = + ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "isMethodResolved", int.class, Object[].class); - private static final org.objectweb.asm.commons.Method GET_FIELD_WITH_REFLECTION_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getField", Class.class, String.class, Object.class)); + private static final ClassTypeDef TYPE_REFLECTION_UTILS = ClassTypeDef.of(ReflectionUtils.class); - private static final org.objectweb.asm.commons.Method METHOD_INVOKE_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "invokeMethod", Object.class, java.lang.reflect.Method.class, Object[].class)); + private static final Method GET_FIELD_WITH_REFLECTION_METHOD = + ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getField", Class.class, String.class, Object.class); + + private static final Method METHOD_INVOKE_INACCESSIBLE_METHOD = + ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "invokeInaccessibleMethod", Object.class, Method.class, Object[].class); private static final Optional> BEAN_DEFINITION_CLASS_CONSTRUCTOR1 = ReflectionUtils.findConstructor( AbstractInitializableBeanDefinitionAndReference.class, @@ -476,19 +478,17 @@ public class BeanDefinitionWriter extends AbstractClassFileWriter implements Bea Throwable.class // failed initialization ); - private static final Type PRECALCULATED_INFO = Type.getType(AbstractInitializableBeanDefinition.PrecalculatedInfo.class); - private static final org.objectweb.asm.commons.Method PRECALCULATED_INFO_CONSTRUCTOR = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.PrecalculatedInfo.class, - Optional.class, // scope - boolean.class, // isAbstract - boolean.class, // isIterable - boolean.class, // isSingleton - boolean.class, // isPrimary - boolean.class, // isConfigurationProperties - boolean.class, // isContainerType - boolean.class, // requiresMethodProcessing, - boolean.class // hasEvaluatedExpressions - )); + private static final Constructor PRECALCULATED_INFO_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.PrecalculatedInfo.class, + Optional.class, // scope + boolean.class, // isAbstract + boolean.class, // isIterable + boolean.class, // isSingleton + boolean.class, // isPrimary + boolean.class, // isConfigurationProperties + boolean.class, // isContainerType + boolean.class, // requiresMethodProcessing, + boolean.class // hasEvaluatedExpressions + ); private static final String FIELD_CONSTRUCTOR = "$CONSTRUCTOR"; private static final String FIELD_EXECUTABLE_METHODS = "$EXEC"; @@ -503,135 +503,136 @@ public class BeanDefinitionWriter extends AbstractClassFileWriter implements Bea private static final String FIELD_PRE_START_CONDITIONS = "$PRE_CONDITIONS"; private static final String FIELD_POST_START_CONDITIONS = "$POST_CONDITIONS"; - private static final org.objectweb.asm.commons.Method METHOD_REFERENCE_CONSTRUCTOR = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor( - Class.class, // declaringType, - String.class, // methodName - Argument[].class, // arguments - AnnotationMetadata.class// annotationMetadata - )); - - private static final org.objectweb.asm.commons.Method METHOD_REFERENCE_CONSTRUCTOR_POST_PRE = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor( - Class.class, // declaringType, - String.class, // methodName - Argument[].class, // arguments - AnnotationMetadata.class, // annotationMetadata - boolean.class, // isPostConstructMethod - boolean.class // isPreDestroyMethod, - )); - - private static final org.objectweb.asm.commons.Method FIELD_REFERENCE_CONSTRUCTOR = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor( - Class.class, // declaringType; - Argument.class // argument; - )); - - private static final org.objectweb.asm.commons.Method ANNOTATION_REFERENCE_CONSTRUCTOR = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor( - Argument.class // argument; - )); - - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_FOR_ARGUMENT = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "forArgument", Argument.class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_NAME = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byName", String.class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_ANNOTATION = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byAnnotationSimple", AnnotationMetadata.class, String.class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_REPEATABLE_ANNOTATION = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byRepeatableAnnotation", AnnotationMetadata.class, String.class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_QUALIFIERS = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byQualifiers", Qualifier[].class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_INTERCEPTOR_BINDING = - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byInterceptorBinding", AnnotationMetadata.class) - ); - private static final org.objectweb.asm.commons.Method METHOD_QUALIFIER_BY_TYPE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Qualifiers.class, "byType", Class[].class) + private static final Constructor METHOD_REFERENCE_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.MethodReference.class, + Class.class, // declaringType, + String.class, // methodName + Argument[].class, // arguments + AnnotationMetadata.class// annotationMetadata ); - private static final org.objectweb.asm.commons.Method METHOD_BEAN_RESOLUTION_CONTEXT_MARK_FACTORY = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(BeanResolutionContext.class, "markDependentAsFactory") + private static final Constructor METHOD_REFERENCE_CONSTRUCTOR_POST_PRE = ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.MethodReference.class, + Class.class, // declaringType, + String.class, // methodName + Argument[].class, // arguments + AnnotationMetadata.class, // annotationMetadata + boolean.class, // isPostConstructMethod + boolean.class // isPreDestroyMethod, ); - private static final Type TYPE_QUALIFIERS = Type.getType(Qualifiers.class); - private static final Type TYPE_QUALIFIER = Type.getType(Qualifier.class); + + private static final Constructor FIELD_REFERENCE_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.FieldReference.class, Class.class, Argument.class); + + private static final Constructor ANNOTATION_REFERENCE_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(AbstractInitializableBeanDefinition.AnnotationReference.class, Argument.class); + + private static final Method METHOD_QUALIFIER_FOR_ARGUMENT = + ReflectionUtils.getRequiredMethod(Qualifiers.class, "forArgument", Argument.class); + + private static final Method METHOD_QUALIFIER_BY_NAME = ReflectionUtils.getRequiredMethod(Qualifiers.class, "byName", String.class); + + private static final Method METHOD_QUALIFIER_BY_ANNOTATION = + ReflectionUtils.getRequiredMethod(Qualifiers.class, "byAnnotationSimple", AnnotationMetadata.class, String.class); + + private static final Method METHOD_QUALIFIER_BY_REPEATABLE_ANNOTATION = + ReflectionUtils.getRequiredMethod(Qualifiers.class, "byRepeatableAnnotation", AnnotationMetadata.class, String.class); + + private static final Method METHOD_QUALIFIER_BY_QUALIFIERS = + ReflectionUtils.getRequiredMethod(Qualifiers.class, "byQualifiers", Qualifier[].class); + + private static final Method METHOD_QUALIFIER_BY_INTERCEPTOR_BINDING = + ReflectionUtils.getRequiredMethod(Qualifiers.class, "byInterceptorBinding", AnnotationMetadata.class); + + private static final Method METHOD_QUALIFIER_BY_TYPE = ReflectionUtils.getRequiredMethod(Qualifiers.class, "byType", Class[].class); + + private static final Method METHOD_BEAN_RESOLUTION_CONTEXT_MARK_FACTORY = ReflectionUtils.getRequiredMethod(BeanResolutionContext.class, "markDependentAsFactory"); + + private static final Method METHOD_PROXY_TARGET_TYPE = ReflectionUtils.getRequiredInternalMethod(ProxyBeanDefinition.class, "getTargetDefinitionType"); + + private static final Method METHOD_PROXY_TARGET_CLASS = ReflectionUtils.getRequiredInternalMethod(ProxyBeanDefinition.class, "getTargetType"); + + private static final ClassTypeDef TYPE_QUALIFIERS = ClassTypeDef.of(Qualifiers.class); + private static final ClassTypeDef TYPE_QUALIFIER = ClassTypeDef.of(Qualifier.class); private static final String MESSAGE_ONLY_SINGLE_CALL_PERMITTED = "Only a single call to visitBeanFactoryMethod(..) is permitted"; private static final int INJECT_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM = 0; private static final int INJECT_METHOD_BEAN_CONTEXT_PARAM = 1; - private static final int INJECT_METHOD_BEAN_INSTANCE_PARAM = 2; private static final int INSTANTIATE_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM = 0; private static final int INSTANTIATE_METHOD_BEAN_CONTEXT_PARAM = 1; - private static final org.objectweb.asm.commons.Method METHOD_BEAN_CONTEXT_GET_CONVERSION_SERVICE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(ConversionServiceProvider.class, "getConversionService") - ); - - private static final org.objectweb.asm.commons.Method METHOD_INVOKE_INTERNAL = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethod.class, "invokeInternal", Object.class, Object[].class)); + private static final Method METHOD_BEAN_CONTEXT_GET_CONVERSION_SERVICE = ReflectionUtils.getRequiredMethod(ConversionServiceProvider.class, "getConversionService"); + + private static final Method METHOD_INVOKE_INTERNAL = + ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethod.class, "invokeInternal", Object.class, Object[].class); + + private static final Method METHOD_INITIALIZE = + ReflectionUtils.getRequiredInternalMethod(InitializingBeanDefinition.class, "initialize", BeanResolutionContext.class, BeanContext.class, Object.class); + + private static final Method METHOD_DISPOSE = + ReflectionUtils.getRequiredInternalMethod(DisposableBeanDefinition.class, "dispose", BeanResolutionContext.class, BeanContext.class, Object.class); + + private static final Method DESTROY_INJECT_SCOPED_BEANS_METHOD = ReflectionUtils.getRequiredInternalMethod(BeanResolutionContext.class, "destroyInjectScopedBeans"); + private static final Method CHECK_IF_SHOULD_LOAD_METHOD = ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, + "checkIfShouldLoad", + BeanResolutionContext.class, + BeanContext.class); + private static final Method GET_MAP_METHOD = ReflectionUtils.getRequiredMethod(Map.class, "get", Object.class); + private static final Method LOAD_REFERENCE_METHOD = ReflectionUtils.getRequiredMethod(BeanDefinitionReference.class, "load"); + private static final Method IS_CONTEXT_SCOPE_METHOD = ReflectionUtils.getRequiredMethod(BeanDefinitionReference.class, "isContextScope"); + private static final Method IS_PROXIED_BEAN_METHOD = ReflectionUtils.getRequiredMethod(BeanDefinitionReference.class, "isProxiedBean"); + private static final Method IS_ENABLED_METHOD = ReflectionUtils.getRequiredMethod(BeanContextConditional.class, "isEnabled", BeanContext.class); + private static final Method IS_ENABLED2_METHOD = ReflectionUtils.getRequiredMethod(BeanContextConditional.class, "isEnabled", BeanContext.class, BeanResolutionContext.class); + private static final Method GET_INTERCEPTED_TYPE_METHOD = ReflectionUtils.getRequiredMethod(AdvisedBeanType.class, "getInterceptedType"); + private static final Method DO_INSTANTIATE_METHOD = ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "doInstantiate", BeanResolutionContext.class, BeanContext.class, Map.class); + private static final Method INSTANTIATE_METHOD = ReflectionUtils.getRequiredMethod(InstantiatableBeanDefinition.class, "instantiate", BeanResolutionContext.class, BeanContext.class); + private static final Method COLLECTION_UTILS_ENUM_SET_METHOD = ReflectionUtils.getRequiredMethod(CollectionUtils.class, "enumSet", Enum[].class); + private static final Method IS_INNER_CONFIGURATION_METHOD = ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "isInnerConfiguration", Class.class); + private static final Method CONTAINS_METHOD = ReflectionUtils.getRequiredMethod(Collection.class, "contains", Object.class); + private static final Method GET_EXPOSED_TYPES_METHOD = ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "getExposedTypes"); + private static final Method GET_ORDER_METHOD = ReflectionUtils.getRequiredMethod(Ordered.class, "getOrder"); + private static final Constructor HASH_SET_COLLECTION_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(HashSet.class, Collection.class); + private static final Method ARRAYS_AS_LIST_METHOD = ReflectionUtils.getRequiredMethod(Arrays.class, "asList", Object[].class); + private static final Method COLLECTIONS_SINGLETON_METHOD = ReflectionUtils.getRequiredMethod(Collections.class, "singleton", Object.class); + private static final Method OPTIONAL_IS_PRESENT_METHOD = ReflectionUtils.getRequiredMethod(Optional.class, "isPresent"); + private static final Method OPTIONAL_GET_METHOD = ReflectionUtils.getRequiredMethod(Optional.class, "get"); + private static final Method DURATION_TO_MILLIS_METHOD = ReflectionUtils.getRequiredMethod(Duration.class, "toMillis"); + private static final Method PROVIDER_GET_ANNOTATION_METADATA_METHOD = ReflectionUtils.getRequiredMethod(AnnotationMetadataProvider.class, "getAnnotationMetadata"); + private static final Method IS_PROXY_TARGET_METHOD = ReflectionUtils.getRequiredMethod(BeanDefinitionReference.class, "isProxyTarget"); + private static final Method GET_CONFIGURATION_PATH_METHOD = ReflectionUtils.getRequiredInternalMethod(BeanResolutionContext.class, "getConfigurationPath"); + private static final Constructor ABSTRACT_EXECUTABLE_METHOD_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(AbstractExecutableMethod.class, Class.class, String.class); + private static final Method GET_TYPE_PARAMETERS_METHOD = ReflectionUtils.getRequiredInternalMethod(TypeVariableResolver.class, "getTypeParameters"); + private static final Method ARGUMENT_OF_METHOD = ReflectionUtils.getRequiredInternalMethod(Argument.class, "of", Class.class); - private final ClassWriter classWriter; private final String beanFullClassName; private final String beanDefinitionName; - private final String beanDefinitionInternalName; - private final Type beanType; - private final Set> interfaceTypes; - private final Map defaultsStorage = new HashMap<>(); - private final Map loadTypeMethods = new LinkedHashMap<>(); - private final Map innerClasses = new LinkedHashMap<>(2); + private final TypeDef beanTypeDef; + private final Map loadTypeMethods = new LinkedHashMap<>(); private final String packageName; private final String beanSimpleClassName; - private final Type beanDefinitionType; + private final ClassTypeDef beanDefinitionTypeDef; private final boolean isInterface; private final boolean isAbstract; private final boolean isConfigurationProperties; private final Element beanProducingElement; private final ClassElement beanTypeElement; private final VisitorContext visitorContext; - private final boolean isPrimitiveBean; private final List beanTypeInnerClasses; private final EvaluatedExpressionProcessor evaluatedExpressionProcessor; - private GeneratorAdapter buildMethodVisitor; - private GeneratorAdapter injectMethodVisitor; - private GeneratorAdapter checkIfShouldLoadMethodVisitor; - private Label injectEnd = null; - private GeneratorAdapter preDestroyMethodVisitor; - private GeneratorAdapter postConstructMethodVisitor; - private boolean postConstructAdded; - private GeneratorAdapter interceptedDisposeMethod; - private int currentFieldIndex = 0; - private int currentMethodIndex = 0; - - private int buildInstanceLocalVarIndex = -1; - private int injectInstanceLocalVarIndex = -1; - private int postConstructInstanceLocalVarIndex = -1; - private int preDestroyInstanceLocalVarIndex = -1; + private boolean beanFinalized = false; - private Type superType = TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE; - private boolean isParametrized = false; + private ClassTypeDef superType = TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE; private boolean superBeanDefinition = false; private boolean isSuperFactory = false; private final AnnotationMetadata annotationMetadata; - private ConfigBuilderState currentConfigBuilderState; private boolean preprocessMethods = false; private Map> typeArguments; + @Nullable private String interceptedType; - private int innerClassIndex; - private final List fieldInjectionPoints = new ArrayList<>(2); private final List methodInjectionPoints = new ArrayList<>(2); private final List postConstructMethodVisits = new ArrayList<>(2); private final List preDestroyMethodVisits = new ArrayList<>(2); private final List allMethodVisits = new ArrayList<>(2); - private final Map> annotationInjectionPoints = new LinkedHashMap<>(2); + private final Map> annotationInjectionPoints = new LinkedHashMap<>(2); private final Map isLifeCycleCache = new HashMap<>(2); private ExecutableMethodsDefinitionWriter executableMethodsDefinitionWriter; @@ -642,6 +643,17 @@ public class BeanDefinitionWriter extends AbstractClassFileWriter implements Bea private boolean proxiedBean = false; private boolean isProxyTarget = false; + private String proxyBeanDefinitionName, proxyBeanTypeName; + + private final OriginatingElements originatingElements; + + private final ClassDef.ClassDefBuilder classDefBuilder; + + private BuildMethodDefinition buildMethodDefinition; + private final List injectCommands = new ArrayList<>(); + private ConfigBuilderInjectCommand configBuilderInjectCommand; + private boolean validated; + /** * Creates a bean definition writer. * @@ -678,8 +690,7 @@ public BeanDefinitionWriter(Element beanProducingElement, OriginatingElements originatingElements, VisitorContext visitorContext, @Nullable Integer uniqueIdentifier) { - super(originatingElements); - this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + this.originatingElements = originatingElements; this.beanProducingElement = beanProducingElement; if (beanProducingElement instanceof ClassElement classElement) { autoApplyNamedToBeanProducingElement(classElement); @@ -760,12 +771,9 @@ public BeanDefinitionWriter(Element beanProducingElement, } else { throw new IllegalArgumentException("Unsupported element type: " + beanProducingElement.getClass().getName()); } - this.isPrimitiveBean = beanTypeElement.isPrimitive() && !beanTypeElement.isArray(); this.annotationMetadata = beanProducingElement.getTargetAnnotationMetadata(); - this.beanDefinitionType = getTypeReferenceForName(this.beanDefinitionName); - this.beanType = getTypeReference(beanTypeElement); - this.beanDefinitionInternalName = getInternalName(this.beanDefinitionName); - this.interfaceTypes = new TreeSet<>(Comparator.comparing(Class::getName)); + this.beanDefinitionTypeDef = ClassTypeDef.of(beanDefinitionName); + this.beanTypeDef = TypeDef.erasure(beanTypeElement); this.isConfigurationProperties = isConfigurationProperties(annotationMetadata); validateExposedTypes(annotationMetadata, visitorContext); this.visitorContext = visitorContext; @@ -774,12 +782,39 @@ public BeanDefinitionWriter(Element beanProducingElement, beanTypeElement.getName().contains(BeanDefinitionVisitor.PROXY_SUFFIX) ? null : beanTypeElement); beanTypeInnerClasses = beanTypeElement.getEnclosedElements(ElementQuery.of(ClassElement.class)) - .stream() - .filter(this::isConfigurationProperties) - .map(Element::getName) - .toList(); + .stream() + .filter(this::isConfigurationProperties) + .map(Element::getName) + .toList(); String prop = visitorContext.getOptions().get(OMIT_CONFPROP_INJECTION_POINTS); keepConfPropInjectPoints = prop == null || !prop.equals("true"); + + TypeDef argumentType; + if (beanTypeDef instanceof TypeDef.Primitive primitive) { + argumentType = primitive.wrapperType(); + } else if (beanTypeDef instanceof TypeDef.Array array) { + argumentType = array; + } else { + argumentType = ClassTypeDef.of(beanTypeElement); + } + + classDefBuilder = ClassDef.builder(beanDefinitionName) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", BeanDefinitionReference.class.getName()).build()) + .superclass(TypeDef.parameterized(superType, argumentType)); + } + + /** + * Mark to generate proxy methods. + * + * @param proxyBeanDefinitionName The definition name + * @param proxyBeanTypeName The proxy bean name + */ + public void generateProxyReference(String proxyBeanDefinitionName, String proxyBeanTypeName) { + Objects.requireNonNull(proxyBeanDefinitionName); + Objects.requireNonNull(proxyBeanTypeName); + this.proxyBeanDefinitionName = proxyBeanDefinitionName; + this.proxyBeanTypeName = proxyBeanTypeName; } @Override @@ -814,7 +849,7 @@ private void validateExposedTypes(AnnotationMetadata annotationMetadata, Visitor annotationMetadata = annotationMetadata.getDeclaredMetadata(); } final String[] types = annotationMetadata - .stringValues(Bean.class, "typed"); + .stringValues(Bean.class, "typed"); if (ArrayUtils.isNotEmpty(types) && !beanTypeElement.isProxy()) { for (String name : types) { final ClassElement exposedType = visitorContext.getClassElement(name).orElse(null); @@ -876,17 +911,10 @@ public String getBeanDefinitionReferenceClassName() { /** * @return The data for any post construct methods that were visited */ - public final List getPostConstructMethodVisits() { + public List getPostConstructMethodVisits() { return Collections.unmodifiableList(postConstructMethodVisits); } - /** - * @return The underlying class writer - */ - public ClassVisitor getClassWriter() { - return classWriter; - } - @Override public boolean isInterface() { return isInterface; @@ -899,18 +927,18 @@ public boolean isSingleton() { @Override public void visitBeanDefinitionInterface(Class interfaceType) { - this.interfaceTypes.add(interfaceType); + this.classDefBuilder.addSuperinterface(TypeDef.of(interfaceType)); } @Override public void visitSuperBeanDefinition(String name) { this.superBeanDefinition = true; - this.superType = getTypeReferenceForName(name); + this.superType = ClassTypeDef.of(name); + classDefBuilder.superclass(superType); } @Override public void visitSuperBeanDefinitionFactory(String beanName) { - visitSuperBeanDefinition(beanName); this.superBeanDefinition = false; this.isSuperFactory = true; } @@ -922,35 +950,39 @@ public String getBeanTypeName() { @Override public Type getProvidedType() { - return beanType; + throw new IllegalStateException("Not supported!"); } @Override public void setValidated(boolean validated) { if (validated) { - this.interfaceTypes.add(ValidatedBeanDefinition.class); + if (!this.validated) { + classDefBuilder.addSuperinterface(ClassTypeDef.of(ValidatedBeanDefinition.class)); + this.validated = true; + } } else { - this.interfaceTypes.remove(ValidatedBeanDefinition.class); + if (this.validated) { + throw new IllegalStateException("Bean definition " + beanTypeDef + " already marked for validation"); + } } } @Override public void setInterceptedType(String typeName) { if (typeName != null) { - this.interfaceTypes.add(AdvisedBeanType.class); + classDefBuilder.addSuperinterface(TypeDef.of(AdvisedBeanType.class)); } this.interceptedType = typeName; } @Override public Optional getInterceptedType() { - return Optional.ofNullable(interceptedType) - .map(BeanDefinitionWriter::getTypeReferenceForName); + throw new IllegalStateException("Not supported!"); } @Override public boolean isValidated() { - return this.interfaceTypes.contains(ValidatedBeanDefinition.class); + return validated; } @Override @@ -958,6 +990,15 @@ public String getBeanDefinitionName() { return beanDefinitionName; } + @Override + public Element getOriginatingElement() { + Element[] originatingElements = getOriginatingElements(); + if (ArrayUtils.isNotEmpty(originatingElements)) { + return originatingElements[0]; + } + return null; + } + /** *

In the case where the produced class is produced by a factory method annotated with * {@link Bean} this method should be called.

@@ -974,8 +1015,6 @@ public void visitBeanFactoryMethod(ClassElement factoryClass, constructor = factoryMethod; // now prepare the implementation of the build method. See BeanFactory interface visitBuildFactoryMethodDefinition(factoryClass, factoryMethod, factoryMethod.getParameters()); - // now implement the inject method - visitInjectMethodDefinition(); } } @@ -997,8 +1036,6 @@ public void visitBeanFactoryMethod(ClassElement factoryClass, constructor = factoryMethod; // now prepare the implementation of the build method. See BeanFactory interface visitBuildFactoryMethodDefinition(factoryClass, factoryMethod, parameters); - // now implement the inject method - visitInjectMethodDefinition(); } } @@ -1019,8 +1056,6 @@ public void visitBeanFactoryField(ClassElement factoryClass, FieldElement factor autoApplyNamedIfPresent(factoryField, factoryField.getAnnotationMetadata()); // now prepare the implementation of the build method. See BeanFactory interface visitBuildFactoryMethodDefinition(factoryClass, factoryField); - // now implement the inject method - visitInjectMethodDefinition(); } } @@ -1039,13 +1074,10 @@ public void visitBeanDefinitionConstructor(MethodElement constructor, this.constructor = constructor; // now prepare the implementation of the build method. See BeanFactory interface - visitBuildMethodDefinition(constructor, requiresReflection); - - // now implement the inject method - visitInjectMethodDefinition(); + visitBuildConstructorDefinition(constructor, requiresReflection); evaluatedExpressionProcessor.processEvaluatedExpressions(constructor.getAnnotationMetadata(), null); - for (ParameterElement parameter: constructor.getParameters()) { + for (ParameterElement parameter : constructor.getParameters()) { evaluatedExpressionProcessor.processEvaluatedExpressions(parameter.getAnnotationMetadata(), null); } } @@ -1054,20 +1086,18 @@ public void visitBeanDefinitionConstructor(MethodElement constructor, @Override public void visitDefaultConstructor(AnnotationMetadata annotationMetadata, VisitorContext visitorContext) { if (this.constructor == null) { - ClassElement bean = ClassElement.of(beanType.getClassName()); + ClassElement bean = ClassElement.of(((ClassTypeDef) beanTypeDef).getName()); MethodElement defaultConstructor = MethodElement.of( - bean, - annotationMetadata, - bean, - bean, - "" + bean, + annotationMetadata, + bean, + bean, + "" ); constructor = defaultConstructor; // now prepare the implementation of the build method. See BeanFactory interface - visitBuildMethodDefinition(defaultConstructor, false); - // now implement the inject method - visitInjectMethodDefinition(); + visitBuildConstructorDefinition(defaultConstructor, false); } } @@ -1077,10 +1107,6 @@ public void visitDefaultConstructor(AnnotationMetadata annotationMetadata, Visit @SuppressWarnings("Duplicates") @Override public void visitBeanDefinitionEnd() { - if (classWriter == null) { - throw new IllegalStateException("At least one called to visitBeanDefinitionConstructor(..) is required"); - } - if (executableMethodsDefinitionWriter != null) { // Make sure the methods are written and annotation defaults are contributed executableMethodsDefinitionWriter.visitDefinitionEnd(); @@ -1088,481 +1114,1129 @@ public void visitBeanDefinitionEnd() { processAllBeanElementVisitors(); - if (constructor instanceof MethodElement methodElement) { - boolean isParametrized = Arrays.stream(methodElement.getParameters()) - .map(AnnotationMetadataProvider::getAnnotationMetadata) - .anyMatch(this::isAnnotatedWithParameter); - if (isParametrized) { - this.interfaceTypes.add(ParametrizedInstantiatableBeanDefinition.class); - } - } - - classWriter.visit( - V17, - ACC_PUBLIC | ACC_SYNTHETIC, - beanDefinitionInternalName, - generateBeanDefSig(beanType), - getSuperTypeInternalType(), - interfaceTypes.stream().map(Type::getInternalName).toArray(String[]::new) - ); - - annotateAsGeneratedAndService(classWriter, BeanDefinitionReference.class.getName()); - - // init expressions at build time - evaluatedExpressionProcessor.registerExpressionForBuildTimeInit(classWriter); + evaluatedExpressionProcessor.registerExpressionForBuildTimeInit(classDefBuilder); - if (buildMethodVisitor == null) { - throw new IllegalStateException("At least one call to visitBeanDefinitionConstructor() is required"); - } else { - if (isPrimitiveBean) { - pushBoxPrimitiveIfNecessary(beanType, buildMethodVisitor); - } - buildMethodVisitor.returnValue(); - buildMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 10); - } - if (injectMethodVisitor != null) { - if (injectEnd != null) { - injectMethodVisitor.visitLabel(injectEnd); - } - invokeSuperInjectMethod(injectMethodVisitor, INJECT_BEAN_METHOD); - if (isPrimitiveBean) { - pushBoxPrimitiveIfNecessary(beanType, injectMethodVisitor); - } - injectMethodVisitor.returnValue(); - injectMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 10); - } - if (postConstructMethodVisitor != null) { - postConstructMethodVisitor.loadLocal(postConstructInstanceLocalVarIndex); - postConstructMethodVisitor.returnValue(); - postConstructMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 10); - } - if (preDestroyMethodVisitor != null) { - preDestroyMethodVisitor.loadLocal(preDestroyInstanceLocalVarIndex); - preDestroyMethodVisitor.returnValue(); - preDestroyMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 10); - } - if (interceptedDisposeMethod != null) { - interceptedDisposeMethod.visitMaxs(1, 1); - interceptedDisposeMethod.visitEnd(); + MethodDef getOrderMethod = getGetOrder(); + if (getOrderMethod != null) { + classDefBuilder.addMethod(getOrderMethod); } - if (checkIfShouldLoadMethodVisitor != null) { - buildCheckIfShouldLoadMethod(checkIfShouldLoadMethodVisitor, annotationInjectionPoints); - checkIfShouldLoadMethodVisitor.visitMaxs(DEFAULT_MAX_STACK, 10); + if (interceptedType != null) { + classDefBuilder.addMethod( + getGetInterceptedType(TypeDef.of(interceptedType)) + ); } - addStaticInitializer(); - - addConstructor(); - addGetOrder(); - - getInterceptedType().ifPresent(t -> implementInterceptedTypeMethod(t, this.classWriter)); - - GeneratorAdapter loadMethod = startPublicMethodZeroArgs(classWriter, BeanDefinition.class, "load"); - pushNewInstance(loadMethod, beanDefinitionType); - loadMethod.returnValue(); - loadMethod.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + LOAD_REFERENCE_METHOD + ).build((aThis, methodParameters) -> beanDefinitionTypeDef.instantiate().returning()) + ); if (annotationMetadata.hasDeclaredAnnotation(Context.class)) { - GeneratorAdapter isContextScopeMethod = startPublicMethodZeroArgs(classWriter, boolean.class, "isContextScope"); - isContextScopeMethod.push(true); - isContextScopeMethod.returnValue(); - isContextScopeMethod.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + IS_CONTEXT_SCOPE_METHOD + ).build((aThis, methodParameters) -> ExpressionDef.trueValue().returning()) + ); } if (proxiedBean || superType != TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE) { - GeneratorAdapter isProxiedBeanMethod = startPublicMethodZeroArgs(classWriter, boolean.class, "isProxiedBean"); - isProxiedBeanMethod.push(proxiedBean); - isProxiedBeanMethod.returnValue(); - isProxiedBeanMethod.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + IS_PROXIED_BEAN_METHOD + ).build((aThis, methodParameters) -> ExpressionDef.constant(proxiedBean).returning()) + ); } if (isProxyTarget || superType != TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE) { - GeneratorAdapter isProxiedBeanMethod = startPublicMethodZeroArgs(classWriter, boolean.class, "isProxyTarget"); - isProxiedBeanMethod.push(isProxyTarget); - isProxiedBeanMethod.returnValue(); - isProxiedBeanMethod.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + IS_PROXY_TARGET_METHOD + ).build((aThis, methodParameters) -> ExpressionDef.constant(isProxyTarget).returning()) + ); } if (!annotationMetadata.hasStereotype(Requires.class)) { - GeneratorAdapter isEnabledBeanMethod = startPublicMethod(classWriter, "isEnabled", boolean.class, BeanContext.class); - isEnabledBeanMethod.push(true); - isEnabledBeanMethod.returnValue(); - isEnabledBeanMethod.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + IS_ENABLED_METHOD + ).build((aThis, methodParameters) -> ExpressionDef.trueValue().returning()) + ); + classDefBuilder.addMethod( + MethodDef.override( + IS_ENABLED2_METHOD + ).build((aThis, methodParameters) -> ExpressionDef.trueValue().returning()) + ); + } + + if (proxyBeanDefinitionName != null) { + classDefBuilder.addMethod( + MethodDef.override( + METHOD_PROXY_TARGET_TYPE + ).build((aThis, methodParameters) + -> ExpressionDef.constant(ClassTypeDef.of(proxyBeanDefinitionName)).returning()) + ); - GeneratorAdapter isEnabledBeanMethod2 = startPublicMethod(classWriter, "isEnabled", boolean.class, BeanContext.class, BeanResolutionContext.class); - isEnabledBeanMethod2.push(true); - isEnabledBeanMethod2.returnValue(); - isEnabledBeanMethod2.endMethod(); + classDefBuilder.addMethod( + MethodDef.override( + METHOD_PROXY_TARGET_CLASS + ).build((aThis, methodParameters) + -> ExpressionDef.constant(ClassTypeDef.of(proxyBeanTypeName)).returning()) + ); } - for (GeneratorAdapter method : loadTypeMethods.values()) { - method.visitMaxs(3, 1); - method.visitEnd(); + classDefBuilder.addMethod( + getBuildMethod(buildMethodDefinition) + ); + if (!injectCommands.isEmpty()) { + classDefBuilder.addMethod( + getInjectMethod(injectCommands) + ); } - classWriter.visitEnd(); - this.beanFinalized = true; - } - private void addStaticInitializer() { - GeneratorAdapter staticInit = visitStaticInitializer(classWriter); + if (buildMethodDefinition.postConstruct != null) { + // for "super bean definition" we only add code to trigger "initialize" + if (!superBeanDefinition || buildMethodDefinition.postConstruct.intercepted) { + classDefBuilder.addSuperinterface(TypeDef.of(InitializingBeanDefinition.class)); - AbstractAnnotationMetadataWriter.initializeAnnotationMetadata(staticInit, classWriter, beanDefinitionType, annotationMetadata, defaultsStorage, loadTypeMethods); + // Create a new method that will be invoked by the intercepted chain + MethodDef targetInitializeMethod = buildInitializeMethod(buildMethodDefinition.postConstruct, MethodDef.builder("initialize$intercepted") + .addModifiers(Modifier.PUBLIC) + .addParameters(BeanResolutionContext.class, BeanContext.class, Object.class) + .returns(Object.class)); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_FAILED_INITIALIZATION, Type.getType(Throwable.class).getDescriptor(), null, null); + classDefBuilder.addMethod( + targetInitializeMethod + ); - Label tryStart = new Label(); - Label tryEnd = new Label(); - Label exceptionHandler = new Label(); - staticInit.visitTryCatchBlock(tryStart, tryEnd, exceptionHandler, Type.getInternalName(Throwable.class)); + // Original initialize method is invoking the interceptor chain + classDefBuilder.addMethod( + MethodDef.override(METHOD_INITIALIZE).build((aThis, methodParameters) -> { + ClassTypeDef executableMethodInterceptor = createExecutableMethodInterceptor(targetInitializeMethod, "InitializeInterceptor"); + return interceptAndReturn(aThis, methodParameters, executableMethodInterceptor, INITIALIZE_INTERCEPTOR_METHOD); + }) + ); + } + } - staticInit.visitLabel(tryStart); + if (buildMethodDefinition.preDestroy != null) { + classDefBuilder.addSuperinterface(TypeDef.of(DisposableBeanDefinition.class)); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_CONSTRUCTOR, - Type.getType(AbstractInitializableBeanDefinition.MethodOrFieldReference.class).getDescriptor(), null, null); + if (buildMethodDefinition.preDestroy.intercepted) { + // Create a new method that will be invoked by the intercepted chain + MethodDef targetDisposeMethod = buildDisposeMethod(buildMethodDefinition.preDestroy, MethodDef.builder("dispose$intercepted") + .addModifiers(Modifier.PUBLIC) + .addParameters(BeanResolutionContext.class, BeanContext.class, Object.class) + .returns(Object.class)); - if (constructor instanceof MethodElement methodElement) { - ParameterElement[] parameters = methodElement.getParameters(); - List parameterList = Arrays.asList(parameters); - applyDefaultNamedToParameters(parameterList); + classDefBuilder.addMethod( + targetDisposeMethod + ); - pushNewMethodReference(staticInit, JavaModelUtils.getTypeReference(methodElement.getDeclaringType()), methodElement, methodElement.getAnnotationMetadata(), false, false); - } else if (constructor instanceof FieldElement fieldConstructor) { - pushNewFieldReference(staticInit, JavaModelUtils.getTypeReference(fieldConstructor.getDeclaringType()), fieldConstructor, fieldConstructor.getAnnotationMetadata()); - } else { - throw new IllegalArgumentException("Unexpected constructor: " + constructor); - } - staticInit.putStatic(beanDefinitionType, FIELD_CONSTRUCTOR, Type.getType(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)); + // Original dispose method is invoking the interceptor chain + classDefBuilder.addMethod( + MethodDef.override(METHOD_DISPOSE).build((aThis, methodParameters) -> { + ClassTypeDef executableMethodInterceptor = createExecutableMethodInterceptor(targetDisposeMethod, "DisposeInterceptor"); + return interceptAndReturn(aThis, methodParameters, executableMethodInterceptor, DISPOSE_INTERCEPTOR_METHOD); + }) + ); - boolean hasMethodInjection = !superBeanDefinition && !allMethodVisits.isEmpty(); - if (hasMethodInjection) { - Type methodsFieldType = Type.getType(AbstractInitializableBeanDefinition.MethodReference[].class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_INJECTION_METHODS, methodsFieldType.getDescriptor(), null, null); - pushNewArray(staticInit, AbstractInitializableBeanDefinition.MethodReference.class, allMethodVisits, methodVisitData -> - pushNewMethodReference( - staticInit, - JavaModelUtils.getTypeReference(methodVisitData.beanType), - methodVisitData.methodElement, - methodVisitData.getAnnotationMetadata(), - methodVisitData.isPostConstruct(), - methodVisitData.isPreDestroy() - )); - staticInit.putStatic(beanDefinitionType, FIELD_INJECTION_METHODS, methodsFieldType); + } else { + classDefBuilder.addMethod( + buildDisposeMethod(buildMethodDefinition.preDestroy, MethodDef.override(METHOD_DISPOSE)) + ); + } } - boolean hasFieldInjection = !fieldInjectionPoints.isEmpty(); - if (hasFieldInjection) { - Type fieldsFieldType = Type.getType(AbstractInitializableBeanDefinition.FieldReference[].class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_INJECTION_FIELDS, fieldsFieldType.getDescriptor(), null, null); - pushNewArray(staticInit, AbstractInitializableBeanDefinition.FieldReference.class, fieldInjectionPoints, fieldVisitData -> - pushNewFieldReference( - staticInit, - JavaModelUtils.getTypeReference(fieldVisitData.beanType), - fieldVisitData.fieldElement, - fieldVisitData.annotationMetadata - )); - staticInit.putStatic(beanDefinitionType, FIELD_INJECTION_FIELDS, fieldsFieldType); - } + StaticBlock staticBlock = getStaticInitializer(); - boolean hasAnnotationInjection = !annotationInjectionPoints.isEmpty(); - if (hasAnnotationInjection) { - Type annotationInjectionsFieldType = Type.getType(AbstractInitializableBeanDefinition.AnnotationReference[].class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_ANNOTATION_INJECTIONS, - annotationInjectionsFieldType.getDescriptor(), null, null); + classDefBuilder.addStaticInitializer(staticBlock.statement); - List injectedTypes = new ArrayList<>(annotationInjectionPoints.keySet()); - pushNewArray(staticInit, AbstractInitializableBeanDefinition.AnnotationReference.class, injectedTypes, annotationVisitData -> - pushNewAnnotationReference(staticInit, annotationVisitData)); - staticInit.putStatic(beanDefinitionType, FIELD_ANNOTATION_INJECTIONS, annotationInjectionsFieldType); - } + addConstructor(staticBlock); - boolean hasTypeArguments = !superBeanDefinition && hasTypeArguments(); - if (hasTypeArguments) { - Type typeArgumentsFieldType = Type.getType(Map.class); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_TYPE_ARGUMENTS, typeArgumentsFieldType.getDescriptor(), null, null); - pushStringMapOf(staticInit, typeArguments, true, null, stringClassElementMap -> - pushTypeArgumentElements( - annotationMetadata, - beanDefinitionType, - classWriter, - staticInit, - beanDefinitionName, - stringClassElementMap, - defaultsStorage, - loadTypeMethods - )); - staticInit.putStatic(beanDefinitionType, FIELD_TYPE_ARGUMENTS, typeArgumentsFieldType); - } + loadTypeMethods.values().forEach(classDefBuilder::addMethod); - boolean hasExecutableMethods = executableMethodsDefinitionWriter != null; - if (hasExecutableMethods) { - Type execType = executableMethodsDefinitionWriter.getClassType(); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_EXECUTABLE_METHODS, execType.getDescriptor(), null, null); - staticInit.newInstance(execType); - staticInit.dup(); - staticInit.invokeConstructor(execType, METHOD_DEFAULT_CONSTRUCTOR); - staticInit.putStatic(beanDefinitionType, FIELD_EXECUTABLE_METHODS, executableMethodsDefinitionWriter.getClassType()); - } + this.beanFinalized = true; + } - staticInit.goTo(tryEnd); + private MethodDef getGetInterceptedType(TypeDef interceptedType) { + return MethodDef.override(GET_INTERCEPTED_TYPE_METHOD) + .build((aThis, methodParameters) -> ExpressionDef.constant(interceptedType).returning()); + } - staticInit.visitLabel(exceptionHandler); - staticInit.putStatic(beanDefinitionType, FIELD_FAILED_INITIALIZATION, Type.getType(Throwable.class)); - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_CONSTRUCTOR, Type.getType(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)); - if (hasExecutableMethods) { - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_EXECUTABLE_METHODS, executableMethodsDefinitionWriter.getClassType()); - } - if (hasMethodInjection) { - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_INJECTION_METHODS, Type.getType(AbstractInitializableBeanDefinition.MethodReference[].class)); - } - if (hasFieldInjection) { - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_INJECTION_FIELDS, Type.getType(AbstractInitializableBeanDefinition.FieldReference[].class)); - } - if (hasAnnotationInjection) { - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_ANNOTATION_INJECTIONS, Type.getType(AbstractInitializableBeanDefinition.AnnotationReference[].class)); - } - if (hasTypeArguments) { - staticInit.push((String) null); - staticInit.putStatic(beanDefinitionType, FIELD_TYPE_ARGUMENTS, Type.getType(Map.class)); + private MethodDef getBuildMethod(BuildMethodDefinition buildMethodDefinition) { + boolean isParametrized = isParametrized(buildMethodDefinition.getParameters()); + + MethodDef.MethodDefBuilder buildMethodBuilder; + if (isParametrized) { + buildMethodBuilder = MethodDef.override(DO_INSTANTIATE_METHOD); + classDefBuilder.addSuperinterface(TypeDef.of(ParametrizedInstantiatableBeanDefinition.class)); + } else { + buildMethodBuilder = MethodDef.override(INSTANTIATE_METHOD); } - staticInit.goTo(tryEnd); - staticInit.visitLabel(tryEnd); + return buildMethodBuilder.build((aThis, methodParameters) -> StatementDef.multi( + invokeCheckIfShouldLoadIfNecessary(aThis, methodParameters), + buildInstance( + aThis, + methodParameters, + buildMethodDefinition, + instance -> onBeanInstance(aThis, methodParameters, buildMethodDefinition, instance), + isParametrized + ) + )); + } - pushPrecalculatedInfo(staticInit, annotationMetadata); + private MethodDef getInjectMethod(List injectCommands) { + return MethodDef.override(INJECT_BEAN_METHOD) + .build((aThis, methodParameters) -> { + return methodParameters.get(2).cast(beanTypeDef).newLocal("beanInstance", instanceVar -> { + InjectMethodSignature injectMethodSignature = new InjectMethodSignature(aThis, methodParameters, instanceVar); + List statements = new ArrayList<>(); + boolean hasInjectPoint = false; + for (InjectMethodCommand injectCommand : injectCommands) { + statements.add(getInjectStatement(injectCommand, injectMethodSignature)); + hasInjectPoint |= injectCommand.hasInjectScope(); + } + List returnStatements = new ArrayList<>(); + if (hasInjectPoint) { + returnStatements.add(destroyInjectScopeBeansIfNecessary(methodParameters)); + } + returnStatements.add(instanceVar.returning()); - addInnerConfigurationMethod(staticInit); - addGetExposedTypes(staticInit); - addConditions(staticInit); + statements.addAll(returnStatements); - // Defaults can be contributed by other static initializers, it should be at the end - AbstractAnnotationMetadataWriter.writeAnnotationDefault(classWriter, staticInit, beanDefinitionType, annotationMetadata, defaultsStorage, loadTypeMethods); + if (isConfigurationProperties) { + return aThis.invoke( + CONTAINS_PROPERTIES_METHOD, - staticInit.returnValue(); - staticInit.visitMaxs(DEFAULT_MAX_STACK, defaultsStorage.size() + 3); - staticInit.visitEnd(); + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext + ).ifTrue( + StatementDef.multi(statements), + StatementDef.multi(returnStatements) + ); + } + return StatementDef.multi(statements); + }); + }); } - private void addConditions(GeneratorAdapter staticInit) { - List preConditions = new ArrayList<>(); - List postConditions = new ArrayList<>(); - List> requirements = annotationMetadata.getAnnotationValuesByType(Requires.class); - if (requirements.isEmpty()) { - return; + private StatementDef getInjectStatement(InjectMethodCommand injectionPoint, InjectMethodSignature injectMethodSignature) { + if (injectionPoint instanceof SetterInjectionInjectCommand setterInjectionInjectCommand) { + return setSetterValue( + injectMethodSignature, + setterInjectionInjectCommand.declaringType, + setterInjectionInjectCommand.methodElement, + setterInjectionInjectCommand.annotationMetadata, + setterInjectionInjectCommand.requiresReflection, + setterInjectionInjectCommand.isOptional + ); } - List> dynamicRequirements = new ArrayList<>(); - for (AnnotationValue requirement : requirements) { - if (requirement.getValues().values().stream().anyMatch(value -> value instanceof EvaluatedExpressionReference)) { - dynamicRequirements.add(requirement); - continue; + if (injectionPoint instanceof InjectFieldInjectCommand injectFieldInjectCommand) { + return injectField( + injectMethodSignature, + injectFieldInjectCommand.declaringType, + injectFieldInjectCommand.fieldElement, + injectFieldInjectCommand.fieldElement.getAnnotationMetadata(), + injectFieldInjectCommand.requiresReflection + ); + } + if (injectionPoint instanceof InjectMethodInjectCommand injectMethodInjectCommand) { + return injectMethod( + injectMethodInjectCommand.methodElement, + injectMethodInjectCommand.requiresReflection, + injectMethodSignature.aThis, + injectMethodSignature.methodParameters, + injectMethodSignature.instanceVar, + injectMethodInjectCommand.methodIndex + ); + } + if (injectionPoint instanceof InjectFieldValueInjectCommand injectFieldValueInjectCommand) { + return setFieldValue( + injectMethodSignature, + injectFieldValueInjectCommand.declaringType, + injectFieldValueInjectCommand.fieldElement, + injectFieldValueInjectCommand.requiresReflection, + injectFieldValueInjectCommand.isOptional + ); + } + if (injectionPoint instanceof ConfigMethodBuilderInjectPointCommand configBuilderMethodInjectPoint) { + String factoryMethod = configBuilderMethodInjectPoint.configBuilderState.getAnnotationMetadata() + .stringValue(ConfigurationBuilder.class, "factoryMethod").orElse(null); + + ClassTypeDef builderType = ClassTypeDef.of(configBuilderMethodInjectPoint.type); + if (StringUtils.isNotEmpty(factoryMethod)) { + return builderType.invokeStatic(factoryMethod, builderType).newLocal("builder" + NameUtils.capitalize(configBuilderMethodInjectPoint.methodName), builderVar -> { + List statements = + getBuilderMethodStatements(injectMethodSignature, configBuilderMethodInjectPoint.builderPoints, builderVar); + + String propertyName = NameUtils.getPropertyNameForGetter(configBuilderMethodInjectPoint.methodName); + String setterName = NameUtils.setterNameFor(propertyName); + + statements.add(injectMethodSignature.instanceVar + .invoke(setterName, TypeDef.VOID, builderVar)); + + return StatementDef.multi(statements); + }); + } else { + return injectMethodSignature.instanceVar + .invoke(configBuilderMethodInjectPoint.methodName, builderType) + .newLocal("builder" + NameUtils.capitalize(configBuilderMethodInjectPoint.methodName), builderVar -> StatementDef.multi( + getBuilderMethodStatements(injectMethodSignature, configBuilderMethodInjectPoint.builderPoints, builderVar) + )); } - MatchesConditionUtils.createConditions(requirement, preConditions, postConditions); } - if (!dynamicRequirements.isEmpty()) { - MutableAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata(); - for (AnnotationValue requirement : requirements) { - annotationMetadata.addRepeatable(Requirements.class.getName(), requirement); + if (injectionPoint instanceof ConfigFieldBuilderInjectCommand configBuilderFieldInjectPoint) { + String factoryMethod = configBuilderFieldInjectPoint.configBuilderState.getAnnotationMetadata() + .stringValue(ConfigurationBuilder.class, "factoryMethod").orElse(null); + ClassTypeDef builderType = ClassTypeDef.of(configBuilderFieldInjectPoint.type); + if (StringUtils.isNotEmpty(factoryMethod)) { + return builderType.invokeStatic(factoryMethod, builderType).newLocal("builder" + NameUtils.capitalize(configBuilderFieldInjectPoint.field), builderVar -> { + List statements = getBuilderMethodStatements(injectMethodSignature, configBuilderFieldInjectPoint.builderPoints, builderVar); + + statements.add(injectMethodSignature.instanceVar + .field(configBuilderFieldInjectPoint.field, builderType) + .put(builderVar)); + + return StatementDef.multi(statements); + }); + } else { + return injectMethodSignature.instanceVar + .field(configBuilderFieldInjectPoint.field, builderType) + .newLocal("builder" + NameUtils.capitalize(configBuilderFieldInjectPoint.field), builderVar -> StatementDef.multi( + getBuilderMethodStatements(injectMethodSignature, configBuilderFieldInjectPoint.builderPoints, builderVar) + )); } - postConditions.add(new MatchesDynamicCondition(annotationMetadata)); } + throw new IllegalStateException(); + } - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_PRE_START_CONDITIONS, Type.getType(Condition[].class).getDescriptor(), null, null); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_POST_START_CONDITIONS, Type.getType(Condition[].class).getDescriptor(), null, null); + private List getBuilderMethodStatements(InjectMethodSignature injectMethodSignature, List points, VariableDef builderVar) { + List statements = new ArrayList<>(); + for (ConfigBuilderPointInjectCommand builderPoint : points) { + statements.add( + getConfigBuilderPointStatement(injectMethodSignature, builderVar, builderPoint) + ); + } + return statements; + } - Consumer writer = new Consumer<>() { - @Override - public void accept(Condition condition) { - if (condition instanceof MatchesPropertyCondition matchesPropertyCondition) { - pushRecord(matchesPropertyCondition.getClass(), () -> { - staticInit.push(matchesPropertyCondition.property()); - staticInit.push(matchesPropertyCondition.value()); - staticInit.push(matchesPropertyCondition.defaultValue()); - pushEnumValue(matchesPropertyCondition.condition()); - }); + private StatementDef getConfigBuilderPointStatement(InjectMethodSignature injectMethodSignature, + VariableDef builderVar, + ConfigBuilderPointInjectCommand builderPoint) { + if (builderPoint instanceof ConfigBuilderMethodInjectCommand configBuilderMethodInjectPoint) { + return visitConfigBuilderMethodInternal( + injectMethodSignature, + configBuilderMethodInjectPoint.propertyName, + configBuilderMethodInjectPoint.returnType, + configBuilderMethodInjectPoint.methodName, + configBuilderMethodInjectPoint.paramType, + configBuilderMethodInjectPoint.generics, + false, + configBuilderMethodInjectPoint.path, + builderVar + ); + } + if (builderPoint instanceof ConfigBuilderMethodDurationInjectCommand configBuilderMethodDurationInjectPoint) { + return visitConfigBuilderMethodInternal( + injectMethodSignature, + configBuilderMethodDurationInjectPoint.propertyName, + configBuilderMethodDurationInjectPoint.returnType, + configBuilderMethodDurationInjectPoint.methodName, + ClassElement.of(Duration.class), + Map.of(), + true, + configBuilderMethodDurationInjectPoint.path, + builderVar + ); + } + throw new IllegalStateException(); + } + + private StatementDef setFieldValue(InjectMethodSignature injectMethodSignature, + TypedElement declaringType, + FieldElement fieldElement, + boolean requiresReflection, + boolean isOptional) { + AnnotationMetadata annotationMetadata = fieldElement.getAnnotationMetadata(); + StatementDef setFieldValueStatement = setFieldValue(injectMethodSignature, fieldElement, isOptional, declaringType, requiresReflection, annotationMetadata); + + if (isOptional) { + return getPropertyContainsCheck( + injectMethodSignature, + fieldElement.getType(), + fieldElement.getName(), + annotationMetadata + ).ifTrue(setFieldValueStatement); + } + return setFieldValueStatement; + } + + private StatementDef setFieldValue(InjectMethodSignature injectMethodSignature, + FieldElement fieldElement, + boolean isOptional, + TypedElement declaringType, + boolean requiresReflection, + AnnotationMetadata annotationMetadata) { + if (isInnerType(fieldElement.getGenericType())) { + return injectField(injectMethodSignature, declaringType, fieldElement, annotationMetadata, requiresReflection); + } + if (!isConfigurationProperties || requiresReflection) { + boolean isRequired = fieldElement + .booleanValue(AnnotationUtil.INJECT, AnnotationUtil.MEMBER_REQUIRED) + .orElse(true); + return visitFieldInjectionPointInternal( + injectMethodSignature, + declaringType, + fieldElement, + annotationMetadata, + requiresReflection, + GET_VALUE_FOR_FIELD, + isOptional, + false, + isRequired + ); + } + fieldInjectionPoints.add(new FieldVisitData(declaringType, fieldElement, annotationMetadata, false)); + int fieldIndex = fieldInjectionPoints.size() - 1; + ExpressionDef value; + Optional property = annotationMetadata.stringValue(Property.class, "name"); + if (property.isPresent()) { + value = getInvokeGetPropertyValueForField(injectMethodSignature, fieldElement, annotationMetadata, property.get(), fieldIndex); + } else { + Optional valueValue = annotationMetadata.stringValue(Value.class); + if (valueValue.isPresent()) { + value = getInvokeGetPropertyPlaceholderValueForField(injectMethodSignature, fieldElement, annotationMetadata, valueValue.get(), fieldIndex); + } else { + // ??? + value = ExpressionDef.nullValue(); + } + } + return injectMethodSignature.instanceVar.field(fieldElement).put(value); + } + + private StatementDef onBeanInstance(VariableDef.This aThis, + List methodParameters, + BuildMethodDefinition buildMethodDefinition, + ExpressionDef beanInstance) { + boolean needsInjectMethod = !injectCommands.isEmpty() || superBeanDefinition; + boolean needsInjectScope = hasInjectScope(buildMethodDefinition.getParameters()); + boolean needsPostConstruct = buildMethodDefinition.postConstruct != null; + if (!needsInjectScope && !needsInjectMethod && !needsPostConstruct) { + return beanInstance.returning(); + } + return beanInstance.newLocal("instance", instanceVar -> { + List statements = new ArrayList<>(); + if (needsInjectMethod) { + statements.add( + aThis.invoke(INJECT_BEAN_METHOD, methodParameters.get(0), methodParameters.get(1), instanceVar) + ); + } + if (needsInjectScope) { + statements.add( + destroyInjectScopeBeansIfNecessary(methodParameters) + ); + } + if (needsPostConstruct) { + statements.add( + aThis.invoke(METHOD_INITIALIZE, + + methodParameters.get(0), + methodParameters.get(1), + instanceVar + ).returning() + ); + } else { + statements.add(instanceVar.returning()); + } + return StatementDef.multi(statements); + }); + } + + private MethodDef buildDisposeMethod(BuildMethodLifecycleDefinition def, MethodDef.MethodDefBuilder override) { + return buildLifeCycleMethod(override, PRE_DESTROY_METHOD, def); + } + + private MethodDef buildInitializeMethod(BuildMethodLifecycleDefinition def, MethodDef.MethodDefBuilder override) { + return buildLifeCycleMethod(override, POST_CONSTRUCT_METHOD, def); + } + + private MethodDef buildLifeCycleMethod(MethodDef.MethodDefBuilder methodDefBuilder, + Method superMethod, + BuildMethodLifecycleDefinition lifeCycleDefinition) { + return methodDefBuilder.build((aThis, methodParameters) -> { + return aThis.invoke(superMethod, methodParameters).cast(beanTypeDef).newLocal("beanInstance", beanInstance -> { + List statements = new ArrayList<>(); + boolean hasInjectScope = false; + for (InjectMethodBuildCommand injectionPoint : lifeCycleDefinition.injectionPoints) { + statements.add(injectMethod(injectionPoint.methodElement, injectionPoint.requiresReflection, aThis, methodParameters, beanInstance, injectionPoint.methodIndex)); + if (!hasInjectScope) { + for (ParameterElement parameter : injectionPoint.methodElement.getSuspendParameters()) { + if (hasInjectScope(parameter)) { + hasInjectScope = true; + break; + } + } + } + } + if (hasInjectScope) { + statements.add( + destroyInjectScopeBeansIfNecessary(methodParameters) + ); + } + statements.add(beanInstance.returning()); + return StatementDef.multi(statements); + }); + }); + } + + private StatementDef buildInstance(VariableDef.This aThis, + List methodParameters, + BuildMethodDefinition buildMethodDefinition, + Function onBeanInstance, + boolean isParametrized) { + Class constructorType; + if (constructor instanceof MethodElement) { + constructorType = AbstractInitializableBeanDefinition.MethodReference.class; + } else { + constructorType = AbstractInitializableBeanDefinition.FieldReference.class; + } + if (buildMethodDefinition instanceof FactoryBuildMethodDefinition factoryBuildMethodDefinition) { + if (factoryBuildMethodDefinition.parameters.length > 0) { + return beanDefinitionTypeDef + .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) + .cast(constructorType) + .newLocal("constructorDef", constructorMethodVar -> { + List values = getConstructorArgumentValues(aThis, methodParameters, List.of(buildMethodDefinition.getParameters()), isParametrized, constructorMethodVar); + return buildFactoryGet(aThis, methodParameters, onBeanInstance, factoryBuildMethodDefinition, values); + }); + } + return buildFactoryGet(aThis, methodParameters, onBeanInstance, factoryBuildMethodDefinition, List.of()); + } + if (buildMethodDefinition instanceof ConstructorBuildMethodDefinition constructorBuildMethodDefinition) { + if (constructorBuildMethodDefinition.constructor.hasParameters()) { + return beanDefinitionTypeDef + .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) + .cast(constructorType) + .newLocal("constructorDef", constructorMethodVar -> { + List values = getConstructorArgumentValues(aThis, methodParameters, List.of(buildMethodDefinition.getParameters()), isParametrized, constructorMethodVar); + return buildConstructorInstantiate(aThis, methodParameters, onBeanInstance, constructorBuildMethodDefinition, values); + }); + } + return buildConstructorInstantiate(aThis, methodParameters, onBeanInstance, constructorBuildMethodDefinition, List.of()); + } + throw new IllegalStateException("Unknown build method definition: " + buildMethodDefinition); + } + + private StatementDef buildConstructorInstantiate(VariableDef.This aThis, List methodParameters, Function onBeanInstance, ConstructorBuildMethodDefinition constructorBuildMethodDefinition, List values) { + List parameters = List.of(constructorBuildMethodDefinition.constructor.getSuspendParameters()); + if (isConstructorIntercepted(constructorBuildMethodDefinition.constructor)) { + ClassTypeDef factoryInterceptor = createConstructorInterceptor(constructorBuildMethodDefinition); + return onBeanInstance.apply( + invokeConstructorChain( + aThis, + methodParameters, + factoryInterceptor.instantiate(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP, aThis), + TypeDef.OBJECT.array().instantiate(values), + parameters) + ); + } + return onBeanInstance.apply( + initializeBean(aThis, methodParameters, constructorBuildMethodDefinition, values) + ); + } + + private StatementDef buildFactoryGet(VariableDef.This aThis, + List methodParameters, + Function onBeanInstance, + FactoryBuildMethodDefinition factoryBuildMethodDefinition, List values) { + return withGetFactoryBean(methodParameters, factoryBuildMethodDefinition, factoryVar -> { + List parameters = List.of(factoryBuildMethodDefinition.parameters); + if (isConstructorIntercepted(factoryBuildMethodDefinition.factoryElement)) { + ClassTypeDef factoryInterceptor = createFactoryInterceptor(factoryBuildMethodDefinition); + return onBeanInstance.apply( + invokeConstructorChain( + aThis, + methodParameters, + factoryInterceptor.instantiate( + List.of( + ClassTypeDef.of(BeanDefinition.class), + factoryVar.type() + ), aThis, factoryVar), + TypeDef.OBJECT.array().instantiate(values), + parameters) + ); + } + return onBeanInstance.apply( + getBeanFromFactory(factoryBuildMethodDefinition, factoryVar, values) + ); + }); + } + + private ExpressionDef getBeanFromFactory(FactoryBuildMethodDefinition factoryBuildMethodDefinition, + ExpressionDef factoryVar, + List values) { + ClassTypeDef factoryType = ClassTypeDef.of(factoryBuildMethodDefinition.factoryClass); + Element factoryElement = factoryBuildMethodDefinition.factoryElement; + if (factoryElement instanceof MethodElement methodElement) { + if (methodElement.isReflectionRequired()) { + return TYPE_REFLECTION_UTILS.invokeStatic( + METHOD_INVOKE_INACCESSIBLE_METHOD, + + methodElement.isStatic() ? ExpressionDef.nullValue() : factoryVar, + DispatchWriter.getTypeUtilsGetRequiredMethod(factoryType, methodElement), + TypeDef.OBJECT.array().instantiate(values) + ); + } + if (methodElement.isStatic()) { + return factoryType.invokeStatic(methodElement, values); + } + return factoryVar.invoke(methodElement, values); + } + FieldElement fieldElement = (FieldElement) factoryElement; + if (fieldElement.isReflectionRequired()) { + return TYPE_REFLECTION_UTILS.invokeStatic( + GET_FIELD_WITH_REFLECTION_METHOD, + + ExpressionDef.constant(factoryType), + ExpressionDef.constant(fieldElement.getName()), + fieldElement.isStatic() ? ExpressionDef.nullValue() : factoryVar + ); + } + if (fieldElement.isStatic()) { + return factoryType.getStaticField(factoryElement.getName(), beanTypeDef); + } + return factoryVar.field(factoryElement.getName(), beanTypeDef); + } + + private ExpressionDef initializeBean(VariableDef.This aThis, + List methodParameters, + ConstructorBuildMethodDefinition constructorBuildMethodDefinition, + List values) { + MethodElement constructor = constructorBuildMethodDefinition.constructor; + List hasValuesExpressions; + if (values == null) { + hasValuesExpressions = null; + } else { + hasValuesExpressions = new ArrayList<>(); + ParameterElement[] parameters = constructorBuildMethodDefinition.getParameters(); + for (int i = 0; i < values.size(); i++) { + ExpressionDef value = values.get(i); + ParameterElement parameter = parameters[i]; + if (parameter.hasAnnotation(Property.class)) { + hasValuesExpressions.add( + getContainsPropertyCheck(aThis, methodParameters, parameter) + ); + } else { + hasValuesExpressions.add(value.isNonNull()); + } + } + + } + return MethodGenUtils.invokeBeanConstructor(constructor, constructorBuildMethodDefinition.requiresReflection, true, values, hasValuesExpressions); + } + + private ExpressionDef getContainsPropertyCheck(VariableDef.This aThis, + List methodParameters, + ParameterElement parameterElement) { + String propertyName = parameterElement.stringValue(Property.class, "name").orElseThrow(); + + return aThis.invoke( + isMultiValueProperty(parameterElement.getType()) ? CONTAINS_PROPERTIES_VALUE_METHOD : CONTAINS_PROPERTY_VALUE_METHOD, + + methodParameters.get(0), + methodParameters.get(1), + ExpressionDef.constant(propertyName) + ); + } + + private StatementDef withGetFactoryBean(List parameters, + FactoryBuildMethodDefinition factoryBuildMethodDefinition, + Function fn) { + if (factoryBuildMethodDefinition.factoryElement.isStatic()) { + return fn.apply(ExpressionDef.nullValue()); + } + + // for Factory beans first we need to look up the factory bean + // before invoking the method to instantiate + // the below code looks up the factory bean. + + TypeDef factoryTypeDef = TypeDef.erasure(factoryBuildMethodDefinition.factoryClass); + + ExpressionDef argumentExpression = ClassTypeDef.of(Argument.class).invokeStatic(ArgumentExpUtils.METHOD_CREATE_ARGUMENT_SIMPLE, + ExpressionDef.constant(factoryTypeDef), + ExpressionDef.constant("factory") + ); + + return StatementDef.multi( + parameters.get(1).cast(DefaultBeanContext.class) + .invoke(METHOD_GET_BEAN, + // load the first argument of the method (the BeanResolutionContext) to be passed to the method + parameters.get(0), + // second argument is the bean type + ExpressionDef.constant(factoryTypeDef), + // third argument is the qualifier for the factory if any + getQualifier(factoryBuildMethodDefinition.factoryClass, argumentExpression) + ).cast(factoryTypeDef).newLocal("factoryBean", factoryBeanVar -> StatementDef.multi( + parameters.get(0).invoke(METHOD_BEAN_RESOLUTION_CONTEXT_MARK_FACTORY), + fn.apply(factoryBeanVar) + )) + ); + } + + private ClassTypeDef createConstructorInterceptor(ConstructorBuildMethodDefinition constructorBuildMethodDefinition) { + String interceptedConstructorWriterName = "ConstructorInterceptor"; + ClassDef.ClassDefBuilder innerClassBuilder = ClassDef.builder(interceptedConstructorWriterName) + .addModifiers(Modifier.FINAL) + .superclass(ClassTypeDef.of(AbstractBeanDefinitionBeanConstructor.class)) + .addAnnotation(Generated.class); + + innerClassBuilder.addMethod( + MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .addParameters(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP.getParameterTypes()) + .build((aThis, methodParameters) + -> aThis.superRef().invokeConstructor(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP, methodParameters.get(0))) + ); + + innerClassBuilder.addMethod( + MethodDef.override(METHOD_BEAN_CONSTRUCTOR_INSTANTIATE) + .build((aThis, methodParameters) -> { + ParameterElement[] parameters = constructorBuildMethodDefinition.constructor.getSuspendParameters(); + List values = IntStream.range(0, parameters.length) + .mapToObj(index -> methodParameters.get(0).arrayElement(index).cast(TypeDef.erasure(parameters[index].getType()))) + .toList(); + return MethodGenUtils.invokeBeanConstructor(constructorBuildMethodDefinition.constructor, true, values) + .returning(); + }) + ); + + classDefBuilder.addInnerType(innerClassBuilder.build()); + + return ClassTypeDef.of(beanDefinitionName + "$" + interceptedConstructorWriterName); + } + + private ClassTypeDef createFactoryInterceptor(FactoryBuildMethodDefinition factoryBuildMethodDefinition) { + String interceptedConstructorWriterName = "ConstructorInterceptor"; + + ClassDef.ClassDefBuilder innerClassBuilder = ClassDef.builder(interceptedConstructorWriterName) + .addModifiers(Modifier.FINAL) + .superclass(ClassTypeDef.of(AbstractBeanDefinitionBeanConstructor.class)) + .addAnnotation(Generated.class); + + + // for factory methods we have to store the factory instance in a field and modify the constructor pass the factory instance + ClassTypeDef factoryType = ClassTypeDef.of(factoryBuildMethodDefinition.factoryClass); + + FieldDef factoryField = FieldDef.builder("$factory", factoryType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + innerClassBuilder.addField(factoryField); + + innerClassBuilder.addMethod( + MethodDef.constructor() + .addModifiers(Modifier.PROTECTED) + .addParameters(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP.getParameterTypes()) + .addParameters(factoryType) + .build((aThis, methodParameters) + -> StatementDef.multi( + aThis.superRef().invokeConstructor(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP, methodParameters.get(0)), + aThis.field(factoryField).put(methodParameters.get(1)) + )) + ); + + // now we need to implement the invoke method to execute the actual instantiation + + innerClassBuilder.addMethod( + MethodDef.override(METHOD_BEAN_CONSTRUCTOR_INSTANTIATE) + .build((aThis, methodParameters) -> { + List values = IntStream.range(0, factoryBuildMethodDefinition.parameters.length) + .mapToObj(index -> methodParameters.get(0) + .arrayElement(index) + .cast(TypeDef.erasure(factoryBuildMethodDefinition.parameters[index].getType()))) + .toList(); + return getBeanFromFactory(factoryBuildMethodDefinition, aThis.field(factoryField), values).returning(); + }) + ); + + classDefBuilder.addInnerType(innerClassBuilder.build()); + + return ClassTypeDef.of(beanDefinitionName + "$" + interceptedConstructorWriterName); + } + + private StaticBlock getStaticInitializer() { + List statements = new ArrayList<>(); + + AnnotationMetadataGenUtils.writeAnnotationDefault(statements, beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); + + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); + + classDefBuilder.addField(annotationMetadataField); + + FieldDef failedInitializationField = FieldDef.builder(FIELD_FAILED_INITIALIZATION, Throwable.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(failedInitializationField); + + List initStatements = new ArrayList<>(); + List failStatements = new ArrayList<>(); + + FieldDef constructorRefField = FieldDef.builder(FIELD_CONSTRUCTOR, AbstractInitializableBeanDefinition.MethodOrFieldReference.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(constructorRefField); + initStatements.add(beanDefinitionTypeDef.getStaticField(constructorRefField).put(getConstructorRef())); + + FieldDef injectionMethodsField = null; + FieldDef injectionFieldsField = null; + FieldDef annotationInjectionsFieldType = null; + FieldDef typeArgumentsField = null; + FieldDef executableMethodsField = null; + + boolean hasMethodInjection = !superBeanDefinition && !allMethodVisits.isEmpty(); + if (hasMethodInjection) { + + TypeDef.Array methodReferenceArray = ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodReference.class).array(); + injectionMethodsField = FieldDef.builder(FIELD_INJECTION_METHODS, methodReferenceArray) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(injectionMethodsField); + initStatements.add(beanDefinitionTypeDef.getStaticField(injectionMethodsField) + .put(methodReferenceArray.instantiate(allMethodVisits.stream() + .map(md -> getNewMethodReference(md.beanType, md.methodElement, md.annotationMetadata, md.postConstruct, md.preDestroy)) + .toList()))); + failStatements.add(beanDefinitionTypeDef.getStaticField(injectionMethodsField).put(ExpressionDef.nullValue())); + } + boolean hasFieldInjection = !fieldInjectionPoints.isEmpty(); + if (hasFieldInjection) { + + TypeDef.Array fieldReferenceArray = ClassTypeDef.of(AbstractInitializableBeanDefinition.FieldReference.class).array(); + injectionFieldsField = FieldDef.builder(FIELD_INJECTION_FIELDS, fieldReferenceArray) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + classDefBuilder.addField(injectionFieldsField); + + initStatements.add(beanDefinitionTypeDef.getStaticField(injectionFieldsField) + .put(fieldReferenceArray.instantiate(fieldInjectionPoints.stream() + .map(fd -> getNewFieldReference(fd.beanType, fd.fieldElement, fd.annotationMetadata)) + .toList()))); + failStatements.add(beanDefinitionTypeDef.getStaticField(injectionFieldsField).put(ExpressionDef.nullValue())); + } + + boolean hasAnnotationInjection = !annotationInjectionPoints.isEmpty(); + if (hasAnnotationInjection) { + TypeDef.Array annotationInjectionsFieldArray = ClassTypeDef.of(AbstractInitializableBeanDefinition.AnnotationReference.class).array(); + annotationInjectionsFieldType = FieldDef.builder(FIELD_ANNOTATION_INJECTIONS, annotationInjectionsFieldArray) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + classDefBuilder.addField(annotationInjectionsFieldType); + + initStatements.add(beanDefinitionTypeDef.getStaticField(annotationInjectionsFieldType) + .put(annotationInjectionsFieldArray.instantiate(annotationInjectionPoints.keySet().stream() + .map(this::getNewAnnotationReference) + .toList()))); + failStatements.add(beanDefinitionTypeDef.getStaticField(annotationInjectionsFieldType).put(ExpressionDef.nullValue())); + } + + boolean hasTypeArguments = !superBeanDefinition && hasTypeArguments(); + if (hasTypeArguments) { + typeArgumentsField = FieldDef.builder(FIELD_TYPE_ARGUMENTS, Map.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + classDefBuilder.addField(typeArgumentsField); + + initStatements.add(beanDefinitionTypeDef.getStaticField(typeArgumentsField) + .put(ExpressionsUtils.stringMapOf( + typeArguments, true, null, el -> ArgumentExpUtils.pushTypeArgumentElements( + annotationMetadata, + beanDefinitionTypeDef, + ClassElement.of(beanDefinitionName), + el, + loadTypeMethods + )) + )); + failStatements.add(beanDefinitionTypeDef.getStaticField(typeArgumentsField).put(ExpressionDef.nullValue())); + } + + boolean hasExecutableMethods = executableMethodsDefinitionWriter != null; + if (hasExecutableMethods) { + ClassTypeDef execType = executableMethodsDefinitionWriter.getClassTypeDef(); + + executableMethodsField = FieldDef.builder(FIELD_EXECUTABLE_METHODS, execType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + classDefBuilder.addField(executableMethodsField); + + initStatements.add(beanDefinitionTypeDef.getStaticField(executableMethodsField).put(execType.instantiate())); + failStatements.add(beanDefinitionTypeDef.getStaticField(executableMethodsField).put(ExpressionDef.nullValue())); + } + + ClassTypeDef precalculatedInfoType = ClassTypeDef.of(AbstractInitializableBeanDefinition.PrecalculatedInfo.class); + FieldDef precalculatedInfoField = FieldDef.builder(FIELD_PRECALCULATED_INFO, precalculatedInfoType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(precalculatedInfoField); + String scope = annotationMetadata.getAnnotationNameByStereotype(AnnotationUtil.SCOPE).orElse(null); + + statements.add( + beanDefinitionTypeDef.getStaticField(precalculatedInfoField) + .put( + precalculatedInfoType.instantiate( + PRECALCULATED_INFO_CONSTRUCTOR, + + // 1: `Optional` scope + scope == null ? TYPE_OPTIONAL.invokeStatic(METHOD_OPTIONAL_EMPTY) + : TYPE_OPTIONAL.invokeStatic(METHOD_OPTIONAL_OF, ExpressionDef.constant(scope)), + // 2: `boolean` isAbstract + ExpressionDef.constant(isAbstract), + // 3: `boolean` isIterable + ExpressionDef.constant(isIterable(annotationMetadata)), + // 4: `boolean` isSingleton + ExpressionDef.constant(isSingleton(scope)), + // 5: `boolean` isPrimary + ExpressionDef.constant(annotationMetadata.hasDeclaredStereotype(Primary.class)), + // 6: `boolean` isConfigurationProperties + ExpressionDef.constant(isConfigurationProperties), + // 7: isContainerType + ExpressionDef.constant(isContainerType()), + // 8: preprocessMethods + ExpressionDef.constant(preprocessMethods), + // 9: hasEvaluatedExpressions + ExpressionDef.constant(evaluatedExpressionProcessor.hasEvaluatedExpressions()) + + ) + ) + ); + + statements.add( + StatementDef.doTry( + StatementDef.multi( + initStatements + ) + ).doCatch(Throwable.class, exceptionVar -> StatementDef.multi( + beanDefinitionTypeDef.getStaticField(failedInitializationField).put(exceptionVar), + StatementDef.multi(failStatements) + )) + ); + + statements.add(addInnerConfigurationMethod()); + statements.add(addGetExposedTypes()); + + FieldDef preStartConditionsField = null; + FieldDef postStartConditionsField = null; + + List> requirements = annotationMetadata.getAnnotationValuesByType(Requires.class); + if (!requirements.isEmpty()) { + TypeDef.Array conditionsArrayType = ClassTypeDef.of(Condition.class).array(); + preStartConditionsField = FieldDef.builder(FIELD_PRE_START_CONDITIONS, conditionsArrayType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + postStartConditionsField = FieldDef.builder(FIELD_POST_START_CONDITIONS, conditionsArrayType) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(preStartConditionsField); + classDefBuilder.addField(postStartConditionsField); + + statements.add(addConditions(requirements, preStartConditionsField, postStartConditionsField)); + } + + // Defaults can be contributed by other static initializers, it should be at the end + AnnotationMetadataGenUtils.writeAnnotationDefault(statements, beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); + + return new StaticBlock( + StatementDef.multi(statements), + annotationMetadataField, + failedInitializationField, + constructorRefField, + injectionMethodsField, + injectionFieldsField, + annotationInjectionsFieldType, + typeArgumentsField, + executableMethodsField, + precalculatedInfoField, + preStartConditionsField, + postStartConditionsField + ); + } + + private ExpressionDef getConstructorRef() { + if (constructor instanceof MethodElement methodElement) { + ParameterElement[] parameters = methodElement.getParameters(); + List parameterList = Arrays.asList(parameters); + applyDefaultNamedToParameters(parameterList); + + return getNewMethodReference(methodElement.getDeclaringType(), methodElement, methodElement.getAnnotationMetadata(), false, false); + } else if (constructor instanceof FieldElement fieldConstructor) { + return getNewFieldReference(fieldConstructor.getDeclaringType(), fieldConstructor, fieldConstructor.getAnnotationMetadata()); + } else { + throw new IllegalArgumentException("Unexpected constructor: " + constructor); + } + } + + private StatementDef addConditions(List> requirements, FieldDef preStartConditionsField, FieldDef postStartConditionsField) { + List preConditions = new ArrayList<>(); + List postConditions = new ArrayList<>(); + if (requirements.isEmpty()) { + return StatementDef.multi(); + } + List> dynamicRequirements = new ArrayList<>(); + for (AnnotationValue requirement : requirements) { + if (requirement.getValues().values().stream().anyMatch(value -> value instanceof EvaluatedExpressionReference)) { + dynamicRequirements.add(requirement); + continue; + } + MatchesConditionUtils.createConditions(requirement, preConditions, postConditions); + } + if (!dynamicRequirements.isEmpty()) { + MutableAnnotationMetadata annotationMetadata = new MutableAnnotationMetadata(); + for (AnnotationValue requirement : requirements) { + annotationMetadata.addRepeatable(Requirements.class.getName(), requirement); + } + postConditions.add(new MatchesDynamicCondition(annotationMetadata)); + } + + Function writer = new Function<>() { + @Override + public ExpressionDef apply(Condition condition) { + if (condition instanceof MatchesPropertyCondition matchesPropertyCondition) { + return newRecord( + matchesPropertyCondition.getClass(), + ExpressionDef.constant(matchesPropertyCondition.property()), + ExpressionDef.constant(matchesPropertyCondition.value()), + ExpressionDef.constant(matchesPropertyCondition.defaultValue()), + ExpressionDef.constant(matchesPropertyCondition.condition()) + ); } else if (condition instanceof MatchesAbsenceOfBeansCondition matchesAbsenceOfBeansCondition) { - pushRecord(matchesAbsenceOfBeansCondition.getClass(), () -> - pushAnnotationClassValues(matchesAbsenceOfBeansCondition.missingBeans())); + return newRecord( + matchesAbsenceOfBeansCondition.getClass(), + getAnnotationClassValues(matchesAbsenceOfBeansCondition.missingBeans()) + ); } else if (condition instanceof MatchesPresenceOfBeansCondition matchesPresenceOfBeansCondition) { - pushRecord(matchesPresenceOfBeansCondition.getClass(), () -> - pushAnnotationClassValues(matchesPresenceOfBeansCondition.beans())); + return newRecord( + matchesPresenceOfBeansCondition.getClass(), + getAnnotationClassValues(matchesPresenceOfBeansCondition.beans()) + ); } else if (condition instanceof MatchesAbsenceOfClassesCondition matchesAbsenceOfClassesCondition) { - pushRecord(matchesAbsenceOfClassesCondition.getClass(), () -> - pushAnnotationClassValues(matchesAbsenceOfClassesCondition.classes())); + return newRecord( + matchesAbsenceOfClassesCondition.getClass(), + getAnnotationClassValues(matchesAbsenceOfClassesCondition.classes()) + ); } else if (condition instanceof MatchesPresenceOfClassesCondition matchesPresenceOfClassesCondition) { - pushRecord(matchesPresenceOfClassesCondition.getClass(), () -> - pushAnnotationClassValues(matchesPresenceOfClassesCondition.classes())); + return newRecord( + matchesPresenceOfClassesCondition.getClass(), + getAnnotationClassValues(matchesPresenceOfClassesCondition.classes()) + ); } else if (condition instanceof MatchesPresenceOfEntitiesCondition matchesPresenceOfEntitiesCondition) { - pushRecord(matchesPresenceOfEntitiesCondition.getClass(), () -> - pushAnnotationClassValues(matchesPresenceOfEntitiesCondition.classes())); + return newRecord( + matchesPresenceOfEntitiesCondition.getClass(), + getAnnotationClassValues(matchesPresenceOfEntitiesCondition.classes()) + ); } else if (condition instanceof MatchesAbsenceOfClassNamesCondition matchesAbsenceOfClassNamesCondition) { - pushRecord(matchesAbsenceOfClassNamesCondition.getClass(), () -> - pushNewArray(staticInit, String.class, matchesAbsenceOfClassNamesCondition.classes(), staticInit::push)); + return newRecord( + matchesAbsenceOfClassNamesCondition.getClass(), + ExpressionDef.constant(matchesAbsenceOfClassNamesCondition.classes()) + ); } else if (condition instanceof MatchesConfigurationCondition matchesConfigurationCondition) { - pushRecord(matchesConfigurationCondition.getClass(), () -> { - staticInit.push(matchesConfigurationCondition.configurationName()); - staticInit.push(matchesConfigurationCondition.minimumVersion()); - }); + return newRecord( + matchesConfigurationCondition.getClass(), + ExpressionDef.constant(matchesConfigurationCondition.configurationName()), + ExpressionDef.constant(matchesConfigurationCondition.minimumVersion()) + ); } else if (condition instanceof MatchesCurrentNotOsCondition matchesCurrentNotOsCondition) { - pushRecord(matchesCurrentNotOsCondition.getClass(), () -> { - pushNewArray(staticInit, Requires.Family.class, matchesCurrentNotOsCondition.notOs(), this::pushEnumValue); - try { - staticInit.invokeStatic( - Type.getType(CollectionUtils.class), - org.objectweb.asm.commons.Method.getMethod( - CollectionUtils.class.getMethod("enumSet", Enum[].class) + return newRecord( + matchesCurrentNotOsCondition.getClass(), + ClassTypeDef.of(CollectionUtils.class) + .invokeStatic( + COLLECTION_UTILS_ENUM_SET_METHOD, + + ClassTypeDef.of(Requires.Family.class).array().instantiate( + matchesCurrentNotOsCondition.notOs().stream().map(ExpressionDef::constant).toList() ) - ); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - }); + ) + ); } else if (condition instanceof MatchesCurrentOsCondition currentOsCondition) { - pushRecord(currentOsCondition.getClass(), () -> { - pushNewArray(staticInit, Requires.Family.class, currentOsCondition.os(), this::pushEnumValue); - try { - staticInit.invokeStatic( - Type.getType(CollectionUtils.class), - org.objectweb.asm.commons.Method.getMethod( - CollectionUtils.class.getMethod("enumSet", Enum[].class) + return newRecord( + currentOsCondition.getClass(), + ClassTypeDef.of(CollectionUtils.class) + .invokeStatic( + COLLECTION_UTILS_ENUM_SET_METHOD, + + ClassTypeDef.of(Requires.Family.class).array().instantiate( + currentOsCondition.os().stream().map(ExpressionDef::constant).toList() ) - ); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - }); + ) + ); } else if (condition instanceof MatchesCustomCondition matchesCustomCondition) { - pushRecord(matchesCustomCondition.getClass(), () -> - pushAnnotationClassValue(matchesCustomCondition.customConditionClass())); + return newRecord( + matchesCustomCondition.getClass(), + getAnnotationClassValue(matchesCustomCondition.customConditionClass()) + ); } else if (condition instanceof MatchesEnvironmentCondition matchesEnvironmentCondition) { - pushRecord(matchesEnvironmentCondition.getClass(), () -> - pushNewArray(staticInit, String.class, matchesEnvironmentCondition.env(), staticInit::push)); + return newRecord( + matchesEnvironmentCondition.getClass(), + ExpressionDef.constant(matchesEnvironmentCondition.env()) + ); } else if (condition instanceof MatchesMissingPropertyCondition matchesMissingPropertyCondition) { - pushRecord(matchesMissingPropertyCondition.getClass(), () -> - staticInit.push(matchesMissingPropertyCondition.property())); + return newRecord( + matchesMissingPropertyCondition.getClass(), + ExpressionDef.constant(matchesMissingPropertyCondition.property()) + ); } else if (condition instanceof MatchesNotEnvironmentCondition matchesNotEnvironmentCondition) { - pushRecord(matchesNotEnvironmentCondition.getClass(), () -> - pushNewArray(staticInit, String.class, matchesNotEnvironmentCondition.env(), staticInit::push)); + return newRecord( + matchesNotEnvironmentCondition.getClass(), + ExpressionDef.constant(matchesNotEnvironmentCondition.env()) + ); } else if (condition instanceof MatchesPresenceOfResourcesCondition matchesPresenceOfResourcesCondition) { - pushRecord(matchesPresenceOfResourcesCondition.getClass(), () -> - pushNewArray(staticInit, String.class, matchesPresenceOfResourcesCondition.resourcePaths(), staticInit::push)); + return newRecord( + matchesPresenceOfResourcesCondition.getClass(), + ExpressionDef.constant(matchesPresenceOfResourcesCondition.resourcePaths()) + ); } else if (condition instanceof MatchesSdkCondition matchesSdkCondition) { - pushRecord(matchesSdkCondition.getClass(), () -> { - pushEnumValue(matchesSdkCondition.sdk()); - staticInit.push(matchesSdkCondition.version()); - }); + return newRecord( + matchesSdkCondition.getClass(), + ExpressionDef.constant(matchesSdkCondition.sdk()), + ExpressionDef.constant(matchesSdkCondition.version()) + ); } else if (condition instanceof MatchesDynamicCondition matchesDynamicCondition) { - pushRecord(matchesDynamicCondition.getClass(), () -> - pushAnnotationMetadata(staticInit, matchesDynamicCondition.annotationMetadata())); + return newRecord( + matchesDynamicCondition.getClass(), + getAnnotationMetadataExpression(matchesDynamicCondition.annotationMetadata()) + ); } else { throw new IllegalStateException("Unsupported condition type: " + condition.getClass().getName()); } } - private void pushEnumValue(Enum anEnum) { - Type type = Type.getType(anEnum.getClass()); - staticInit.getStatic(type, anEnum.name(), type); - } - - private void pushAnnotationClassValues(AnnotationClassValue[] classValues) { - pushNewArray(staticInit, AnnotationClassValue.class, classValues, this::pushAnnotationClassValue); + private ExpressionDef getAnnotationClassValues(AnnotationClassValue[] classValues) { + return ClassTypeDef.of(AnnotationClassValue.class) + .array() + .instantiate(Arrays.stream(classValues).map(this::getAnnotationClassValue).toList()); } - private void pushAnnotationClassValue(AnnotationClassValue annotationClassValue) { - AnnotationMetadataWriter.invokeLoadClassValueMethod(beanDefinitionType, classWriter, staticInit, loadTypeMethods, annotationClassValue); + private ExpressionDef getAnnotationClassValue(AnnotationClassValue annotationClassValue) { + return AnnotationMetadataGenUtils.invokeLoadClassValueMethod(beanDefinitionTypeDef, loadTypeMethods, annotationClassValue); } - private void pushRecord(Class classType, Runnable setValues) { - Type type = Type.getType(classType); - staticInit.newInstance(type); - staticInit.dup(); - setValues.run(); - staticInit.invokeConstructor(type, org.objectweb.asm.commons.Method.getMethod( - classType.getConstructors()[0] - )); + private ExpressionDef newRecord(Class classType, ExpressionDef... values) { + return ClassTypeDef.of(classType).instantiate(classType.getConstructors()[0], values); } }; - pushNewArray(staticInit, Condition.class, preConditions, writer); - staticInit.putStatic(beanDefinitionType, FIELD_PRE_START_CONDITIONS, Type.getType(Condition[].class)); - pushNewArray(staticInit, Condition.class, postConditions, writer); - staticInit.putStatic(beanDefinitionType, FIELD_POST_START_CONDITIONS, Type.getType(Condition[].class)); - } - - private String getSuperTypeInternalType() { - return getSuperType().getInternalName(); - } - - private Type getSuperType() { - return isSuperFactory ? TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE : superType; - } - - private void buildCheckIfShouldLoadMethod(GeneratorAdapter adapter, Map> beanPropertyVisitData) { - List injectedTypes = new ArrayList<>(beanPropertyVisitData.keySet()); - - for (int currentTypeIndex = 0; currentTypeIndex < injectedTypes.size(); currentTypeIndex++) { - Type injectedType = injectedTypes.get(currentTypeIndex); - List annotationVisitData = beanPropertyVisitData.get(injectedType); - boolean multiplePropertiesFromType = annotationVisitData.size() > 1; - - Integer injectedBeanIndex = null; - for (int i = 0; i < annotationVisitData.size(); i++) { - int currentPropertyIndex = i; - boolean isLastProperty = currentTypeIndex == (injectedTypes.size() - 1) && currentPropertyIndex == (annotationVisitData.size() - 1); - - AnnotationVisitData visitData = annotationVisitData.get(currentPropertyIndex); - MethodElement propertyGetter = visitData.memberPropertyGetter; - // load 'this' - adapter.loadThis(); - // push injected bean property name - adapter.push(visitData.memberPropertyName); - - if (injectedBeanIndex != null) { - adapter.loadLocal(injectedBeanIndex); - } else { - // load 'this' - adapter.loadThis(); - // 1st argument load BeanResolutionContext - adapter.loadArg(0); - // 2nd argument load BeanContext - adapter.loadArg(1); - // 3rd argument the injected bean index - adapter.push(currentTypeIndex); - // push qualifier - pushQualifier(adapter, visitData.memberBeanType, () -> resolveAnnotationArgument(adapter, currentPropertyIndex)); - // invoke getBeanForAnnotation - pushInvokeMethodOnSuperClass(adapter, GET_BEAN_FOR_ANNOTATION); - // cast the return value to the correct type - pushCastToType(adapter, visitData.memberBeanType); - - // if multiple properties from same bean should be checked, saving bean to local variable - if (multiplePropertiesFromType) { - injectedBeanIndex = adapter.newLocal(injectedType); - adapter.storeLocal(injectedBeanIndex); - adapter.loadLocal(injectedBeanIndex); - } - } - - org.objectweb.asm.commons.Method propertyGetterMethod = org.objectweb.asm.commons.Method.getMethod(propertyGetter.getDescription(false)); - // getter might be an interface method - if (visitData.memberBeanType.getType().isInterface()) { - adapter.invokeInterface(injectedType, propertyGetterMethod); - } else { - adapter.invokeVirtual(injectedType, propertyGetterMethod); - } - pushBoxPrimitiveIfNecessary(propertyGetterMethod.getReturnType(), adapter); - // pushing required arguments and invoking checkBeanPropertyValue - adapter.push(visitData.requiredValue); - adapter.push(visitData.notEqualsValue); - pushInvokeMethodOnSuperClass(adapter, CHECK_INJECTED_BEAN_PROPERTY_VALUE); - - if (isLastProperty) { - adapter.returnValue(); - } - } - } + TypeDef.Array conditionsArrayType = ClassTypeDef.of(Condition.class).array(); + return StatementDef.multi( + beanDefinitionTypeDef.getStaticField(preStartConditionsField).put( + conditionsArrayType.instantiate(preConditions.stream().map(writer).toList()) + ), + beanDefinitionTypeDef.getStaticField(postStartConditionsField).put( + conditionsArrayType.instantiate(postConditions.stream().map(writer).toList()) + ) + ); } private void processAllBeanElementVisitors() { @@ -1575,8 +2249,8 @@ private void processAllBeanElementVisitors() { } } catch (Exception e) { visitorContext.fail( - "Error occurred visiting BeanElementVisitor of type [" + visitor.getClass().getName() + "]: " + e.getMessage(), - this + "Error occurred visiting BeanElementVisitor of type [" + visitor.getClass().getName() + "]: " + e.getMessage(), + this ); break; } @@ -1584,68 +2258,80 @@ private void processAllBeanElementVisitors() { } } - private void addInnerConfigurationMethod(GeneratorAdapter staticInit) { + private StatementDef addInnerConfigurationMethod() { if (isConfigurationProperties && !beanTypeInnerClasses.isEmpty()) { - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_INNER_CLASSES, Type.getType(Set.class).getDescriptor(), null, null); - pushStoreClassesAsSet(staticInit, beanTypeInnerClasses.toArray(EMPTY_STRING_ARRAY)); - staticInit.putStatic(beanDefinitionType, FIELD_INNER_CLASSES, Type.getType(Set.class)); - - GeneratorAdapter isInnerConfigurationMethod = startProtectedMethod(classWriter, "isInnerConfiguration", boolean.class.getName(), Class.class.getName()); - isInnerConfigurationMethod.getStatic(beanDefinitionType, FIELD_INNER_CLASSES, Type.getType(Set.class)); - isInnerConfigurationMethod.loadArg(0); - isInnerConfigurationMethod.invokeInterface(Type.getType(Collection.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Collection.class, "contains", Object.class) - )); - isInnerConfigurationMethod.returnValue(); - isInnerConfigurationMethod.visitMaxs(1, 1); - isInnerConfigurationMethod.visitEnd(); + FieldDef innerClassesField = FieldDef.builder(FIELD_INNER_CLASSES, Set.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(innerClassesField); + + classDefBuilder.addMethod( + MethodDef.override(IS_INNER_CONFIGURATION_METHOD) + .build((aThis, methodParameters) -> beanDefinitionTypeDef.getStaticField(innerClassesField) + .invoke(CONTAINS_METHOD, methodParameters.get(0)) + .returning()) + ); + + return beanDefinitionTypeDef.getStaticField(innerClassesField).put( + getClassesAsSetExpression(beanTypeInnerClasses.toArray(EMPTY_STRING_ARRAY)) + ); } + return StatementDef.multi(); } - private void addGetExposedTypes(GeneratorAdapter staticInit) { + private StatementDef addGetExposedTypes() { if (annotationMetadata.hasDeclaredAnnotation(Bean.class.getName())) { final String[] exposedTypes = annotationMetadata.stringValues(Bean.class.getName(), "typed"); if (exposedTypes.length > 0) { - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_EXPOSED_TYPES, Type.getType(Set.class).getDescriptor(), null, null); - pushStoreClassesAsSet(staticInit, exposedTypes); - staticInit.putStatic(beanDefinitionType, FIELD_EXPOSED_TYPES, Type.getType(Set.class)); - - GeneratorAdapter getExposedTypesMethod = startPublicMethod(classWriter, "getExposedTypes", Set.class.getName()); - getExposedTypesMethod.getStatic(beanDefinitionType, FIELD_EXPOSED_TYPES, Type.getType(Set.class)); - getExposedTypesMethod.returnValue(); - getExposedTypesMethod.visitMaxs(1, 1); - getExposedTypesMethod.visitEnd(); + FieldDef exposedTypesField = FieldDef.builder(FIELD_EXPOSED_TYPES, Set.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .build(); + + classDefBuilder.addField(exposedTypesField); + + classDefBuilder.addMethod( + MethodDef.override(GET_EXPOSED_TYPES_METHOD) + .build((aThis, methodParameters) -> beanDefinitionTypeDef.getStaticField(exposedTypesField).returning()) + ); + + return beanDefinitionTypeDef.getStaticField(exposedTypesField).put(getClassesAsSetExpression(exposedTypes)); } } + return StatementDef.multi(); } - private void addGetOrder() { + @Nullable + private MethodDef getGetOrder() { int order = OrderUtil.getOrder(annotationMetadata); if (order != 0) { - GeneratorAdapter getOrderMethod = startPublicMethod(classWriter, "getOrder", int.class.getName()); - getOrderMethod.push(order); - getOrderMethod.returnValue(); - getOrderMethod.endMethod(); + return MethodDef.override(GET_ORDER_METHOD) + .build((aThis, methodParameters) -> TypeDef.Primitive.INT.constant(order).returning()); } + return null; } - private void pushStoreClassesAsSet(GeneratorAdapter writer, String[] classes) { + private ExpressionDef getClassesAsSetExpression(String[] classes) { if (classes.length > 1) { - writer.newInstance(Type.getType(HashSet.class)); - writer.dup(); - pushArrayOfClasses(writer, classes); - writer.invokeStatic(Type.getType(Arrays.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Arrays.class, "asList", Object[].class) - )); - writer.invokeConstructor(Type.getType(HashSet.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalConstructor(HashSet.class, Collection.class) - )); - } else { - pushClass(writer, classes[0]); - writer.invokeStatic(Type.getType(Collections.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Collections.class, "singleton", Object.class) - )); + return ClassTypeDef.of(HashSet.class) + .instantiate( + HASH_SET_COLLECTION_CONSTRUCTOR, + + ClassTypeDef.of(Arrays.class) + .invokeStatic( + ARRAYS_AS_LIST_METHOD, + + getArrayOfClasses(classes) + ) + + ); } + return ClassTypeDef.of(Collections.class) + .invokeStatic( + COLLECTIONS_SINGLETON_METHOD, + + asClassExpression(classes[0]) + ); } private boolean hasTypeArguments() { @@ -1669,19 +2355,11 @@ private boolean isSingleton(String scope) { } return annotationMetadata.stringValue(DefaultScope.class) - .map(t -> t.equals(Singleton.class.getName())) - .orElse(false); + .map(t -> t.equals(Singleton.class.getName())) + .orElse(false); } } - private void lookupReferenceAnnotationMetadata(GeneratorAdapter annotationMetadataMethod) { - annotationMetadataMethod.loadThis(); - annotationMetadataMethod.getStatic(beanDefinitionType, AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - annotationMetadataMethod.returnValue(); - annotationMetadataMethod.visitMaxs(1, 1); - annotationMetadataMethod.visitEnd(); - } - /** * @return The bytes of the class */ @@ -1689,7 +2367,7 @@ public byte[] toByteArray() { if (!beanFinalized) { throw new IllegalStateException("Bean definition not finalized. Call visitBeanDefinitionEnd() first."); } - return classWriter.toByteArray(); + return new ByteCodeWriter().write(classDefBuilder.build()); } @Override @@ -1702,139 +2380,164 @@ public void accept(ClassWriterOutputVisitor visitor) throws IOException { beanDefinitionName, getOriginatingElement() ); - try (OutputStream out = visitor.visitClass(getBeanDefinitionName(), getOriginatingElements())) { - if (!innerClasses.isEmpty()) { - for (Map.Entry entry : innerClasses.entrySet()) { - try (OutputStream constructorOut = visitor.visitClass(entry.getKey(), getOriginatingElements())) { - constructorOut.write(entry.getValue().toByteArray()); - } - } + write(visitor, classDefBuilder.build()); + try { + if (executableMethodsDefinitionWriter != null) { + executableMethodsDefinitionWriter.accept(visitor); } - try { - if (executableMethodsDefinitionWriter != null) { - executableMethodsDefinitionWriter.accept(visitor); - } - } catch (RuntimeException e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException exception) { - throw exception; - } else { - throw e; - } + } catch (RuntimeException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException exception) { + throw exception; + } else { + throw e; } - evaluatedExpressionProcessor.writeEvaluatedExpressions(visitor); - out.write(toByteArray()); + } + evaluatedExpressionProcessor.writeEvaluatedExpressions(visitor); + } + + private void write(ClassWriterOutputVisitor visitor, ObjectDef objectDef) throws IOException { + try (OutputStream out = visitor.visitClass(objectDef.getName(), getOriginatingElements())) { + out.write(new ByteCodeWriter().write(objectDef)); + } + for (ObjectDef innerType : objectDef.getInnerTypes()) { + write(visitor, innerType); } } @Override public void visitSetterValue( - TypedElement declaringType, - MethodElement methodElement, - AnnotationMetadata annotationMetadata, - boolean requiresReflection, - boolean isOptional) { + TypedElement declaringType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection, + boolean isOptional) { + + injectCommands.add(new SetterInjectionInjectCommand(declaringType, methodElement, annotationMetadata, requiresReflection, isOptional)); + } + + private StatementDef setSetterValue(InjectMethodSignature injectMethodSignature, + TypedElement declaringType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection, + boolean isOptional) { if (!requiresReflection) { ParameterElement parameter = methodElement.getParameters()[0]; - Label falseCondition = isOptional ? pushPropertyContainsCheck( - injectMethodVisitor, + StatementDef setValueStatement = setSetterValue(injectMethodSignature, declaringType, methodElement, annotationMetadata, parameter); + if (isOptional) { + return getPropertyContainsCheck( + injectMethodSignature, parameter.getType(), parameter.getName(), annotationMetadata - ) : null; - - ClassElement genericType = parameter.getGenericType(); - if (isConfigurationProperties && isValueType(annotationMetadata)) { - - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - - Optional property = annotationMetadata.stringValue(Property.class, "name"); - Optional valueValue = annotationMetadata.stringValue(Value.class); - if (isInnerType(genericType)) { - boolean isArray = genericType.isArray(); - boolean isCollection = genericType.isAssignable(Collection.class); - if (isCollection || isArray) { - ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); - if (typeArgument != null && !typeArgument.isPrimitive()) { - pushInvokeGetBeansOfTypeForSetter(injectMethodVisitor, methodElement.getName(), parameter, annotationMetadata); - } else { - pushInvokeGetBeanForSetter(injectMethodVisitor, methodElement.getName(), parameter, annotationMetadata); - } - } else { - pushInvokeGetBeanForSetter(injectMethodVisitor, methodElement.getName(), parameter, annotationMetadata); - } - } else if (property.isPresent()) { - pushInvokeGetPropertyValueForSetter(injectMethodVisitor, methodElement.getName(), parameter, property.get(), annotationMetadata); - } else if (valueValue.isPresent()) { - pushInvokeGetPropertyPlaceholderValueForSetter(injectMethodVisitor, methodElement.getName(), parameter, valueValue.get(), annotationMetadata); - } else { - throw new IllegalStateException(); - } - - Type declaringTypeRef = JavaModelUtils.getTypeReference(declaringType); - String methodDescriptor = getMethodDescriptor(methodElement.getReturnType(), Arrays.asList(methodElement.getParameters())); - boolean isDeclaringTypeInterface = declaringType.getType().isInterface(); - injectMethodVisitor.visitMethodInsn(isDeclaringTypeInterface ? INVOKEINTERFACE : INVOKEVIRTUAL, - declaringTypeRef.getInternalName(), methodElement.getName(), - methodDescriptor, isDeclaringTypeInterface); + ).ifTrue(setValueStatement); + } + return setValueStatement; + } + final MethodVisitData methodVisitData = new MethodVisitData( + declaringType, + methodElement, + false, + annotationMetadata); + methodInjectionPoints.add(methodVisitData); + allMethodVisits.add(methodVisitData); + return StatementDef.multi(); + } - if (!methodElement.getReturnType().isVoid()) { - injectMethodVisitor.pop(); - } + private StatementDef setSetterValue(InjectMethodSignature injectMethodSignature, + TypedElement declaringType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + ParameterElement parameter) { + ClassElement genericType = parameter.getGenericType(); + if (isConfigurationProperties && isValueType(annotationMetadata)) { - if (keepConfPropInjectPoints) { - final MethodVisitData methodVisitData = new MethodVisitData( - declaringType, - methodElement, - false, - annotationMetadata); - methodInjectionPoints.add(methodVisitData); - allMethodVisits.add(methodVisitData); - currentMethodIndex++; - } - } else { + int methodIndex = -1; + if (keepConfPropInjectPoints) { final MethodVisitData methodVisitData = new MethodVisitData( - declaringType, - methodElement, - false, - annotationMetadata); - visitMethodInjectionPointInternal(methodVisitData, injectMethodVisitor, injectInstanceLocalVarIndex); + declaringType, + methodElement, + false, + annotationMetadata); methodInjectionPoints.add(methodVisitData); allMethodVisits.add(methodVisitData); - currentMethodIndex++; + methodIndex = allMethodVisits.size() - 1; } - if (falseCondition != null) { - injectMethodVisitor.visitLabel(falseCondition); + Function onValue = value -> injectMethodSignature + .instanceVar.invoke(methodElement, value); + + Optional valueValue = annotationMetadata.stringValue(Value.class); + if (isInnerType(genericType)) { + boolean isArray = genericType.isArray(); + boolean isCollection = genericType.isAssignable(Collection.class); + if (isCollection || isArray) { + ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); + if (typeArgument != null && !typeArgument.isPrimitive()) { + return getInvokeGetBeansOfTypeForSetter(injectMethodSignature, methodElement.getName(), parameter, annotationMetadata, onValue, methodIndex); + } + return onValue.apply( + getInvokeGetBeanForSetter(injectMethodSignature, methodElement.getName(), parameter, annotationMetadata, methodIndex) + ); + } + return onValue.apply( + getInvokeGetBeanForSetter(injectMethodSignature, methodElement.getName(), parameter, annotationMetadata, methodIndex) + ); } + Optional property = annotationMetadata.stringValue(Property.class, "name"); + if (property.isPresent()) { + return onValue.apply( + getInvokeGetPropertyValueForSetter(injectMethodSignature, methodElement.getName(), parameter, property.get(), annotationMetadata, methodIndex) + ); + } + if (valueValue.isPresent()) { + return onValue.apply( + getInvokeGetPropertyPlaceholderValueForSetter(injectMethodSignature, methodElement.getName(), parameter, valueValue.get(), annotationMetadata, methodIndex) + ); + } + throw new IllegalStateException(); } else { final MethodVisitData methodVisitData = new MethodVisitData( - declaringType, - methodElement, - false, - annotationMetadata); + declaringType, + methodElement, + false, + annotationMetadata); methodInjectionPoints.add(methodVisitData); allMethodVisits.add(methodVisitData); - currentMethodIndex++; + return injectMethod( + methodElement, + false, + injectMethodSignature.aThis, + injectMethodSignature.methodParameters, + injectMethodSignature.instanceVar, + allMethodVisits.size() - 1 + ); } } @Override public void visitPostConstructMethod(TypedElement declaringType, MethodElement methodElement, - boolean requiresReflection, VisitorContext visitorContext) { - - visitPostConstructMethodDefinition(false); + boolean requiresReflection, + VisitorContext visitorContext) { + buildMethodDefinition.postConstruct(false); // for "super bean definitions" we just delegate to super if (!superBeanDefinition || isInterceptedLifeCycleByType(this.annotationMetadata, "POST_CONSTRUCT")) { MethodVisitData methodVisitData = new MethodVisitData(declaringType, methodElement, requiresReflection, methodElement.getAnnotationMetadata(), true, false); postConstructMethodVisits.add(methodVisitData); allMethodVisits.add(methodVisitData); - visitMethodInjectionPointInternal(methodVisitData, postConstructMethodVisitor, postConstructInstanceLocalVarIndex); - currentMethodIndex++; + buildMethodDefinition.postConstruct.injectionPoints.add(new + InjectMethodBuildCommand( + declaringType, + methodElement, + requiresReflection, + allMethodVisits.size() - 1 + ) + ); } } @@ -1845,13 +2548,17 @@ public void visitPreDestroyMethod(TypedElement declaringType, VisitorContext visitorContext) { // for "super bean definitions" we just delegate to super if (!superBeanDefinition || isInterceptedLifeCycleByType(this.annotationMetadata, "PRE_DESTROY")) { - visitPreDestroyMethodDefinition(false); + buildMethodDefinition.preDestroy(false); MethodVisitData methodVisitData = new MethodVisitData(declaringType, methodElement, requiresReflection, methodElement.getAnnotationMetadata(), false, true); preDestroyMethodVisits.add(methodVisitData); allMethodVisits.add(methodVisitData); - visitMethodInjectionPointInternal(methodVisitData, preDestroyMethodVisitor, preDestroyInstanceLocalVarIndex); - currentMethodIndex++; + buildMethodDefinition.preDestroy.injectionPoints.add(new InjectMethodBuildCommand( + declaringType, + methodElement, + requiresReflection, + allMethodVisits.size() - 1 + )); } } @@ -1864,18 +2571,23 @@ public void visitMethodInjectionPoint(TypedElement declaringType, evaluatedExpressionProcessor.processEvaluatedExpressions(methodElement.getAnnotationMetadata(), this.beanTypeElement); methodInjectionPoints.add(methodVisitData); allMethodVisits.add(methodVisitData); - visitMethodInjectionPointInternal(methodVisitData, injectMethodVisitor, injectInstanceLocalVarIndex); - currentMethodIndex++; + injectCommands.add(new InjectMethodInjectCommand( + declaringType, + methodElement, + requiresReflection, + visitorContext, + allMethodVisits.size() - 1) + ); } @Override public int visitExecutableMethod(TypedElement declaringBean, MethodElement methodElement, VisitorContext visitorContext) { return visitExecutableMethod( - declaringBean, - methodElement, - null, - null + declaringBean, + methodElement, + null, + null ); } @@ -1896,7 +2608,6 @@ public int visitExecutableMethod(TypedElement declaringType, if (executableMethodsDefinitionWriter == null) { executableMethodsDefinitionWriter = new ExecutableMethodsDefinitionWriter( - visitorContext, evaluatedExpressionProcessor, annotationMetadata, beanDefinitionName, @@ -1910,8 +2621,8 @@ public int visitExecutableMethod(TypedElement declaringType, @Override public String toString() { return "BeanDefinitionWriter{" + - "beanFullClassName='" + beanFullClassName + '\'' + - '}'; + "beanFullClassName='" + beanFullClassName + '\'' + + '}'; } @Override @@ -1926,122 +2637,58 @@ public String getBeanSimpleName() { @Override public AnnotationMetadata getAnnotationMetadata() { - return this.annotationMetadata; + return annotationMetadata; } @Override public void visitConfigBuilderField( - ClassElement type, - String field, - AnnotationMetadata annotationMetadata, - ConfigurationMetadataBuilder metadataBuilder, - boolean isInterface) { - String factoryMethod = annotationMetadata - .getValue( - ConfigurationBuilder.class, - "factoryMethod", - String.class) - .orElse(null); - - if (StringUtils.isNotEmpty(factoryMethod)) { - Type builderType = JavaModelUtils.getTypeReference(type); - - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - injectMethodVisitor.invokeStatic( - builderType, - org.objectweb.asm.commons.Method.getMethod( - builderType.getClassName() + " " + factoryMethod + "()" - ) - ); - - injectMethodVisitor.putField(beanType, field, builderType); - } + ClassElement type, + String field, + AnnotationMetadata annotationMetadata, + ConfigurationMetadataBuilder metadataBuilder, + boolean isInterface) { - this.currentConfigBuilderState = new ConfigBuilderState( - type, - field, - false, - annotationMetadata, - isInterface); + ConfigBuilderState state = new ConfigBuilderState(type, field, false, annotationMetadata, isInterface); + configBuilderInjectCommand = new ConfigFieldBuilderInjectCommand(type, field, annotationMetadata, metadataBuilder, isInterface, state, new ArrayList<>()); + injectCommands.add(configBuilderInjectCommand); } @Override public void visitConfigBuilderMethod( - ClassElement type, - String methodName, - AnnotationMetadata annotationMetadata, - ConfigurationMetadataBuilder metadataBuilder, - boolean isInterface) { - - String factoryMethod = annotationMetadata - .getValue( - ConfigurationBuilder.class, - "factoryMethod", - String.class) - .orElse(null); - - if (StringUtils.isNotEmpty(factoryMethod)) { - Type builderType = JavaModelUtils.getTypeReference(type); - - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - injectMethodVisitor.invokeStatic( - builderType, - org.objectweb.asm.commons.Method.getMethod( - builderType.getClassName() + " " + factoryMethod + "()" - ) - ); - - String propertyName = NameUtils.getPropertyNameForGetter(methodName); - String setterName = NameUtils.setterNameFor(propertyName); - - injectMethodVisitor.invokeVirtual(beanType, org.objectweb.asm.commons.Method.getMethod( - "void " + setterName + "(" + builderType.getClassName() + ")" - )); - } + ClassElement type, + String methodName, + AnnotationMetadata annotationMetadata, + ConfigurationMetadataBuilder metadataBuilder, + boolean isInterface) { - this.currentConfigBuilderState = new ConfigBuilderState(type, methodName, true, annotationMetadata, isInterface); + ConfigBuilderState state = new ConfigBuilderState(type, methodName, true, annotationMetadata, isInterface); + configBuilderInjectCommand = new ConfigMethodBuilderInjectPointCommand(type, methodName, annotationMetadata, metadataBuilder, isInterface, state, new ArrayList<>()); + injectCommands.add(configBuilderInjectCommand); } @Override public void visitConfigBuilderDurationMethod( - String propertyName, - ClassElement returnType, - String methodName, - String path) { - visitConfigBuilderMethodInternal( - propertyName, - returnType, - methodName, - ClassElement.of(Duration.class), - Collections.emptyMap(), - true, - path - ); + String propertyName, + ClassElement returnType, + String methodName, + String path) { + configBuilderInjectCommand.builderPoints().add(new ConfigBuilderMethodDurationInjectCommand(propertyName, returnType, methodName, path)); } @Override public void visitConfigBuilderMethod( - String propertyName, - ClassElement returnType, - String methodName, - ClassElement paramType, - Map generics, - String path) { - - visitConfigBuilderMethodInternal( - propertyName, - returnType, - methodName, - paramType, - generics, - false, - path - ); + String propertyName, + ClassElement returnType, + String methodName, + ClassElement paramType, + Map generics, + String path) { + configBuilderInjectCommand.builderPoints().add(new ConfigBuilderMethodInjectCommand(propertyName, returnType, methodName, paramType, generics, path)); } @Override public void visitConfigBuilderEnd() { - currentConfigBuilderState = null; + configBuilderInjectCommand = null; } @Override @@ -2061,24 +2708,18 @@ public boolean requiresMethodProcessing() { @Override public void visitFieldInjectionPoint( - TypedElement declaringType, - FieldElement fieldElement, - boolean requiresReflection, - VisitorContext visitorContext) { - - visitFieldInjectionPointInternal( - declaringType, - fieldElement, - fieldElement.getAnnotationMetadata(), - requiresReflection - ); + TypedElement declaringType, + FieldElement fieldElement, + boolean requiresReflection, + VisitorContext visitorContext) { + injectCommands.add(new InjectFieldInjectCommand(declaringType, fieldElement, requiresReflection)); } - private void visitFieldInjectionPointInternal( - TypedElement declaringType, - FieldElement fieldElement, - AnnotationMetadata annotationMetadata, - boolean requiresReflection) { + private StatementDef injectField(InjectMethodSignature injectMethodSignature, + TypedElement declaringType, + FieldElement fieldElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection) { boolean isRequired = fieldElement .booleanValue(AnnotationUtil.INJECT, AnnotationUtil.MEMBER_REQUIRED) @@ -2117,21 +2758,22 @@ private void visitFieldInjectionPointInternal( } else { methodToInvoke = GET_BEAN_FOR_FIELD; } - visitFieldInjectionPointInternal( - declaringType, - fieldElement, - annotationMetadata, - requiresReflection, - methodToInvoke, - isArray, - requiresGenericType, - isRequired + return visitFieldInjectionPointInternal( + injectMethodSignature, + declaringType, + fieldElement, + annotationMetadata, + requiresReflection, + methodToInvoke, + isArray, + requiresGenericType, + isRequired ); } private static boolean isInjectableMap(ClassElement genericType) { boolean typeMatches = Stream.of(Map.class, HashMap.class, LinkedHashMap.class, TreeMap.class) - .anyMatch(t -> genericType.getName().equals(t.getName())); + .anyMatch(t -> genericType.getName().equals(t.getName())); if (typeMatches) { Map typeArgs = genericType.getTypeArguments(); @@ -2162,32 +2804,31 @@ public void visitAnnotationMemberPropertyInjectionPoint(TypedElement annotationM @Nullable String notEqualsValue) { ClassElement annotationMemberClassElement = annotationMemberBeanType.getType(); MethodElement memberPropertyGetter = annotationMemberClassElement.getBeanProperties() - .stream() - .filter(property -> property.getSimpleName().equals(annotationMemberProperty)) - .findFirst() - .flatMap(PropertyElement::getReadMethod) - .orElse(null); + .stream() + .filter(property -> property.getSimpleName().equals(annotationMemberProperty)) + .findFirst() + .flatMap(PropertyElement::getReadMethod) + .orElse(null); if (memberPropertyGetter == null) { final String[] readPrefixes = annotationMemberBeanType.getAnnotationMetadata() - .getValue(AccessorsStyle.class, "readPrefixes", String[].class) - .orElse(new String[]{AccessorsStyle.DEFAULT_READ_PREFIX}); + .getValue(AccessorsStyle.class, "readPrefixes", String[].class) + .orElse(new String[]{AccessorsStyle.DEFAULT_READ_PREFIX}); memberPropertyGetter = annotationMemberClassElement.getEnclosedElement( - ElementQuery.ALL_METHODS - .onlyAccessible(beanTypeElement) - .onlyInstance() - .filter(m -> annotationMemberProperty.equals(NameUtils.getPropertyNameForGetter(m.getName(), readPrefixes)) && !m.hasParameters()) + ElementQuery.ALL_METHODS + .onlyAccessible(beanTypeElement) + .onlyInstance() + .filter(m -> annotationMemberProperty.equals(NameUtils.getPropertyNameForGetter(m.getName(), readPrefixes)) && !m.hasParameters()) ).orElse(null); } if (memberPropertyGetter == null) { visitorContext.fail("Bean property [" + annotationMemberProperty + "] is not available on bean [" - + annotationMemberBeanType.getName() + "]", annotationMemberBeanType); + + annotationMemberBeanType.getName() + "]", annotationMemberBeanType); } else { - Type injectedType = JavaModelUtils.getTypeReference(annotationMemberClassElement); - annotationInjectionPoints.computeIfAbsent(injectedType, type -> new ArrayList<>(2)) - .add(new AnnotationVisitData(annotationMemberBeanType, annotationMemberProperty, memberPropertyGetter, requiredValue, notEqualsValue)); + annotationInjectionPoints.computeIfAbsent(annotationMemberClassElement, type -> new ArrayList<>(2)) + .add(new AnnotationVisitData(annotationMemberBeanType, annotationMemberProperty, memberPropertyGetter, requiredValue, notEqualsValue)); } } @@ -2196,415 +2837,250 @@ public void visitFieldValue(TypedElement declaringType, FieldElement fieldElement, boolean requiresReflection, boolean isOptional) { - AnnotationMetadata annotationMetadata = fieldElement.getAnnotationMetadata(); - Label falseCondition = isOptional ? pushPropertyContainsCheck(injectMethodVisitor, fieldElement.getType(), fieldElement.getName(), annotationMetadata) : null; - - if (isInnerType(fieldElement.getGenericType())) { - visitFieldInjectionPointInternal(declaringType, fieldElement, annotationMetadata, requiresReflection); - } else if (!isConfigurationProperties || requiresReflection) { - boolean isRequired = fieldElement - .booleanValue(AnnotationUtil.INJECT, AnnotationUtil.MEMBER_REQUIRED) - .orElse(true); - visitFieldInjectionPointInternal( - declaringType, - fieldElement, - annotationMetadata, - requiresReflection, - GET_VALUE_FOR_FIELD, - isOptional, - false, - isRequired - ); - } else { - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - - Optional property = annotationMetadata.stringValue(Property.class, "name"); - if (property.isPresent()) { - pushInvokeGetPropertyValueForField(injectMethodVisitor, fieldElement, annotationMetadata, property.get()); - } else { - Optional valueValue = annotationMetadata.stringValue(Value.class); - valueValue.ifPresent(vv -> pushInvokeGetPropertyPlaceholderValueForField(injectMethodVisitor, fieldElement, annotationMetadata, vv)); - } - putField(injectMethodVisitor, fieldElement, false, declaringType); - - if (keepConfPropInjectPoints) { - fieldInjectionPoints.add(new FieldVisitData(declaringType, fieldElement, annotationMetadata, false)); - currentFieldIndex++; - } - - } - - if (falseCondition != null) { - injectMethodVisitor.visitLabel(falseCondition); - } + injectCommands.add(new InjectFieldValueInjectCommand(declaringType, fieldElement, requiresReflection, isOptional)); } - private void pushInvokeGetPropertyValueForField(GeneratorAdapter injectMethodVisitor, FieldElement fieldElement, AnnotationMetadata annotationMetadata, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method index - + private ExpressionDef getInvokeGetPropertyValueForField(InjectMethodSignature injectMethodSignature, + FieldElement fieldElement, + AnnotationMetadata annotationMetadata, + String value, + int fieldIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); - if (keepConfPropInjectPoints) { - resolveFieldArgument(injectMethodVisitor, currentFieldIndex); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - fieldElement.getName(), - fieldElement.getGenericType(), - annotationMetadata, - fieldElement.getGenericType().getTypeArguments(), - new HashMap<>(), - loadTypeMethods - ); - } - // 4th property value - injectMethodVisitor.push(value); - // 5th cli property name - injectMethodVisitor.push(getCliPrefix(fieldElement.getName())); + return injectMethodSignature.aThis + .invoke( + GET_PROPERTY_VALUE_FOR_FIELD, - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_VALUE_FOR_FIELD); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, fieldElement.getType()); + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + getFieldArgument(fieldElement, annotationMetadata, fieldIndex), + ExpressionDef.constant(value), + ExpressionDef.constant(getCliPrefix(fieldElement.getName())) + ).cast(TypeDef.erasure(fieldElement.getType())); } - private void pushInvokeGetPropertyPlaceholderValueForField(GeneratorAdapter injectMethodVisitor, FieldElement fieldElement, AnnotationMetadata annotationMetadata, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method index - + private ExpressionDef getInvokeGetPropertyPlaceholderValueForField(InjectMethodSignature injectMethodSignature, + FieldElement fieldElement, + AnnotationMetadata annotationMetadata, + String value, + int fieldIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); - if (keepConfPropInjectPoints) { - resolveFieldArgument(injectMethodVisitor, currentFieldIndex); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - fieldElement.getName(), - fieldElement.getGenericType(), - annotationMetadata, - fieldElement.getGenericType().getTypeArguments(), - new HashMap<>(), - loadTypeMethods - ); - } - // 4th property value - injectMethodVisitor.push(value); + return injectMethodSignature.aThis + .invoke( + GET_PROPERTY_PLACEHOLDER_VALUE_FOR_FIELD, - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_PLACEHOLDER_VALUE_FOR_FIELD); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, fieldElement.getType()); + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + getFieldArgument(fieldElement, annotationMetadata, fieldIndex), + ExpressionDef.constant(value) + ).cast(TypeDef.erasure(fieldElement.getType())); } - private void visitConfigBuilderMethodInternal( - String propertyName, - ClassElement returnType, - String methodName, - ClassElement paramType, - Map generics, - boolean isDurationWithTimeUnit, - String propertyPath) { - - if (currentConfigBuilderState != null) { - Type builderType = currentConfigBuilderState.getType(); - String builderName = currentConfigBuilderState.getName(); - boolean isResolveBuilderViaMethodCall = currentConfigBuilderState.isMethod(); - GeneratorAdapter injectMethodVisitor = this.injectMethodVisitor; - - boolean zeroArgs = paramType == null; - - // Optional optional = AbstractBeanDefinition.getValueForPath(...) - int optionalLocalIndex = pushGetValueForPathCall(injectMethodVisitor, paramType, propertyName, propertyPath, zeroArgs, generics); - - Label ifEnd = new Label(); - // if(optional.isPresent()) - injectMethodVisitor.invokeVirtual(Type.getType(Optional.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Optional.class, "isPresent") - )); - injectMethodVisitor.push(false); - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.EQ, ifEnd); - if (zeroArgs) { - pushOptionalGet(injectMethodVisitor, optionalLocalIndex); - pushCastToType(injectMethodVisitor, boolean.class); - injectMethodVisitor.push(false); - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.EQ, ifEnd); - } - - injectMethodVisitor.visitLabel(new Label()); - - String methodDescriptor; - if (zeroArgs) { - methodDescriptor = getMethodDescriptor(returnType, Collections.emptyList()); - } else if (isDurationWithTimeUnit) { - methodDescriptor = getMethodDescriptor(returnType, Arrays.asList(ClassElement.of(long.class), ClassElement.of(TimeUnit.class))); - } else { - methodDescriptor = getMethodDescriptor(returnType, Collections.singleton(paramType)); - } - - Label tryStart = new Label(); - Label tryEnd = new Label(); - Label exceptionHandler = new Label(); - injectMethodVisitor.visitTryCatchBlock(tryStart, tryEnd, exceptionHandler, Type.getInternalName(NoSuchMethodError.class)); - - injectMethodVisitor.visitLabel(tryStart); - - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex); - if (isResolveBuilderViaMethodCall) { - String desc = builderType.getClassName() + " " + builderName + "()"; - injectMethodVisitor.invokeVirtual(beanType, org.objectweb.asm.commons.Method.getMethod(desc)); - } else { - injectMethodVisitor.getField(beanType, builderName, builderType); - } - - if (!zeroArgs) { - pushOptionalGet(injectMethodVisitor, optionalLocalIndex); - pushCastToType(injectMethodVisitor, paramType); - } - - boolean anInterface = currentConfigBuilderState.isInterface(); - - if (isDurationWithTimeUnit) { - injectMethodVisitor.invokeVirtual(Type.getType(Duration.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Duration.class, "toMillis") - )); - Type tu = Type.getType(TimeUnit.class); - injectMethodVisitor.getStatic(tu, "MILLISECONDS", tu); - } + private StatementDef visitConfigBuilderMethodInternal( + InjectMethodSignature injectMethodSignature, + String propertyName, + ClassElement returnType, + String methodName, + ClassElement paramType, + Map generics, + boolean isDurationWithTimeUnit, + String propertyPath, + VariableDef builderVar) { - if (anInterface) { - injectMethodVisitor.invokeInterface(builderType, - new org.objectweb.asm.commons.Method(methodName, methodDescriptor)); - } else { - injectMethodVisitor.invokeVirtual(builderType, - new org.objectweb.asm.commons.Method(methodName, methodDescriptor)); - } - - if (!returnType.isVoid()) { - injectMethodVisitor.pop(); - } - injectMethodVisitor.visitJumpInsn(GOTO, tryEnd); - injectMethodVisitor.visitLabel(exceptionHandler); - injectMethodVisitor.pop(); - injectMethodVisitor.visitLabel(tryEnd); - injectMethodVisitor.visitLabel(ifEnd); - } - } + boolean zeroArgs = paramType == null; - private void pushOptionalGet(GeneratorAdapter injectMethodVisitor, int optionalLocalIndex) { - injectMethodVisitor.loadLocal(optionalLocalIndex); - // get the value: optional.get() - injectMethodVisitor.invokeVirtual(Type.getType(Optional.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Optional.class, "get") - )); + // Optional optional = AbstractBeanDefinition.getValueForPath(...) + return getGetValueForPathCall(injectMethodSignature, paramType, propertyName, propertyPath, zeroArgs, generics) + .newLocal("optional" + NameUtils.capitalize(propertyPath.replace('.', '_')), optionalVar -> { + return optionalVar.invoke(OPTIONAL_IS_PRESENT_METHOD) + .ifTrue( + optionalVar.invoke(OPTIONAL_GET_METHOD).newLocal("value", valueVar -> { + if (zeroArgs) { + return valueVar.cast(boolean.class).ifTrue( + StatementDef.doTry( + builderVar.invoke(methodName, TypeDef.erasure(returnType)) + ).doCatch(NoSuchMethodError.class, exceptionVar -> StatementDef.multi()) + ); + } + List values = new ArrayList<>(); + List parameterTypes = new ArrayList<>(); + if (isDurationWithTimeUnit) { + parameterTypes.add(TypeDef.Primitive.LONG); + ClassTypeDef timeInitType = ClassTypeDef.of(TimeUnit.class); + parameterTypes.add(timeInitType); + values.add( + valueVar.cast(ClassTypeDef.of(Duration.class)) + .invoke(DURATION_TO_MILLIS_METHOD) + ); + values.add( + timeInitType.getStaticField("MILLISECONDS", timeInitType) + ); + } else { + TypeDef paramTypeDef = TypeDef.erasure(paramType); + parameterTypes.add(paramTypeDef); + values.add(valueVar.cast(paramTypeDef)); + } + return StatementDef.doTry( + builderVar.invoke(methodName, parameterTypes, TypeDef.erasure(returnType), values) + ).doCatch(NoSuchMethodError.class, exceptionVar -> StatementDef.multi()); + }) + ); + }); } - private int pushGetValueForPathCall(GeneratorAdapter injectMethodVisitor, ClassElement propertyType, String propertyName, String propertyPath, boolean zeroArgs, Map generics) { - injectMethodVisitor.loadThis(); - injectMethodVisitor.loadArg(0); // the resolution context - injectMethodVisitor.loadArg(1); // the bean context - if (zeroArgs) { - // if the parameter type is null this is a zero args method that expects a boolean flag - buildArgument( - injectMethodVisitor, - propertyName, - Type.getType(Boolean.class) - ); - } else { - buildArgumentWithGenerics( + private ExpressionDef getGetValueForPathCall(InjectMethodSignature injectMethodSignature, + ClassElement propertyType, + String propertyName, + String propertyPath, + boolean zeroArgs, + Map generics) { + return injectMethodSignature.aThis + .invoke( + GET_VALUE_FOR_PATH, + + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + zeroArgs ? ClassTypeDef.of(Argument.class).invokeStatic( + ArgumentExpUtils.METHOD_CREATE_ARGUMENT_SIMPLE, + + ExpressionDef.constant(TypeDef.of(Boolean.class)), + ExpressionDef.constant("factory") + ) : ArgumentExpUtils.buildArgumentWithGenerics( annotationMetadata, - beanDefinitionType, - classWriter, - injectMethodVisitor, + beanDefinitionTypeDef, propertyName, - JavaModelUtils.getTypeReference(propertyType), propertyType, generics, new HashSet<>(), - new HashMap<>(), loadTypeMethods + ), + ExpressionDef.constant(propertyPath) ); - } - - injectMethodVisitor.push(propertyPath); - // Optional optional = AbstractBeanDefinition.getValueForPath(...) - injectMethodVisitor.invokeVirtual(beanDefinitionType, org.objectweb.asm.commons.Method.getMethod(GET_VALUE_FOR_PATH)); - int optionalInstanceIndex = injectMethodVisitor.newLocal(Type.getType(Optional.class)); - injectMethodVisitor.storeLocal(optionalInstanceIndex); - injectMethodVisitor.loadLocal(optionalInstanceIndex); - return optionalInstanceIndex; } - private boolean pushValueBypassingBeanContext(GeneratorAdapter writer, ClassElement type) { + @Internal + private ExpressionDef getValueBypassingBeanContext(ClassElement type, List methodParameters) { // Used in instantiate and inject methods if (type.isAssignable(BeanResolutionContext.class)) { - writer.loadArg(INSTANTIATE_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM); - return true; + return methodParameters.get(INSTANTIATE_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM); } if (type.isAssignable(BeanContext.class)) { - writer.loadArg(INSTANTIATE_METHOD_BEAN_CONTEXT_PARAM); - return true; + return methodParameters.get(INSTANTIATE_METHOD_BEAN_CONTEXT_PARAM); } if (visitorContext.getClassElement(ConversionService.class).orElseThrow().equals(type)) { // We only want to assign to exact `ConversionService` classes not to classes extending `ConversionService` - writer.loadArg(INSTANTIATE_METHOD_BEAN_CONTEXT_PARAM); - writer.invokeInterface(TYPE_BEAN_CONTEXT, METHOD_BEAN_CONTEXT_GET_CONVERSION_SERVICE); - return true; + return methodParameters.get(INSTANTIATE_METHOD_BEAN_CONTEXT_PARAM) + .invoke(METHOD_BEAN_CONTEXT_GET_CONVERSION_SERVICE); } if (type.isAssignable(ConfigurationPath.class)) { - writer.loadArg(INSTANTIATE_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM); - writer.invokeInterface(Type.getType(BeanResolutionContext.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(BeanResolutionContext.class, "getConfigurationPath") - )); - return true; + return methodParameters.get(INSTANTIATE_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM) + .invoke(GET_CONFIGURATION_PATH_METHOD); } - return false; + return null; } - private void visitFieldInjectionPointInternal( - TypedElement declaringType, - FieldElement fieldElement, - AnnotationMetadata annotationMetadata, - boolean requiresReflection, - Method methodToInvoke, - boolean isArray, - boolean requiresGenericType, - boolean isRequired) { + private StatementDef visitFieldInjectionPointInternal(InjectMethodSignature injectMethodSignature, + TypedElement declaringType, + FieldElement fieldElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection, + Method methodToInvoke, + boolean isArray, + boolean requiresGenericType, + boolean isRequired) { evaluatedExpressionProcessor.processEvaluatedExpressions(annotationMetadata, null); autoApplyNamedIfPresent(fieldElement, annotationMetadata); - GeneratorAdapter injectMethodVisitor = this.injectMethodVisitor; + fieldInjectionPoints.add(new FieldVisitData(declaringType, fieldElement, annotationMetadata, requiresReflection)); - if (isRequired) { - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - } + int fieldIndex = fieldInjectionPoints.size() - 1; - if (!pushValueBypassingBeanContext(injectMethodVisitor, fieldElement.getGenericField())) { - // first get the value of the field by calling AbstractBeanDefinition.getBeanForField(..) - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(INJECT_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(INJECT_METHOD_BEAN_CONTEXT_PARAM); - // 3rd argument the field index - injectMethodVisitor.push(currentFieldIndex); + ExpressionDef valueExpression = getValueBypassingBeanContext(fieldElement.getGenericField(), injectMethodSignature.methodParameters); + if (valueExpression == null) { + List valueExpressions = new ArrayList<>( + List.of( + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + ExpressionDef.constant(fieldIndex) + ) + ); if (requiresGenericType) { - resolveFieldArgumentGenericType(injectMethodVisitor, fieldElement.getGenericType(), currentFieldIndex); + valueExpressions.add( + resolveFieldArgumentGenericType(fieldElement.getGenericType(), fieldIndex) + ); } - // push qualifier - pushQualifier(injectMethodVisitor, fieldElement, () -> resolveFieldArgument(injectMethodVisitor, currentFieldIndex)); - // invoke getBeanForField - pushInvokeMethodOnSuperClass(injectMethodVisitor, methodToInvoke); + valueExpressions.add( + getQualifier(fieldElement, resolveFieldArgument(fieldIndex)) + ); + valueExpression = injectMethodSignature.aThis + .invoke(methodToInvoke, valueExpressions); + if (isArray && requiresGenericType) { - convertToArray(fieldElement.getType().fromArray(), injectMethodVisitor); + valueExpression = convertToArray(fieldElement.getType().fromArray(), valueExpression); } - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, fieldElement.getType()); + valueExpression = valueExpression.cast(TypeDef.erasure(fieldElement.getType())); } - Label falseCondition = null; - Type fieldType = JavaModelUtils.getTypeReference(fieldElement.getType()); if (!isRequired) { - int i = injectMethodVisitor.newLocal(fieldType); - injectMethodVisitor.storeLocal(i, fieldType); - injectMethodVisitor.loadLocal(i, fieldType); - falseCondition = injectMethodVisitor.newLabel(); - injectMethodVisitor.ifNull(falseCondition); - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - injectMethodVisitor.loadLocal(i, fieldType); - } - putField( - injectMethodVisitor, - fieldElement, - requiresReflection, - declaringType - ); - if (falseCondition != null) { - injectMethodVisitor.visitLabel(falseCondition); + return valueExpression.newLocal("value", valueVar -> + valueVar.ifNonNull( + putField(fieldElement, requiresReflection, injectMethodSignature, valueVar, fieldIndex) + )); } - currentFieldIndex++; - fieldInjectionPoints.add(new FieldVisitData(declaringType, fieldElement, annotationMetadata, requiresReflection)); + return putField(fieldElement, requiresReflection, injectMethodSignature, valueExpression, fieldIndex); } - private void putField(GeneratorAdapter injectMethodVisitor, FieldElement fieldElement, boolean requiresReflection, TypedElement declaringType) { - Type declaringTypeRef = JavaModelUtils.getTypeReference(declaringType); - Type fieldType = JavaModelUtils.getTypeReference(fieldElement.getType()); - if (!requiresReflection) { - injectMethodVisitor.putField(declaringTypeRef, fieldElement.getName(), fieldType); - } else { - pushBoxPrimitiveIfNecessary(fieldType, injectMethodVisitor); - int storedIndex = injectMethodVisitor.newLocal(Type.getType(Object.class)); - injectMethodVisitor.storeLocal(storedIndex); - injectMethodVisitor.loadThis(); - injectMethodVisitor.loadArg(0); - injectMethodVisitor.loadArg(1); - injectMethodVisitor.push(currentFieldIndex); - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex); - injectMethodVisitor.loadLocal(storedIndex); - injectMethodVisitor.invokeVirtual(superType, SET_FIELD_WITH_REFLECTION_METHOD); - injectMethodVisitor.pop(); + private StatementDef putField(FieldElement fieldElement, + boolean requiresReflection, + InjectMethodSignature injectMethodSignature, + ExpressionDef valueExpression, + int fieldIndex) { + if (requiresReflection) { + return injectMethodSignature.aThis + .invoke( + SET_FIELD_WITH_REFLECTION_METHOD, + + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + ExpressionDef.constant(fieldIndex), + injectMethodSignature.instanceVar, + valueExpression + ); } + return injectMethodSignature.instanceVar.field(fieldElement).put(valueExpression); } - private Label pushPropertyContainsCheck(GeneratorAdapter injectMethodVisitor, ClassElement propertyType, String propertyName, AnnotationMetadata annotationMetadata) { + private ExpressionDef getPropertyContainsCheck(InjectMethodSignature injectMethodSignature, + ClassElement propertyType, + String propertyName, + AnnotationMetadata annotationMetadata) { String propertyValue = annotationMetadata.stringValue(Property.class, "name").orElse(propertyName); - Label trueCondition = new Label(); - Label falseCondition = new Label(); - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument push property name - injectMethodVisitor.push(propertyValue); - if (isMultiValueProperty(propertyType)) { - injectMethodVisitor.invokeVirtual(beanDefinitionType, CONTAINS_PROPERTIES_VALUE_METHOD); - } else { - injectMethodVisitor.invokeVirtual(beanDefinitionType, CONTAINS_PROPERTY_VALUE_METHOD); - } - injectMethodVisitor.push(false); + ExpressionDef.InvokeInstanceMethod containsProperty = injectMethodSignature.aThis.invoke( + isMultiValueProperty(propertyType) ? CONTAINS_PROPERTIES_VALUE_METHOD : CONTAINS_PROPERTY_VALUE_METHOD, - String cliProperty = getCliPrefix(propertyName); - if (cliProperty != null) { - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, trueCondition); + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + ExpressionDef.constant(propertyValue) // property name + ); - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument push property name - injectMethodVisitor.push(cliProperty); - injectMethodVisitor.invokeVirtual(beanDefinitionType, CONTAINS_PROPERTY_VALUE_METHOD); - injectMethodVisitor.push(false); + String cliProperty = getCliPrefix(propertyName); + if (cliProperty == null) { + return containsProperty.isTrue(); } + return containsProperty.isTrue().or( + injectMethodSignature.aThis.invoke( + CONTAINS_PROPERTY_VALUE_METHOD, - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.EQ, falseCondition); - - injectMethodVisitor.visitLabel(trueCondition); - - return falseCondition; + injectMethodSignature.beanResolutionContext, + injectMethodSignature.beanContext, + ExpressionDef.constant(cliProperty) // property name + ).isTrue() + ); } private String getCliPrefix(String propertyName) { @@ -2618,95 +3094,88 @@ private boolean isMultiValueProperty(ClassElement type) { return type.isAssignable(Map.class) || type.isAssignable(Collection.class) || isConfigurationProperties(type); } - private void pushQualifier(GeneratorAdapter generatorAdapter, Element element, Runnable resolveArgument) { + private ExpressionDef getQualifier(Element element, ExpressionDef argumentExpression) { final List qualifierNames = element.getAnnotationNamesByStereotype(AnnotationUtil.QUALIFIER); if (!qualifierNames.isEmpty()) { if (qualifierNames.size() == 1) { // simple qualifier final String annotationName = qualifierNames.iterator().next(); - pushQualifierForAnnotation(generatorAdapter, element, annotationName, resolveArgument); - } else { - // composite qualifier - pushNewArray(generatorAdapter, Qualifier.class, qualifierNames, - name -> pushQualifierForAnnotation(generatorAdapter, element, name, resolveArgument)); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_QUALIFIERS); - } - } else if (element.hasAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDING_QUALIFIER)) { - resolveArgument.run(); - retrieveAnnotationMetadataFromProvider(generatorAdapter); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_INTERCEPTOR_BINDING); - } else { - String[] byType = element.hasDeclaredAnnotation(io.micronaut.context.annotation.Type.NAME) ? element.stringValues(io.micronaut.context.annotation.Type.NAME) : null; - if (byType != null && byType.length > 0) { - pushArrayOfClasses(generatorAdapter, byType); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_TYPE); - } else { - generatorAdapter.push((String) null); + return getQualifierForAnnotation(element, annotationName, argumentExpression); } + // composite qualifier + return TYPE_QUALIFIERS.invokeStatic( + METHOD_QUALIFIER_BY_QUALIFIERS, + + TYPE_QUALIFIER.array().instantiate( + qualifierNames.stream().map(name -> getQualifierForAnnotation(element, name, argumentExpression)).toList() + ) + ); + } + if (element.hasAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDING_QUALIFIER)) { + return TYPE_QUALIFIERS.invokeStatic( + METHOD_QUALIFIER_BY_INTERCEPTOR_BINDING, + getAnnotationMetadataFromProvider(argumentExpression) + ); } + String[] byType = element.hasDeclaredAnnotation(io.micronaut.context.annotation.Type.NAME) ? element.stringValues(io.micronaut.context.annotation.Type.NAME) : null; + if (byType != null && byType.length > 0) { + return TYPE_QUALIFIERS.invokeStatic( + METHOD_QUALIFIER_BY_TYPE, + + TypeDef.CLASS.array().instantiate(Arrays.stream(byType).map(this::asClassExpression).toList()) + ); + } + return ExpressionDef.nullValue(); } - private void retrieveAnnotationMetadataFromProvider(GeneratorAdapter generatorAdapter) { - generatorAdapter.invokeInterface(Type.getType(AnnotationMetadataProvider.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(AnnotationMetadataProvider.class, "getAnnotationMetadata") - )); + private ExpressionDef getAnnotationMetadataFromProvider(ExpressionDef argumentExpression) { + return argumentExpression.invoke(PROVIDER_GET_ANNOTATION_METADATA_METHOD); } - private void pushQualifierForAnnotation(GeneratorAdapter generatorAdapter, - Element element, - String annotationName, - Runnable resolveArgument) { + private ExpressionDef getQualifierForAnnotation(Element element, + String annotationName, + ExpressionDef argumentExpression) { if (annotationName.equals(Primary.NAME)) { // primary is the same as no qualifier - generatorAdapter.visitInsn(ACONST_NULL); - } else if (annotationName.equals(AnnotationUtil.NAMED)) { - final String n = element.stringValue(AnnotationUtil.NAMED) - .orElse(element.getName()); + return ExpressionDef.nullValue(); + } + if (annotationName.equals(AnnotationUtil.NAMED)) { + final String n = element.stringValue(AnnotationUtil.NAMED).orElse(element.getName()); if (!n.contains("$")) { - generatorAdapter.push(n); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_NAME); - } else { - // need to resolve the name at runtime - doResolveArgument(generatorAdapter, resolveArgument); + return TYPE_QUALIFIERS.invokeStatic(METHOD_QUALIFIER_BY_NAME, ExpressionDef.constant(n)); } - } else if (annotationName.equals(Any.NAME)) { - final Type t = Type.getType(AnyQualifier.class); - generatorAdapter.getStatic( - t, - "INSTANCE", - t + return TYPE_QUALIFIERS.invokeStatic(METHOD_QUALIFIER_FOR_ARGUMENT, argumentExpression); + } + if (annotationName.equals(Any.NAME)) { + return ClassTypeDef.of(AnyQualifier.class).getStaticField("INSTANCE", ClassTypeDef.of(AnyQualifier.class)); + } + final String repeatableContainerName = element.findRepeatableAnnotation(annotationName).orElse(null); + if (repeatableContainerName != null) { + return TYPE_QUALIFIERS.invokeStatic( + METHOD_QUALIFIER_BY_REPEATABLE_ANNOTATION, + getAnnotationMetadataFromProvider(argumentExpression), + ExpressionDef.constant(repeatableContainerName) ); - } else { - final String repeatableContainerName = element.findRepeatableAnnotation(annotationName).orElse(null); - resolveArgument.run(); - retrieveAnnotationMetadataFromProvider(generatorAdapter); - if (repeatableContainerName != null) { - generatorAdapter.push(repeatableContainerName); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_REPEATABLE_ANNOTATION); - } else { - generatorAdapter.push(annotationName); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_BY_ANNOTATION); - } } + return TYPE_QUALIFIERS.invokeStatic( + METHOD_QUALIFIER_BY_ANNOTATION, + getAnnotationMetadataFromProvider(argumentExpression), + ExpressionDef.constant(annotationName) + ); } - private void doResolveArgument(GeneratorAdapter generatorAdapter, Runnable resolveArgument) { - resolveArgument.run(); - generatorAdapter.invokeStatic(TYPE_QUALIFIERS, METHOD_QUALIFIER_FOR_ARGUMENT); - } - - private void pushArrayOfClasses(GeneratorAdapter writer, String[] byType) { - pushNewArray(writer, Class.class, byType, type -> pushClass(writer, type)); + private ExpressionDef getArrayOfClasses(String[] byType) { + return TypeDef.CLASS.array().instantiate(Arrays.stream(byType).map(this::asClassExpression).toList()); } - private void pushClass(GeneratorAdapter writer, String className) { - writer.push(Type.getObjectType(className.replace('.', '/'))); + private ExpressionDef.Constant asClassExpression(String type) { + return ExpressionDef.constant(TypeDef.of(type)); } - private void convertToArray(ClassElement arrayType, GeneratorAdapter injectMethodVisitor) { - injectMethodVisitor.push(0); - injectMethodVisitor.newArray(JavaModelUtils.getTypeReference(arrayType)); - injectMethodVisitor.invokeInterface(Type.getType(Collection.class), COLLECTION_TO_ARRAY); + private ExpressionDef convertToArray(ClassElement arrayType, ExpressionDef value) { + return value + .cast(TypeDef.of(Collection.class)) + .invoke(COLLECTION_TO_ARRAY, ClassTypeDef.of(arrayType).array().instantiate()); } private void autoApplyNamedIfPresent(Element element, AnnotationMetadata annotationMetadata) { @@ -2739,342 +3208,311 @@ private void autoApplyNamed(Element element) { } } - private void visitMethodInjectionPointInternal(MethodVisitData methodVisitData, - GeneratorAdapter injectMethodVisitor, - int injectInstanceIndex) { + private StatementDef injectMethod(MethodElement methodElement, + boolean requiresReflection, + VariableDef.This aThis, + List parameters, + VariableDef instanceVar, + int methodIndex) { - MethodElement methodElement = methodVisitData.getMethodElement(); - @NonNull ParameterElement[] methodParameters = methodElement.getParameters(); - final List argumentTypes = Arrays.asList(methodParameters); + final List argumentTypes = Arrays.asList(methodElement.getParameters()); applyDefaultNamedToParameters(argumentTypes); - final TypedElement declaringType = methodVisitData.beanType; - final String methodName = methodElement.getName(); - final boolean requiresReflection = methodVisitData.requiresReflection; - final ClassElement returnType = methodElement.getReturnType(); - boolean hasArguments = methodElement.hasParameters(); - int argCount = hasArguments ? argumentTypes.size() : 0; - Type declaringTypeRef = JavaModelUtils.getTypeReference(declaringType); - boolean hasInjectScope = false; for (ParameterElement value : argumentTypes) { evaluatedExpressionProcessor.processEvaluatedExpressions(value.getAnnotationMetadata(), null); - if (value.hasDeclaredAnnotation(InjectScope.class)) { - hasInjectScope = true; - } } + return injectStatement(aThis, parameters, methodElement, requiresReflection, instanceVar, methodIndex); + } + + private StatementDef injectStatement(VariableDef.This aThis, + List parameters, + MethodElement methodElement, + boolean requiresReflection, + VariableDef instanceVar, + int methodIndex) { + final List argumentTypes = Arrays.asList(methodElement.getParameters()); boolean isRequiredInjection = InjectionPoint.isInjectionRequired(methodElement); - if (!isRequiredInjection && hasArguments) { + List invocationValues = IntStream.range(0, argumentTypes.size()) + .mapToObj(index -> getBeanForMethodParameter(aThis, parameters, index, argumentTypes.get(index), methodIndex)) + .toList(); + if (!isRequiredInjection && methodElement.hasParameters()) { // store parameter values in local object[] - final int parametersIndex = createParameterArray(argumentTypes, injectMethodVisitor, (index, parameter) -> - pushMethodParameterValue(injectMethodVisitor, index, parameter) - ); - // invoke isMethodResolved with method parameters - injectMethodVisitor.loadThis(); - injectMethodVisitor.push(currentMethodIndex); - injectMethodVisitor.loadLocal(parametersIndex, Type.getType(Object[].class)); - injectMethodVisitor.invokeVirtual(superType, IS_METHOD_RESOLVED); - injectMethodVisitor.push(false); - - // check method resolved - Label falseCondition = injectMethodVisitor.newLabel(); - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.EQ, falseCondition); - String methodDescriptor = getMethodDescriptor(returnType, argumentTypes); - injectMethodVisitor.loadLocal(injectInstanceIndex, beanType); - - // load parameters from Object[] - for (int i = 0; i < methodParameters.length; i++) { - ParameterElement methodParameter = methodParameters[i]; - injectMethodVisitor.loadLocal(parametersIndex); - injectMethodVisitor.push(i); - Type t = getTypeReference(methodParameter.getType()); - injectMethodVisitor.arrayLoad(t); - pushCastToType(injectMethodVisitor, t); - } - // invoke the bean method - invokeBeanMethodDirectly(injectMethodVisitor, declaringTypeRef, methodName, methodDescriptor, returnType); - injectMethodVisitor.visitLabel(falseCondition); - } else if (!requiresReflection) { - // if the method doesn't require reflection then invoke it directly - - // invoke the method on this injected instance - injectMethodVisitor.loadLocal(injectInstanceIndex, beanType); - - String methodDescriptor; - if (hasArguments) { - methodDescriptor = getMethodDescriptor(returnType, argumentTypes); - Iterator argIterator = argumentTypes.iterator(); - for (int i = 0; i < argCount; i++) { - ParameterElement entry = argIterator.next(); - pushMethodParameterValue(injectMethodVisitor, i, entry); - } - } else { - methodDescriptor = getMethodDescriptor(returnType, Collections.emptyList()); - } - invokeBeanMethodDirectly(injectMethodVisitor, declaringTypeRef, methodName, methodDescriptor, returnType); - } else { - injectMethodVisitor.loadThis(); - injectMethodVisitor.loadArg(INJECT_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM); - injectMethodVisitor.loadArg(INJECT_METHOD_BEAN_CONTEXT_PARAM); - injectMethodVisitor.push(currentMethodIndex); - injectMethodVisitor.loadLocal(injectInstanceLocalVarIndex, beanType); - newArrayOfMethodParameters(injectMethodVisitor, argumentTypes); - injectMethodVisitor.invokeVirtual(superType, INVOKE_WITH_REFLECTION_METHOD); - } + return TypeDef.OBJECT.array().instantiate(invocationValues).newLocal("values", valuesVar -> { + // invoke isMethodResolved with method parameters + List values = IntStream.range(0, argumentTypes.size()) + .mapToObj(index -> valuesVar.arrayElement(index).cast(TypeDef.erasure(argumentTypes.get(index).getType()))) + .toList(); - destroyInjectScopeBeansIfNecessary(injectMethodVisitor, hasInjectScope); - } + return aThis.invoke( + IS_METHOD_RESOLVED, + + ExpressionDef.constant(methodIndex), + valuesVar + ).ifTrue( + instanceVar.invoke(methodElement, values) + ); + }); - private void invokeBeanMethodDirectly(GeneratorAdapter injectMethodVisitor, Type declaringTypeRef, String methodName, String methodDescriptor, ClassElement returnType) { - injectMethodVisitor.visitMethodInsn(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL, - declaringTypeRef.getInternalName(), methodName, - methodDescriptor, isInterface); - if (isConfigurationProperties && !returnType.isVoid()) { - injectMethodVisitor.pop(); } - } + if (!requiresReflection) { + return instanceVar.invoke(methodElement, invocationValues); + } + return aThis.invoke( + INVOKE_WITH_REFLECTION_METHOD, - private void newArrayOfMethodParameters(GeneratorAdapter injectMethodVisitor, List argumentTypes) { - pushNewArrayIndexed(injectMethodVisitor, Object.class, argumentTypes, (index, entry) -> { - pushMethodParameterValue(injectMethodVisitor, index, entry); - pushBoxPrimitiveIfNecessary(entry.getType(), injectMethodVisitor); - }); + parameters.get(INJECT_METHOD_BEAN_RESOLUTION_CONTEXT_PARAM), + parameters.get(INJECT_METHOD_BEAN_CONTEXT_PARAM), + ExpressionDef.constant(methodIndex), + instanceVar, + TypeDef.OBJECT.array().instantiate(invocationValues) + ); } - private void destroyInjectScopeBeansIfNecessary(GeneratorAdapter injectMethodVisitor, boolean hasInjectScope) { - if (hasInjectScope) { - injectMethodVisitor.loadArg(0); - injectMethodVisitor.invokeInterface( - Type.getType(BeanResolutionContext.class), - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(BeanResolutionContext.class, "destroyInjectScopedBeans") - ) - ); - } + private StatementDef destroyInjectScopeBeansIfNecessary(List parameters) { + return parameters.get(0).invoke(DESTROY_INJECT_SCOPED_BEANS_METHOD); } - private void pushMethodParameterValue(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry) { + private ExpressionDef getBeanForMethodParameter(VariableDef.This aThis, + List methodParameters, + int i, + ParameterElement entry, + int methodIndex) { AnnotationMetadata argMetadata = entry.getAnnotationMetadata(); - if (!pushValueBypassingBeanContext(injectMethodVisitor, entry.getGenericType())) { - boolean requiresGenericType = false; - final ClassElement genericType = entry.getGenericType(); - Method methodToInvoke; - boolean isCollection = genericType.isAssignable(Collection.class); - boolean isMap = isInjectableMap(genericType); - boolean isArray = genericType.isArray(); - - if (isValueType(argMetadata) && !isInnerType(entry.getGenericType())) { - Optional property = argMetadata.stringValue(Property.class, "name"); - if (property.isPresent()) { - pushInvokeGetPropertyValueForMethod(injectMethodVisitor, i, entry, property.get()); + ExpressionDef expressionDef = getValueBypassingBeanContext(entry.getGenericType(), methodParameters); + if (expressionDef != null) { + return expressionDef; + } + boolean requiresGenericType = false; + final ClassElement genericType = entry.getGenericType(); + Method methodToInvoke; + boolean isCollection = genericType.isAssignable(Collection.class); + boolean isMap = isInjectableMap(genericType); + boolean isArray = genericType.isArray(); + + if (isValueType(argMetadata) && !isInnerType(entry.getGenericType())) { + Optional property = argMetadata.stringValue(Property.class, "name"); + if (property.isPresent()) { + return getInvokeGetPropertyValueForMethod(aThis, methodParameters, i, entry, property.get(), methodIndex); + } else { + if (entry.getAnnotationMetadata().getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { + return getInvokeGetEvaluatedExpressionValueForMethodArgument(aThis, i, entry, methodIndex); } else { - if (entry.getAnnotationMetadata().getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { - pushInvokeGetEvaluatedExpressionValueForMethodArgument(injectMethodVisitor, i, entry); - } else { - Optional valueValue = entry.getAnnotationMetadata().stringValue(Value.class); - valueValue.ifPresent(s -> pushInvokeGetPropertyPlaceholderValueForMethod(injectMethodVisitor, i, entry, s)); + Optional valueValue = entry.getAnnotationMetadata().stringValue(Value.class); + if (valueValue.isPresent()) { + return getInvokeGetPropertyPlaceholderValueForMethod(aThis, methodParameters, i, entry, valueValue.get(), methodIndex); } } - return; - } else if (isCollection || isArray) { - requiresGenericType = true; - ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); - if (typeArgument != null && !typeArgument.isPrimitive()) { - if (typeArgument.isAssignable(BeanRegistration.class)) { - methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_METHOD_ARGUMENT; - } else { - methodToInvoke = GET_BEANS_OF_TYPE_FOR_METHOD_ARGUMENT; - } + return ExpressionDef.nullValue(); + } + } else if (isCollection || isArray) { + requiresGenericType = true; + ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); + if (typeArgument != null && !typeArgument.isPrimitive()) { + if (typeArgument.isAssignable(BeanRegistration.class)) { + methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_METHOD_ARGUMENT; } else { - methodToInvoke = GET_BEAN_FOR_METHOD_ARGUMENT; - requiresGenericType = false; + methodToInvoke = GET_BEANS_OF_TYPE_FOR_METHOD_ARGUMENT; } - } else if (isMap) { - requiresGenericType = true; - methodToInvoke = GET_MAP_OF_TYPE_FOR_METHOD_ARGUMENT; - } else if (genericType.isAssignable(Stream.class)) { - requiresGenericType = true; - methodToInvoke = GET_STREAM_OF_TYPE_FOR_METHOD_ARGUMENT; - } else if (genericType.isAssignable(Optional.class)) { - requiresGenericType = true; - methodToInvoke = FIND_BEAN_FOR_METHOD_ARGUMENT; - } else if (genericType.isAssignable(BeanRegistration.class)) { - requiresGenericType = true; - methodToInvoke = GET_BEAN_REGISTRATION_FOR_METHOD_ARGUMENT; } else { methodToInvoke = GET_BEAN_FOR_METHOD_ARGUMENT; + requiresGenericType = false; } + } else if (isMap) { + requiresGenericType = true; + methodToInvoke = GET_MAP_OF_TYPE_FOR_METHOD_ARGUMENT; + } else if (genericType.isAssignable(Stream.class)) { + requiresGenericType = true; + methodToInvoke = GET_STREAM_OF_TYPE_FOR_METHOD_ARGUMENT; + } else if (genericType.isAssignable(Optional.class)) { + requiresGenericType = true; + methodToInvoke = FIND_BEAN_FOR_METHOD_ARGUMENT; + } else if (genericType.isAssignable(BeanRegistration.class)) { + requiresGenericType = true; + methodToInvoke = GET_BEAN_REGISTRATION_FOR_METHOD_ARGUMENT; + } else { + methodToInvoke = GET_BEAN_FOR_METHOD_ARGUMENT; + } + + List values = new ArrayList<>( + List.of( + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 3rd argument the method index + ExpressionDef.constant(methodIndex), + // 4th argument the argument index + ExpressionDef.constant(i) + ) + ); - // first get the value of the field by calling AbstractBeanDefinition.getBeanForMethod(..) - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method index - injectMethodVisitor.push(currentMethodIndex); - // 4th argument the argument index - injectMethodVisitor.push(i); - // invoke getBeanForField - if (requiresGenericType) { - resolveMethodArgumentGenericType(injectMethodVisitor, genericType, currentMethodIndex, i); - } - pushQualifier(injectMethodVisitor, entry, () -> resolveMethodArgument(injectMethodVisitor, currentMethodIndex, i)); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, methodToInvoke); - if (isArray && requiresGenericType) { - convertToArray(genericType.fromArray(), injectMethodVisitor); - } - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); - } - } - - private void pushInvokeGetPropertyValueForMethod(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method index - injectMethodVisitor.push(currentMethodIndex); - // 4th argument the argument index - injectMethodVisitor.push(i); - // 5th property value - injectMethodVisitor.push(value); - // 6 cli property name - injectMethodVisitor.push(getCliPrefix(entry.getName())); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_VALUE_FOR_METHOD_ARGUMENT); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); - } + // invoke getBeanForField + if (requiresGenericType) { + values.add( + resolveMethodArgumentGenericType(genericType, methodIndex, i) + ); + } + ExpressionDef argumentExpression = resolveMethodArgument(methodIndex, i); + values.add( + getQualifier(entry, argumentExpression) + ); - private void pushInvokeGetEvaluatedExpressionValueForMethodArgument(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument the method index - injectMethodVisitor.push(currentMethodIndex); - // 2nd argument the argument index - injectMethodVisitor.push(i); + ExpressionDef result = aThis.invoke(methodToInvoke, values); - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_EVALUATED_EXPRESSION_VALUE_FOR_METHOD_ARGUMENT); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); - } - - private void pushInvokeGetPropertyPlaceholderValueForMethod(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method index - injectMethodVisitor.push(currentMethodIndex); - // 4th argument the argument index - injectMethodVisitor.push(i); - // 5th property value - injectMethodVisitor.push(value); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_PLACEHOLDER_VALUE_FOR_METHOD_ARGUMENT); + if (isArray && requiresGenericType) { + result = convertToArray(genericType.fromArray(), result); + } // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + return result.cast(TypeDef.erasure(entry.getType())); } - private void pushInvokeGetPropertyValueForSetter(GeneratorAdapter injectMethodVisitor, String setterName, ParameterElement entry, String value, AnnotationMetadata annotationMetadata) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method name - injectMethodVisitor.push(setterName); - + private ExpressionDef getInvokeGetPropertyValueForMethod(VariableDef.This aThis, + List methodParameters, + int i, + ParameterElement entry, + String value, + int methodIndex) { + return aThis.invoke( + GET_PROPERTY_VALUE_FOR_METHOD_ARGUMENT, + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 3rd argument the method index + ExpressionDef.constant(methodIndex), + // 4th argument the argument index + ExpressionDef.constant(i), + // 5th property value + ExpressionDef.constant(value), + // 6 cli property name + ExpressionDef.constant(getCliPrefix(entry.getName())) + ).cast(TypeDef.erasure(entry.getType())); + } + + private ExpressionDef getInvokeGetEvaluatedExpressionValueForMethodArgument(VariableDef.This aThis, + int i, + ParameterElement entry, + int methodIndex) { + return aThis.invoke( + GET_EVALUATED_EXPRESSION_VALUE_FOR_METHOD_ARGUMENT, + + // 1st argument the method index + ExpressionDef.constant(methodIndex), + // 2nd argument the argument index + ExpressionDef.constant(i) + ).cast(TypeDef.erasure(entry.getType())); + } + + private ExpressionDef getInvokeGetPropertyPlaceholderValueForMethod(VariableDef.This aThis, + List methodParameters, + int i, + ParameterElement entry, + String value, + int methodIndex) { + return aThis.invoke( + GET_PROPERTY_PLACEHOLDER_VALUE_FOR_METHOD_ARGUMENT, + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 3rd argument the method index + ExpressionDef.constant(methodIndex), + // 4th argument the argument index + ExpressionDef.constant(i), + // 5th property value + ExpressionDef.constant(value) + ).cast(TypeDef.erasure(entry.getType())); + } + + private ExpressionDef getInvokeGetPropertyValueForSetter(InjectMethodSignature injectMethodSignature, + String setterName, + ParameterElement entry, + String value, + AnnotationMetadata annotationMetadata, + int methodIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); - // 4th argument the argument - if (keepConfPropInjectPoints) { - resolveMethodArgument(injectMethodVisitor, currentMethodIndex, 0); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - entry.getName(), - entry.getGenericType(), - annotationMetadata, - entry.getGenericType().getTypeArguments(), - new HashMap<>(), - loadTypeMethods + return injectMethodSignature.aThis.invoke( + GET_PROPERTY_VALUE_FOR_SETTER, + + // 1st argument load BeanResolutionContext + injectMethodSignature.beanResolutionContext, + // 2nd argument load BeanContext + injectMethodSignature.beanContext, + // 3rd argument the method name + ExpressionDef.constant(setterName), + // 4th argument the argument + getMethodArgument(entry, annotationMetadata, methodIndex), + // 5th property value + ExpressionDef.constant(value), + // 6 cli property name + ExpressionDef.constant(getCliPrefix(entry.getName())) + ).cast(TypeDef.erasure(entry.getType())); + } + + private ExpressionDef getMethodArgument(ParameterElement entry, AnnotationMetadata annotationMetadata, int methodIndex) { + return keepConfPropInjectPoints ? resolveMethodArgument(methodIndex, 0) : ArgumentExpUtils.pushCreateArgument( + this.annotationMetadata, + ClassElement.of(beanFullClassName), + beanDefinitionTypeDef, + entry.getName(), + entry.getGenericType(), + annotationMetadata, + entry.getGenericType().getTypeArguments(), + loadTypeMethods + ); + } + + private ExpressionDef getFieldArgument(FieldElement fieldElement, AnnotationMetadata annotationMetadata, int fieldIndex) { + if (!keepConfPropInjectPoints) { + return ArgumentExpUtils.pushCreateArgument( + this.annotationMetadata, + ClassElement.of(beanFullClassName), + beanDefinitionTypeDef, + fieldElement.getName(), + fieldElement.getGenericType(), + annotationMetadata, + fieldElement.getGenericType().getTypeArguments(), + loadTypeMethods ); } - // 5th property value - injectMethodVisitor.push(value); - // 6 cli property name - injectMethodVisitor.push(getCliPrefix(entry.getName())); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_VALUE_FOR_SETTER); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + return resolveFieldArgument(fieldIndex); } - private void pushInvokeGetBeanForSetter(GeneratorAdapter injectMethodVisitor, String setterName, ParameterElement entry, AnnotationMetadata annotationMetadata) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method name - injectMethodVisitor.push(setterName); + private ExpressionDef getInvokeGetBeanForSetter(InjectMethodSignature injectMethodSignature, + String setterName, + ParameterElement entry, + AnnotationMetadata annotationMetadata, + int methodIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); - // 4th argument the argument - if (keepConfPropInjectPoints) { - resolveMethodArgument(injectMethodVisitor, currentMethodIndex, 0); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - entry.getName(), - entry.getGenericType(), - annotationMetadata, - entry.getGenericType().getTypeArguments(), - new HashMap<>(), - loadTypeMethods - ); - } - - // push qualifier - pushQualifier(injectMethodVisitor, entry.getGenericType(), injectMethodVisitor::dup); + return injectMethodSignature.aThis.invoke( + GET_BEAN_FOR_SETTER, - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_BEAN_FOR_SETTER); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + // 1st argument load BeanResolutionContext + injectMethodSignature.beanResolutionContext, + // 2nd argument load BeanContext + injectMethodSignature.beanContext, + // 3rd argument the method name + ExpressionDef.constant(setterName), + // 4th argument the argument + getMethodArgument(entry, annotationMetadata, methodIndex), + // push qualifier + getQualifier(entry.getGenericType(), getMethodArgument(entry, annotationMetadata, methodIndex)) + ).cast(TypeDef.erasure(entry.getType())); } - private void pushInvokeGetBeansOfTypeForSetter(GeneratorAdapter injectMethodVisitor, String setterName, ParameterElement entry, AnnotationMetadata annotationMetadata) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method name - injectMethodVisitor.push(setterName); + private StatementDef getInvokeGetBeansOfTypeForSetter(InjectMethodSignature injectMethodSignature, + String setterName, + ParameterElement entry, + AnnotationMetadata annotationMetadata, + Function onValue, + int methodIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); @@ -3082,84 +3520,62 @@ private void pushInvokeGetBeansOfTypeForSetter(GeneratorAdapter injectMethodVisi // 4th argument the argument ClassElement genericType = entry.getGenericType(); - if (keepConfPropInjectPoints) { - resolveMethodArgument(injectMethodVisitor, currentMethodIndex, 0); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - entry.getName(), - genericType, - annotationMetadata, - genericType.getTypeArguments(), - new HashMap<>(), - loadTypeMethods - ); - } - - int thisArgument = injectMethodVisitor.newLocal(Type.getType(Argument.class)); - injectMethodVisitor.storeLocal(thisArgument); - injectMethodVisitor.loadLocal(thisArgument); + return getMethodArgument(entry, annotationMetadata, methodIndex).newLocal("argument", argumentVar -> { + ExpressionDef value = injectMethodSignature.aThis.invoke( + GET_BEANS_OF_TYPE_FOR_SETTER, + + // 1st argument load BeanResolutionContext + injectMethodSignature.beanResolutionContext, + // 2nd argument load BeanContext + injectMethodSignature.beanContext, + // 3rd argument the method name + ExpressionDef.constant(setterName), + // 4th argument the argument + argumentVar, + // generic type + resolveGenericType(argumentVar, genericType), + // push qualifier + getQualifier(entry.getGenericType(), argumentVar) + ).cast(TypeDef.erasure(entry.getType())); + return onValue.apply(value); + }); + } - if (!resolveArgumentGenericType(injectMethodVisitor, genericType)) { - injectMethodVisitor.loadLocal(thisArgument); - resolveFirstTypeArgument(injectMethodVisitor); - resolveInnerTypeArgumentIfNeeded(injectMethodVisitor, genericType); - } else { - injectMethodVisitor.push((String) null); + private ExpressionDef resolveGenericType(VariableDef argumentVar, ClassElement genericType) { + ExpressionDef argumentExpression = resolveArgumentGenericType(genericType); + if (argumentExpression == null) { + argumentExpression = resolveFirstTypeArgument(argumentVar); + return resolveInnerTypeArgumentIfNeeded(argumentExpression, genericType); } + return argumentExpression; + } - // push qualifier - pushQualifier(injectMethodVisitor, genericType, () -> injectMethodVisitor.loadLocal(thisArgument)); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_BEANS_OF_TYPE_FOR_SETTER); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); - } - - private void pushInvokeGetPropertyPlaceholderValueForSetter(GeneratorAdapter injectMethodVisitor, String setterName, ParameterElement entry, String value, AnnotationMetadata annotationMetadata) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 3rd argument the method name - injectMethodVisitor.push(setterName); - // 4th argument the argument - + private ExpressionDef getInvokeGetPropertyPlaceholderValueForSetter(InjectMethodSignature injectMethodSignature, + String setterName, + ParameterElement entry, + String value, + AnnotationMetadata annotationMetadata, + int methodIndex) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); removeAnnotations(annotationMetadata, PropertySource.class.getName(), Property.class.getName()); - if (keepConfPropInjectPoints) { - resolveMethodArgument(injectMethodVisitor, currentMethodIndex, 0); - } else { - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - injectMethodVisitor, - entry.getName(), - entry.getGenericType(), - annotationMetadata, - entry.getGenericType().getTypeArguments(), - new HashMap<>(), - loadTypeMethods - ); - } - - // 5th property value - injectMethodVisitor.push(value); - // 6 cli property name - injectMethodVisitor.push(getCliPrefix(entry.getName())); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_PLACEHOLDER_VALUE_FOR_SETTER); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + return injectMethodSignature.aThis + .invoke( + GET_PROPERTY_PLACEHOLDER_VALUE_FOR_SETTER, + + // 1st argument load BeanResolutionContext + injectMethodSignature.beanResolutionContext, + // 2nd argument load BeanContext + injectMethodSignature.beanContext, + // 3rd argument the method name + ExpressionDef.constant(setterName), + // 4th argument the argument + getMethodArgument(entry, annotationMetadata, methodIndex), + // 5th property value + ExpressionDef.constant(value), + // 6 cli property name + ExpressionDef.constant(getCliPrefix(entry.getName()) + ).cast(TypeDef.erasure(entry.getType()))); } private void removeAnnotations(AnnotationMetadata annotationMetadata, String... annotationNames) { @@ -3177,795 +3593,256 @@ private void applyDefaultNamedToParameters(List argumentTypes) } } - private void pushInvokeMethodOnSuperClass(MethodVisitor constructorVisitor, Method methodToInvoke) { - constructorVisitor.visitMethodInsn(INVOKESPECIAL, - getSuperTypeInternalType(), - methodToInvoke.getName(), - Type.getMethodDescriptor(methodToInvoke), - false); - } - - private void pushInvokeMethodOnSuperClass(MethodVisitor constructorVisitor, org.objectweb.asm.commons.Method methodToInvoke) { - constructorVisitor.visitMethodInsn(INVOKESPECIAL, - getSuperTypeInternalType(), - methodToInvoke.getName(), - methodToInvoke.getDescriptor(), - false); - } - - private void visitCheckIfShouldLoadMethodDefinition() { - String desc = getMethodDescriptor("void", BeanResolutionContext.class.getName(), BeanContext.class.getName()); - this.checkIfShouldLoadMethodVisitor = new GeneratorAdapter(classWriter.visitMethod( - ACC_PROTECTED, - "checkIfShouldLoad", - desc, - null, - null), ACC_PROTECTED, "checkIfShouldLoad", desc); - } - - @SuppressWarnings("MagicNumber") - private void visitInjectMethodDefinition() { - if (!isPrimitiveBean && !superBeanDefinition && injectMethodVisitor == null) { - injectMethodVisitor = new GeneratorAdapter(classWriter.visitMethod( - ACC_PUBLIC, - INJECT_BEAN_METHOD.getName(), - INJECT_BEAN_METHOD.getDescriptor(), - null, - null), ACC_PUBLIC, INJECT_BEAN_METHOD.getName(), INJECT_BEAN_METHOD.getDescriptor()); - - GeneratorAdapter injectMethodVisitor = this.injectMethodVisitor; - if (isConfigurationProperties) { - injectMethodVisitor.loadThis(); - injectMethodVisitor.loadArg(0); // the resolution context - injectMethodVisitor.loadArg(1); // the bean context - // invoke AbstractBeanDefinition.containsProperties(..) - injectMethodVisitor.invokeVirtual(beanDefinitionType, org.objectweb.asm.commons.Method.getMethod(CONTAINS_PROPERTIES_METHOD)); - injectMethodVisitor.push(false); - injectEnd = new Label(); - injectMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.EQ, injectEnd); - // add the true condition - injectMethodVisitor.visitLabel(new Label()); - } - // The object being injected is argument 3 of the inject method - injectMethodVisitor.loadArg(2); - // store it in a local variable - pushCastToType(injectMethodVisitor, beanType); - injectInstanceLocalVarIndex = injectMethodVisitor.newLocal(beanType); - injectMethodVisitor.storeLocal(injectInstanceLocalVarIndex); - } - } - @SuppressWarnings("MagicNumber") - private void visitPostConstructMethodDefinition(boolean intercepted) { - if (!postConstructAdded) { - // override the post construct method - final String lifeCycleMethodName = "initialize"; - - // for "super bean definition" we only add code to trigger "initialize" - if (!superBeanDefinition || intercepted) { - interfaceTypes.add(InitializingBeanDefinition.class); - - GeneratorAdapter postConstructMethodVisitor = newLifeCycleMethod(lifeCycleMethodName); - this.postConstructMethodVisitor = postConstructMethodVisitor; - // The object being injected is argument 3 of the inject method - postConstructMethodVisitor.loadArg(2); - // store it in a local variable - pushCastToType(postConstructMethodVisitor, beanType); - postConstructInstanceLocalVarIndex = postConstructMethodVisitor.newLocal(beanType); - postConstructMethodVisitor.storeLocal(postConstructInstanceLocalVarIndex); - invokeSuperInjectMethod(postConstructMethodVisitor, POST_CONSTRUCT_METHOD); - } - - if (intercepted) { - // store executable method in local variable - writeInterceptedLifecycleMethod( - lifeCycleMethodName, - lifeCycleMethodName, - buildMethodVisitor, - buildInstanceLocalVarIndex - ); - } else { - pushBeanDefinitionMethodInvocation(buildMethodVisitor, lifeCycleMethodName); - } - pushCastToType(buildMethodVisitor, beanType); - buildMethodVisitor.loadLocal(buildInstanceLocalVarIndex); - postConstructAdded = true; - } - } - - private void writeInterceptedLifecycleMethod( - String lifeCycleMethodName, - String dispatchMethodName, - GeneratorAdapter targetMethodVisitor, - int instanceLocalIndex) { + private ClassTypeDef createExecutableMethodInterceptor(MethodDef interceptMethod, String name) { // if there is method interception in place we need to construct an inner executable method class that invokes the "initialize" // method and apply interception - final InnerClassDef postConstructInnerMethod = newInnerClass(AbstractExecutableMethod.class); - // needs fields to propagate the correct arguments to the initialize method - final ClassWriter postConstructInnerWriter = postConstructInnerMethod.innerClassWriter; - final Type postConstructInnerClassType = postConstructInnerMethod.innerClassType; - final String fieldBeanDef = "$beanDef"; - final String fieldResContext = "$resolutionContext"; - final String fieldBeanContext = "$beanContext"; - final String fieldBean = "$bean"; - newFinalField(postConstructInnerWriter, beanDefinitionType, fieldBeanDef); - newFinalField(postConstructInnerWriter, TYPE_RESOLUTION_CONTEXT, fieldResContext); - newFinalField(postConstructInnerWriter, TYPE_BEAN_CONTEXT, fieldBeanContext); - newFinalField(postConstructInnerWriter, beanType, fieldBean); - // constructor will be AbstractExecutableMethod(BeanDefinition, BeanResolutionContext, BeanContext, T beanType) - final String constructorDescriptor = getConstructorDescriptor(new Type[]{ - beanDefinitionType, - TYPE_RESOLUTION_CONTEXT, - TYPE_BEAN_CONTEXT, - beanType - }); - GeneratorAdapter protectedConstructor = new GeneratorAdapter( - postConstructInnerWriter.visitMethod( - ACC_PROTECTED, CONSTRUCTOR_NAME, - constructorDescriptor, - null, - null - ), - ACC_PROTECTED, - CONSTRUCTOR_NAME, - constructorDescriptor - ); - // set field $beanDef - protectedConstructor.loadThis(); - protectedConstructor.loadArg(0); - protectedConstructor.putField(postConstructInnerClassType, fieldBeanDef, beanDefinitionType); - // set field $resolutionContext - protectedConstructor.loadThis(); - protectedConstructor.loadArg(1); - protectedConstructor.putField(postConstructInnerClassType, fieldResContext, TYPE_RESOLUTION_CONTEXT); - // set field $beanContext - protectedConstructor.loadThis(); - protectedConstructor.loadArg(2); - protectedConstructor.putField(postConstructInnerClassType, fieldBeanContext, TYPE_BEAN_CONTEXT); - // set field $bean - protectedConstructor.loadThis(); - protectedConstructor.loadArg(3); - protectedConstructor.putField(postConstructInnerClassType, fieldBean, beanType); - - protectedConstructor.loadThis(); - protectedConstructor.push(beanType); - protectedConstructor.push(lifeCycleMethodName); - invokeConstructor( - protectedConstructor, - AbstractExecutableMethod.class, - Class.class, - String.class - ); - protectedConstructor.returnValue(); - protectedConstructor.visitMaxs(1, 1); - protectedConstructor.visitEnd(); - - // annotation metadata should reference to the metadata for bean definition - final GeneratorAdapter getAnnotationMetadata = startPublicFinalMethodZeroArgs(postConstructInnerWriter, AnnotationMetadata.class, "getAnnotationMetadata"); - lookupReferenceAnnotationMetadata(getAnnotationMetadata); - - // now define the invokerInternal method - final GeneratorAdapter invokeMethod = startPublicMethod(postConstructInnerWriter, METHOD_INVOKE_INTERNAL); - invokeMethod.loadThis(); - // load the bean definition field - invokeMethod.getField(postConstructInnerClassType, fieldBeanDef, beanDefinitionType); - // load the arguments to the initialize method - // 1st argument the resolution context - invokeMethod.loadThis(); - invokeMethod.getField(postConstructInnerClassType, fieldResContext, TYPE_RESOLUTION_CONTEXT); - // 2nd argument the bean context - invokeMethod.loadThis(); - invokeMethod.getField(postConstructInnerClassType, fieldBeanContext, TYPE_BEAN_CONTEXT); - // 3rd argument the bean - invokeMethod.loadThis(); - invokeMethod.getField(postConstructInnerClassType, fieldBean, beanType); - // now invoke initialize - invokeMethod.visitMethodInsn(INVOKEVIRTUAL, - beanDefinitionInternalName, - lifeCycleMethodName, - METHOD_DESCRIPTOR_INITIALIZE, - false); - invokeMethod.returnValue(); - invokeMethod.visitMaxs(1, 1); - invokeMethod.visitEnd(); + String interceptedConstructorWriterName = name; - // now instantiate the inner class - targetMethodVisitor.visitTypeInsn(NEW, postConstructInnerMethod.constructorInternalName); - targetMethodVisitor.visitInsn(DUP); - // constructor signature is AbstractExecutableMethod(BeanDefinition, BeanResolutionContext, BeanContext, T beanType) - // 1st argument: pass outer class instance to constructor - targetMethodVisitor.loadThis(); - - // 2nd argument: resolution context - targetMethodVisitor.loadArg(0); - - // 3rd argument: bean context - targetMethodVisitor.loadArg(1); - - // 4th argument: bean instance - targetMethodVisitor.loadLocal(instanceLocalIndex); - pushCastToType(targetMethodVisitor, beanType); - targetMethodVisitor.visitMethodInsn( - INVOKESPECIAL, - postConstructInnerMethod.constructorInternalName, - "", - constructorDescriptor, - false - ); - final int executableInstanceIndex = targetMethodVisitor.newLocal(Type.getType(ExecutableMethod.class)); - targetMethodVisitor.storeLocal(executableInstanceIndex); - // now invoke MethodInterceptorChain.initialize or dispose - // 1st argument: resolution context - targetMethodVisitor.loadArg(0); - // 2nd argument: bean context - targetMethodVisitor.loadArg(1); - // 3rd argument: this definition - targetMethodVisitor.loadThis(); - // 4th argument: executable method instance - targetMethodVisitor.loadLocal(executableInstanceIndex); - // 5th argument: the bean instance - targetMethodVisitor.loadLocal(instanceLocalIndex); - pushCastToType(targetMethodVisitor, beanType); - targetMethodVisitor.visitMethodInsn( - INVOKESTATIC, - "io/micronaut/aop/chain/MethodInterceptorChain", - dispatchMethodName, - METHOD_DESCRIPTOR_INTERCEPTED_LIFECYCLE, - false - ); - targetMethodVisitor.loadLocal(instanceLocalIndex); - } + ClassDef.ClassDefBuilder innerClassBuilder = ClassDef.builder(interceptedConstructorWriterName) + .addModifiers(Modifier.FINAL) + .superclass(ClassTypeDef.of(AbstractExecutableMethod.class)) + .addAnnotation(Generated.class); - @SuppressWarnings("MagicNumber") - private void visitPreDestroyMethodDefinition(boolean intercepted) { - if (preDestroyMethodVisitor == null) { - interfaceTypes.add(DisposableBeanDefinition.class); - - // override the dispose method - GeneratorAdapter preDestroyMethodVisitor; - if (intercepted) { - preDestroyMethodVisitor = newLifeCycleMethod("doDispose"); - final GeneratorAdapter disposeMethod = newLifeCycleMethod("dispose"); - disposeMethod.loadArg(2); - int instanceLocalIndex = disposeMethod.newLocal(beanType); - disposeMethod.storeLocal(instanceLocalIndex); - - writeInterceptedLifecycleMethod( - "doDispose", - "dispose", - disposeMethod, - instanceLocalIndex - ); - disposeMethod.returnValue(); + FieldDef fieldBeanDef = FieldDef.builder("$beanDef", beanDefinitionTypeDef) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + FieldDef fieldResContext = FieldDef.builder("$resolutionContext", BeanResolutionContext.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); - this.interceptedDisposeMethod = disposeMethod; - } else { - preDestroyMethodVisitor = newLifeCycleMethod("dispose"); - } + FieldDef fieldBeanContext = FieldDef.builder("$beanContext", BeanContext.class) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); - this.preDestroyMethodVisitor = preDestroyMethodVisitor; - // The object being injected is argument 3 of the inject method - preDestroyMethodVisitor.loadArg(2); - // store it in a local variable - pushCastToType(preDestroyMethodVisitor, beanType); - preDestroyInstanceLocalVarIndex = preDestroyMethodVisitor.newLocal(beanType); - preDestroyMethodVisitor.storeLocal(preDestroyInstanceLocalVarIndex); + FieldDef fieldBean = FieldDef.builder("$bean", beanTypeDef) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); - invokeSuperInjectMethod(preDestroyMethodVisitor, PRE_DESTROY_METHOD); - } - } + innerClassBuilder.addField(fieldBeanDef); + innerClassBuilder.addField(fieldResContext); + innerClassBuilder.addField(fieldBeanContext); + innerClassBuilder.addField(fieldBean); - private GeneratorAdapter newLifeCycleMethod(String methodName) { - String desc = getMethodDescriptor(Object.class.getName(), BeanResolutionContext.class.getName(), BeanContext.class.getName(), Object.class.getName()); - return new GeneratorAdapter(classWriter.visitMethod( - ACC_PUBLIC, - methodName, - desc, - getMethodSignature(getTypeDescriptor(beanFullClassName), getTypeDescriptor(BeanResolutionContext.class.getName()), getTypeDescriptor(BeanContext.class.getName()), getTypeDescriptor(beanFullClassName)), - null), - ACC_PUBLIC, - methodName, - desc - ); - } + // constructor will be AbstractExecutableMethod(BeanDefinition, BeanResolutionContext, BeanContext, T beanType) - @SuppressWarnings("MagicNumber") - private void invokeSuperInjectMethod(GeneratorAdapter methodVisitor, Method methodToInvoke) { - // load this - methodVisitor.loadThis(); - // load BeanResolutionContext arg 1 - methodVisitor.loadArg(0); - // load BeanContext arg 2 - methodVisitor.loadArg(1); - // load object being injected arg 3 - methodVisitor.loadArg(2); - pushInvokeMethodOnSuperClass(methodVisitor, methodToInvoke); - } - - private void invokeSuperInjectMethod(GeneratorAdapter methodVisitor, org.objectweb.asm.commons.Method methodToInvoke) { - // load this - methodVisitor.loadThis(); - // load BeanResolutionContext arg 1 - methodVisitor.loadArg(0); - // load BeanContext arg 2 - methodVisitor.loadArg(1); - // load object being injected arg 3 - methodVisitor.loadArg(2); - pushInvokeMethodOnSuperClass(methodVisitor, methodToInvoke); - } - - private void visitBuildFactoryMethodDefinition( - ClassElement factoryClass, - Element factoryElement, ParameterElement... parameters) { - if (buildMethodVisitor == null) { - evaluatedExpressionProcessor.processEvaluatedExpressions(factoryElement.getAnnotationMetadata(), null); - for (ParameterElement parameterElement: parameters) { - evaluatedExpressionProcessor.processEvaluatedExpressions(parameterElement.getAnnotationMetadata(), null); - } + innerClassBuilder.addMethod( + MethodDef.constructor() + .addParameters(beanDefinitionTypeDef) + .addParameters(BeanResolutionContext.class, BeanContext.class) + .addParameters(beanTypeDef) + .build((aThis, methodParameters) -> StatementDef.multi( + aThis.superRef().invokeConstructor( + ABSTRACT_EXECUTABLE_METHOD_CONSTRUCTOR, + + ExpressionDef.constant(beanTypeDef), + ExpressionDef.constant(interceptMethod.getName()) + ), + aThis.field(fieldBeanDef).put(methodParameters.get(0)), + aThis.field(fieldResContext).put(methodParameters.get(1)), + aThis.field(fieldBeanContext).put(methodParameters.get(2)), + aThis.field(fieldBean).put(methodParameters.get(3)) + )) + ); - List parameterList = Arrays.asList(parameters); - boolean isParametrized = isParametrized(parameters); - boolean isIntercepted = isConstructorIntercepted(factoryElement); - Type factoryType = JavaModelUtils.getTypeReference(factoryClass); + innerClassBuilder.addMethod( + MethodDef.override(PROVIDER_GET_ANNOTATION_METADATA_METHOD) + .build((aThis, methodParameters) -> + beanDefinitionTypeDef.getStaticField(AnnotationMetadataGenUtils.FIELD_ANNOTATION_METADATA) + .returning()) + ); - defineBuilderMethod(isParametrized); - // load this + innerClassBuilder.addMethod( + MethodDef.override(METHOD_INVOKE_INTERNAL) + .build((aThis, methodParameters) -> + aThis.field(fieldBeanDef).invoke(interceptMethod, - GeneratorAdapter buildMethodVisitor = this.buildMethodVisitor; + aThis.field(fieldResContext), + aThis.field(fieldBeanContext), + aThis.field(fieldBean) + ).returning()) + ); - int factoryVar = -1; - // Skip initializing a producer instance for static producers - if (!factoryElement.isStatic()) { - factoryVar = pushGetFactoryBean(factoryClass, factoryType, buildMethodVisitor); - } - String methodDescriptor = getMethodDescriptorForReturnType(beanType, parameterList); - boolean hasInjectScope = false; - if (isIntercepted) { - int constructorIndex = initInterceptedConstructorWriter( - buildMethodVisitor, - parameterList, - new FactoryMethodDef(factoryType, factoryElement, methodDescriptor, factoryVar) - ); - // populate an Object[] of all constructor arguments - final int parametersIndex = createConstructorParameterArray(parameterList, buildMethodVisitor); - invokeConstructorChain(buildMethodVisitor, constructorIndex, parametersIndex, parameterList); - } else { - if (factoryElement instanceof MethodElement methodElement) { - if (!methodElement.isReflectionRequired() && !parameterList.isEmpty()) { - hasInjectScope = pushConstructorArguments(buildMethodVisitor, parameters); - } - if (methodElement.isReflectionRequired()) { - if (methodElement.isStatic()) { - buildMethodVisitor.push((String) null); - } - DispatchWriter.pushTypeUtilsGetRequiredMethod(buildMethodVisitor, factoryType, methodElement); - buildMethodVisitor.dup(); - buildMethodVisitor.push(true); - buildMethodVisitor.invokeVirtual(Type.getType(Method.class), org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(Method.class, "setAccessible", boolean.class) - )); - hasInjectScope = pushParametersAsArray(buildMethodVisitor, parameters); - buildMethodVisitor.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_INVOKE_METHOD); -// buildMethodVisitor.push((String) null); - if (methodElement.isReflectionRequired() && isPrimitiveBean) { - // Reflection always returns Object, convert it to appropriate primitive - pushCastToType(buildMethodVisitor, beanType); - } - } else if (methodElement.isStatic()) { - buildMethodVisitor.invokeStatic(factoryType, new org.objectweb.asm.commons.Method(factoryElement.getName(), methodDescriptor)); - } else { - buildMethodVisitor.invokeVirtual(factoryType, new org.objectweb.asm.commons.Method(factoryElement.getName(), methodDescriptor)); - } - } else { - FieldElement fieldElement = (FieldElement) factoryElement; - if (fieldElement.isReflectionRequired()) { - if (!fieldElement.isStatic()) { - buildMethodVisitor.storeLocal(factoryVar); - } - buildMethodVisitor.push(factoryType); - buildMethodVisitor.push(fieldElement.getName()); - if (fieldElement.isStatic()) { - buildMethodVisitor.push((String) null); - } else { - buildMethodVisitor.loadLocal(factoryVar); - } - buildMethodVisitor.invokeStatic(TYPE_REFLECTION_UTILS, GET_FIELD_WITH_REFLECTION_METHOD); - if (fieldElement.isReflectionRequired() && isPrimitiveBean) { - // Reflection always returns Object, convert it to appropriate primitive - pushCastToType(buildMethodVisitor, beanType); - } - } else if (fieldElement.isStatic()) { - buildMethodVisitor.getStatic(factoryType, factoryElement.getName(), beanType); - } else { - buildMethodVisitor.getField(factoryType, factoryElement.getName(), beanType); - } - } - } + classDefBuilder.addInnerType(innerClassBuilder.build()); - this.buildInstanceLocalVarIndex = buildMethodVisitor.newLocal(beanType); - buildMethodVisitor.storeLocal(buildInstanceLocalVarIndex, beanType); - if (!isPrimitiveBean) { - pushBeanDefinitionMethodInvocation(buildMethodVisitor, INJECT_BEAN_METHOD.getName()); - pushCastToType(buildMethodVisitor, beanType); - buildMethodVisitor.storeLocal(buildInstanceLocalVarIndex); - } - destroyInjectScopeBeansIfNecessary(buildMethodVisitor, hasInjectScope); - buildMethodVisitor.loadLocal(buildInstanceLocalVarIndex, beanType); - initLifeCycleMethodsIfNecessary(); - } + return ClassTypeDef.of(beanDefinitionName + "$" + interceptedConstructorWriterName); } - private int pushGetFactoryBean(ClassElement factoryClass, Type factoryType, GeneratorAdapter buildMethodVisitor) { - invokeCheckIfShouldLoadIfNecessary(buildMethodVisitor); - // for Factory beans first we need to look up the factory bean - // before invoking the method to instantiate - // the below code looks up the factory bean. + private StatementDef interceptAndReturn(VariableDef.This aThis, + List methodParameters, + ClassTypeDef innerTypeDef, + Method interceptorMethod) { + ExpressionDef localInstance = methodParameters.get(2).cast(beanTypeDef); - // Load the BeanContext for the method call - buildMethodVisitor.loadArg(1); - pushCastToType(buildMethodVisitor, DefaultBeanContext.class); - // load the first argument of the method (the BeanResolutionContext) to be passed to the method - buildMethodVisitor.loadArg(0); - // second argument is the bean type - buildMethodVisitor.push(factoryType); - // third argument is the qualifier for the factory if any - pushQualifier(buildMethodVisitor, factoryClass, () -> { - buildMethodVisitor.push(factoryType); - buildMethodVisitor.push("factory"); - invokeInterfaceStaticMethod(buildMethodVisitor, Argument.class, METHOD_CREATE_ARGUMENT_SIMPLE); - }); - buildMethodVisitor.invokeVirtual( - Type.getType(DefaultBeanContext.class), - org.objectweb.asm.commons.Method.getMethod(METHOD_GET_BEAN) + // now instantiate the inner class + ExpressionDef executableMethodInstance = innerTypeDef.instantiate( + // 1st argument: pass outer class instance to constructor + aThis, + // 2nd argument: resolution context + methodParameters.get(0), + // 3rd argument: bean context + methodParameters.get(1), + // 4th argument: bean instance + localInstance ); + // now invoke MethodInterceptorChain.initialize or dispose + return ClassTypeDef.of(MethodInterceptorChain.class) + .invokeStatic( + interceptorMethod, - int factoryVar = buildMethodVisitor.newLocal(factoryType); - buildMethodVisitor.storeLocal(factoryVar, factoryType); - - // BeanResolutionContext - buildMethodVisitor.loadArg(0); - // .markDependentAsFactory() - buildMethodVisitor.invokeInterface(TYPE_RESOLUTION_CONTEXT, METHOD_BEAN_RESOLUTION_CONTEXT_MARK_FACTORY); - - buildMethodVisitor.loadLocal(factoryVar); - pushCastToType(buildMethodVisitor, factoryClass); - return factoryVar; - } - - private void visitBuildMethodDefinition(MethodElement constructor, boolean requiresReflection) { - if (buildMethodVisitor == null) { - boolean isIntercepted = isConstructorIntercepted(constructor); - final ParameterElement[] parameterArray = constructor.getParameters(); - List parameters = Arrays.asList(parameterArray); - boolean isParametrized = isParametrized(parameterArray); - defineBuilderMethod(isParametrized); - // load this - - GeneratorAdapter buildMethodVisitor = this.buildMethodVisitor; - invokeCheckIfShouldLoadIfNecessary(buildMethodVisitor); - - // if there is constructor interception present then we have to - // build the parameters into an Object[] and build a constructor invocation - if (isIntercepted) { - final int constructorIndex = initInterceptedConstructorWriter(buildMethodVisitor, parameters, null); - // populate an Object[] of all constructor arguments - final int parametersIndex = createConstructorParameterArray(parameters, buildMethodVisitor); - invokeConstructorChain(buildMethodVisitor, constructorIndex, parametersIndex, parameters); - } else { - if (WriterUtils.hasKotlinDefaultsParameters(parameters)) { - Map checksLocals = new HashMap<>(); - Map valuesLocals = new HashMap<>(); - WriterUtils.invokeBeanConstructor(buildMethodVisitor, constructor, requiresReflection, true, (index, parameter) -> { - Integer checkLocal = checksLocals.get(index); - if (checkLocal != null) { - buildMethodVisitor.loadLocal(checkLocal); - buildMethodVisitor.push(true); - Label end = new Label(); - Label propertyMissingLabel = new Label(); - buildMethodVisitor.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, propertyMissingLabel); - pushConstructorArgument(buildMethodVisitor, parameter, index); - buildMethodVisitor.goTo(end); - buildMethodVisitor.visitLabel(propertyMissingLabel); - WriterUtils.pushDefaultTypeValue(buildMethodVisitor, parameter.getType()); - buildMethodVisitor.goTo(end); - buildMethodVisitor.visitLabel(end); - return; - } - int loadedLocal = valuesLocals.computeIfAbsent(index, integer -> { - pushConstructorArgument(buildMethodVisitor, parameter, index); - int local = buildMethodVisitor.newLocal(getTypeReference(parameter)); - buildMethodVisitor.storeLocal(local); - return local; - }); - buildMethodVisitor.loadLocal(loadedLocal); - }, (index, parameterElement) -> { - if (parameterElement.hasAnnotation(Property.class)) { - int local = buildMethodVisitor.newLocal(Type.BOOLEAN_TYPE); - pushContainsPropertyCheck(buildMethodVisitor, parameterElement); - buildMethodVisitor.storeLocal(local); - buildMethodVisitor.loadLocal(local); - checksLocals.put(index, local); - return true; - } - return false; - }); - } else { - WriterUtils.invokeBeanConstructor( - buildMethodVisitor, - constructor, - requiresReflection, - true, - (index, parameter) -> pushConstructorArgument(buildMethodVisitor, parameter, index), - null); - } - } + List.of( + // 1st argument: resolution context + methodParameters.get(0), + // 2nd argument: bean context + methodParameters.get(1), + // 3rd argument: this definition + aThis, + // 4th argument: executable method instance + executableMethodInstance, + // 5th argument: the bean instance + localInstance + ) + ).cast(beanTypeDef).returning(); + } - this.buildInstanceLocalVarIndex = buildMethodVisitor.newLocal(beanType); - buildMethodVisitor.storeLocal(buildInstanceLocalVarIndex); - pushBeanDefinitionMethodInvocation(buildMethodVisitor, INJECT_BEAN_METHOD.getName()); - pushCastToType(buildMethodVisitor, beanType); - buildMethodVisitor.storeLocal(buildInstanceLocalVarIndex); - buildMethodVisitor.loadLocal(buildInstanceLocalVarIndex); - initLifeCycleMethodsIfNecessary(); - pushBoxPrimitiveIfNecessary(beanType, buildMethodVisitor); - } - } - - private void pushContainsPropertyCheck(GeneratorAdapter writer, ParameterElement parameterElement) { - String propertyValue = parameterElement.stringValue(Property.class, "name").orElseThrow(); - writer.loadThis(); - // 1st argument load BeanResolutionContext - writer.loadArg(0); - // 2nd argument load BeanContext - writer.loadArg(1); - // 3rd argument push property name - writer.push(propertyValue); - if (isMultiValueProperty(parameterElement.getType())) { - writer.invokeVirtual(beanDefinitionType, CONTAINS_PROPERTIES_VALUE_METHOD); - } else { - writer.invokeVirtual(beanDefinitionType, CONTAINS_PROPERTY_VALUE_METHOD); + private void visitBuildFactoryMethodDefinition(ClassElement factoryClass, Element factoryElement, ParameterElement... parameters) { + if (buildMethodDefinition == null) { + buildMethodDefinition = new FactoryBuildMethodDefinition(factoryClass, factoryElement, parameters); + onBuild(factoryElement, parameters); } } - private void invokeCheckIfShouldLoadIfNecessary(GeneratorAdapter buildMethodVisitor) { - AnnotationValue requiresAnnotation = annotationMetadata.getAnnotation(Requires.class); - if (requiresAnnotation != null - && requiresAnnotation.stringValue(RequiresCondition.MEMBER_BEAN).isPresent() - && requiresAnnotation.stringValue(RequiresCondition.MEMBER_BEAN_PROPERTY).isPresent()) { - visitCheckIfShouldLoadMethodDefinition(); - - buildMethodVisitor.loadThis(); - buildMethodVisitor.loadArg(0); - buildMethodVisitor.loadArg(1); - buildMethodVisitor.invokeVirtual(beanDefinitionType, org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredMethod(AbstractInitializableBeanDefinition.class, "checkIfShouldLoad", - BeanResolutionContext.class, - BeanContext.class))); + private void visitBuildConstructorDefinition(MethodElement constructor, boolean requiresReflection) { + if (buildMethodDefinition == null) { + buildMethodDefinition = new ConstructorBuildMethodDefinition(constructor, requiresReflection); + onBuild(constructor, constructor.getParameters()); } } - private void initLifeCycleMethodsIfNecessary() { + private void onBuild(Element factoryElement, ParameterElement[] parameters) { + evaluatedExpressionProcessor.processEvaluatedExpressions(factoryElement.getAnnotationMetadata(), null); + for (ParameterElement parameterElement : parameters) { + evaluatedExpressionProcessor.processEvaluatedExpressions(parameterElement.getAnnotationMetadata(), null); + } if (isInterceptedLifeCycleByType(this.annotationMetadata, "POST_CONSTRUCT")) { - visitPostConstructMethodDefinition(true); + buildMethodDefinition.postConstruct(true); } if (!superBeanDefinition && isInterceptedLifeCycleByType(this.annotationMetadata, "PRE_DESTROY")) { - visitPreDestroyMethodDefinition(true); - } - } - - private void invokeConstructorChain(GeneratorAdapter generatorAdapter, int constructorLocalIndex, int parametersLocalIndex, List parameters) { - // 1st argument: The resolution context - generatorAdapter.loadArg(0); - // 2nd argument: The bean context - generatorAdapter.loadArg(1); - // 3rd argument: The interceptors if present - if (StringUtils.isNotEmpty(interceptedType)) { - // interceptors will be last entry in parameter list for interceptors types - generatorAdapter.loadLocal(parametersLocalIndex); - // array index for last parameter - generatorAdapter.push(AopProxyWriter.findInterceptorsListParameterIndex(parameters)); - generatorAdapter.arrayLoad(TYPE_OBJECT); - pushCastToType(generatorAdapter, List.class); - } else { - // for non interceptor types we have to perform a lookup based on the binding - generatorAdapter.visitInsn(ACONST_NULL); - } - // 4th argument: the bean definition - generatorAdapter.loadThis(); - // 5th argument: The constructor - generatorAdapter.loadLocal(constructorLocalIndex); - // 6th argument: additional proxy parameters count - if (getInterceptedType().isPresent()) { - generatorAdapter.push(AopProxyWriter.ADDITIONAL_PARAMETERS_COUNT); - } else { - generatorAdapter.push(0); + buildMethodDefinition.preDestroy(true); } - // 7th argument: load the Object[] for the parameters - generatorAdapter.loadLocal(parametersLocalIndex); - - generatorAdapter.visitMethodInsn( - INVOKESTATIC, - "io/micronaut/aop/chain/ConstructorInterceptorChain", - METHOD_NAME_INSTANTIATE, - METHOD_DESCRIPTOR_CONSTRUCTOR_INSTANTIATE, - false - ); } - private int initInterceptedConstructorWriter( - GeneratorAdapter buildMethodVisitor, - List parameters, - @Nullable FactoryMethodDef factoryMethodDef) { - // write the constructor that is a subclass of AbstractConstructorInjectionPoint - InnerClassDef constructorInjectionPointInnerClass = newInnerClass(AbstractBeanDefinitionBeanConstructor.class); - final ClassWriter interceptedConstructorWriter = constructorInjectionPointInnerClass.innerClassWriter; - org.objectweb.asm.commons.Method constructorMethod = org.objectweb.asm.commons.Method.getMethod(CONSTRUCTOR_ABSTRACT_CONSTRUCTOR_IP); - GeneratorAdapter protectedConstructor; - - final boolean hasFactoryMethod = factoryMethodDef != null; - final String interceptedConstructorDescriptor; - final Type factoryType = hasFactoryMethod ? factoryMethodDef.factoryType : null; - final String factoryFieldName = "$factory"; - if (hasFactoryMethod) { - // for factory methods we have to store the factory instance in a field and modify the constructor pass the factory instance - newFinalField(interceptedConstructorWriter, factoryType, factoryFieldName); - - interceptedConstructorDescriptor = getConstructorDescriptor(new Type[]{ - TYPE_BEAN_DEFINITION, - factoryType - }); - } else { - interceptedConstructorDescriptor = constructorMethod.getDescriptor(); - - } - protectedConstructor = new GeneratorAdapter( - interceptedConstructorWriter.visitMethod( - ACC_PROTECTED, CONSTRUCTOR_NAME, - interceptedConstructorDescriptor, - null, - null - ), - ACC_PROTECTED, - CONSTRUCTOR_NAME, - interceptedConstructorDescriptor - ); - if (hasFactoryMethod) { - protectedConstructor.loadThis(); - protectedConstructor.loadArg(1); - protectedConstructor.putField(constructorInjectionPointInnerClass.innerClassType, factoryFieldName, factoryType); - } - protectedConstructor.loadThis(); - protectedConstructor.loadArg(0); - protectedConstructor.invokeConstructor(Type.getType(AbstractBeanDefinitionBeanConstructor.class), constructorMethod); - protectedConstructor.returnValue(); - protectedConstructor.visitMaxs(1, 1); - protectedConstructor.visitEnd(); + @Nullable + private StatementDef invokeCheckIfShouldLoadIfNecessary(VariableDef.This aThis, List parameters) { + AnnotationValue requiresAnnotation = annotationMetadata.getAnnotation(Requires.class); + if (requiresAnnotation != null + && requiresAnnotation.stringValue(RequiresCondition.MEMBER_BEAN).isPresent() + && requiresAnnotation.stringValue(RequiresCondition.MEMBER_BEAN_PROPERTY).isPresent()) { - // now we need to implement the invoke method to execute the actual instantiation - final GeneratorAdapter invokeMethod = startPublicMethod(interceptedConstructorWriter, METHOD_BEAN_CONSTRUCTOR_INSTANTIATE); - if (hasFactoryMethod) { - invokeMethod.loadThis(); - invokeMethod.getField( - constructorInjectionPointInnerClass.innerClassType, - factoryFieldName, - factoryType - ); - pushCastToType(invokeMethod, factoryType); - } else { - invokeMethod.visitTypeInsn(NEW, beanType.getInternalName()); - invokeMethod.visitInsn(DUP); - } - for (int i = 0; i < parameters.size(); i++) { - invokeMethod.loadArg(0); - invokeMethod.push(i); - invokeMethod.arrayLoad(TYPE_OBJECT); - pushCastToType(invokeMethod, parameters.get(i)); - } - if (hasFactoryMethod) { - if (factoryMethodDef.factoryMethod instanceof MethodElement) { + MethodDef checkIfShouldLoad = buildCheckIfShouldLoadMethod(); - invokeMethod.visitMethodInsn( - INVOKEVIRTUAL, - factoryType.getInternalName(), - factoryMethodDef.factoryMethod.getName(), - factoryMethodDef.methodDescriptor, - false - ); - } else { - invokeMethod.getField(factoryType, factoryMethodDef.factoryMethod.getName(), beanType); - } - } else { - String constructorDescriptor = getConstructorDescriptor(parameters); - invokeMethod.visitMethodInsn(INVOKESPECIAL, beanType.getInternalName(), "", constructorDescriptor, false); - } - invokeMethod.returnValue(); - invokeMethod.visitMaxs(1, 1); - invokeMethod.visitEnd(); - - // instantiate a new instance and return - buildMethodVisitor.visitTypeInsn(NEW, constructorInjectionPointInnerClass.constructorInternalName); - buildMethodVisitor.visitInsn(DUP); - // pass outer class instance to constructor - buildMethodVisitor.loadThis(); - - if (hasFactoryMethod) { - buildMethodVisitor.loadLocal(factoryMethodDef.factoryVar); - pushCastToType(buildMethodVisitor, factoryType); - } - - buildMethodVisitor.visitMethodInsn( - INVOKESPECIAL, - constructorInjectionPointInnerClass.constructorInternalName, - "", - interceptedConstructorDescriptor, - false - ); + classDefBuilder.addMethod( + checkIfShouldLoad + ); - final int constructorIndex = buildMethodVisitor.newLocal(Type.getType(AbstractBeanDefinitionBeanConstructor.class)); - buildMethodVisitor.storeLocal(constructorIndex); - return constructorIndex; + return aThis.invoke(checkIfShouldLoad, parameters); + } + return StatementDef.multi(); } - private void newFinalField(ClassWriter classWriter, Type fieldType, String fieldName) { - classWriter - .visitField(ACC_PRIVATE | ACC_FINAL, - fieldName, - fieldType.getDescriptor(), - null, - null - ); + private MethodDef buildCheckIfShouldLoadMethod() { + return MethodDef.override(CHECK_IF_SHOULD_LOAD_METHOD) + .build((aThis, methodParameters) -> { + List injectedTypes = new ArrayList<>(annotationInjectionPoints.keySet()); + List statements = new ArrayList<>(); + for (int index = 0; index < injectedTypes.size(); index++) { + ClassElement injectedType = injectedTypes.get(index); + List annotationVisitData = annotationInjectionPoints.get(injectedType); + if (annotationVisitData.isEmpty()) { + continue; + } + AnnotationVisitData data = annotationVisitData.get(0); + ExpressionDef beanExpression = getBeanForAnnotation(aThis, methodParameters, index, data.memberBeanType); + + if (annotationVisitData.size() == 1) { + statements.add( + checkInjectedBean(aThis, data, beanExpression.invoke(data.memberPropertyGetter)) + ); + } else { + statements.add( + beanExpression.newLocal("beanInstance" + index, beanInstanceVar -> StatementDef.multi( + annotationVisitData.stream(). + map(d -> checkInjectedBean( + aThis, + d, + beanInstanceVar.invoke(d.memberPropertyGetter) + ) + ).toList() + )) + ); + } + } + return StatementDef.multi(statements); + }); } - private InnerClassDef newInnerClass(Class superType) { - ClassWriter interceptedConstructorWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - String interceptedConstructorWriterName = newInnerClassName(); - this.innerClasses.put(interceptedConstructorWriterName, interceptedConstructorWriter); - final String constructorInternalName = getInternalName(interceptedConstructorWriterName); - final Type interceptedConstructorType = getTypeReferenceForName(interceptedConstructorWriterName); - interceptedConstructorWriter.visit(V17, ACC_SYNTHETIC | ACC_FINAL | ACC_PRIVATE, - constructorInternalName, - null, - Type.getInternalName(superType), - null - ); + private ExpressionDef.InvokeInstanceMethod checkInjectedBean(VariableDef.This aThis, AnnotationVisitData data, ExpressionDef valueExpression) { + return aThis + .invoke( + CHECK_INJECTED_BEAN_PROPERTY_VALUE, - interceptedConstructorWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - interceptedConstructorWriter.visitOuterClass( - beanDefinitionInternalName, - null, - null - ); - classWriter.visitInnerClass(constructorInternalName, beanDefinitionInternalName, null, ACC_PRIVATE); - return new InnerClassDef( - interceptedConstructorWriter, - constructorInternalName, - interceptedConstructorType - ); + ExpressionDef.constant(data.memberPropertyName), + valueExpression, + ExpressionDef.constant(data.requiredValue), + ExpressionDef.constant(data.notEqualsValue) + ); } - @NonNull - private String newInnerClassName() { - return this.beanDefinitionName + "$" + ++innerClassIndex; - } + private ExpressionDef.Cast getBeanForAnnotation(VariableDef.This aThis, + List methodParameters, + int currentTypeIndex, + TypedElement memberType) { + return aThis.invoke( + GET_BEAN_FOR_ANNOTATION, - private int createConstructorParameterArray(List parameters, GeneratorAdapter buildMethodVisitor) { - return createParameterArray(parameters, buildMethodVisitor, (index, parameter) -> - pushConstructorArgument( - buildMethodVisitor, - parameter, - index, - true - ) - ); + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 3rd argument the injected bean index + ExpressionDef.constant(currentTypeIndex), + // push qualifier + getQualifier(memberType, resolveAnnotationArgument(0)) + ).cast(TypeDef.erasure(memberType)); } - private static int createParameterArray(List parameters, GeneratorAdapter buildMethodVisitor, BiConsumer parameterHandler) { - pushNewArrayIndexed(buildMethodVisitor, Object.class, parameters, parameterHandler); - int local = buildMethodVisitor.newLocal(Type.getType(Object[].class)); - buildMethodVisitor.storeLocal(local); - return local; + private ExpressionDef invokeConstructorChain(VariableDef.This aThis, + List methodParameters, + ExpressionDef beanConstructor, + ExpressionDef constructorValue, + List parameters) { + return ClassTypeDef.of(ConstructorInterceptorChain.class) + .invokeStatic( + METHOD_DESCRIPTOR_CONSTRUCTOR_INSTANTIATE, + // 1st argument: The resolution context + methodParameters.get(0), + // 2nd argument: The bean context + methodParameters.get(1), + // 3rd argument: The interceptors if present + StringUtils.isNotEmpty(interceptedType) ? + constructorValue.arrayElement(AopProxyWriter.findInterceptorsListParameterIndex(parameters)).cast(List.class) + : ExpressionDef.nullValue(), + // 4th argument: the bean definition + aThis, + // 5th argument: The constructor + beanConstructor, + // 6th argument: additional proxy parameters count + interceptedType != null ? ExpressionDef.constant(AopProxyWriter.ADDITIONAL_PARAMETERS_COUNT) : ExpressionDef.constant(0), + // 7th argument: load the Object[] for the parameters + constructorValue + ); } private boolean isConstructorIntercepted(Element constructor) { @@ -3992,13 +3869,13 @@ private boolean isInterceptedLifeCycleByType(AnnotationMetadata annotationMetada final boolean isConstructorInterceptionCandidate = (isProxyTarget && !isAopType) || (isAopType && !isProxyTarget); final boolean hasAroundConstruct; final AnnotationValue interceptorBindings - = annotationMetadata.getAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDINGS); + = annotationMetadata.getAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDINGS); List> interceptorBindingAnnotations; if (interceptorBindings != null) { interceptorBindingAnnotations = interceptorBindings.getAnnotations(AnnotationMetadata.VALUE_MEMBER); hasAroundConstruct = interceptorBindingAnnotations - .stream() - .anyMatch(av -> av.stringValue("kind").map(k -> k.equals(interceptType)).orElse(false)); + .stream() + .anyMatch(av -> av.stringValue("kind").map(k -> k.equals(interceptType)).orElse(false)); } else { interceptorBindingAnnotations = Collections.emptyList(); hasAroundConstruct = false; @@ -4011,7 +3888,7 @@ private boolean isInterceptedLifeCycleByType(AnnotationMetadata annotationMetada if (!isSuperFactory && typeMetadata instanceof AnnotationMetadataHierarchy hierarchy) { typeMetadata = hierarchy.getRootMetadata(); final AnnotationValue av = - typeMetadata.getAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDINGS); + typeMetadata.getAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDINGS); if (av != null) { interceptorBindingAnnotations = av.getAnnotations(AnnotationMetadata.VALUE_MEMBER); } else { @@ -4020,311 +3897,291 @@ private boolean isInterceptedLifeCycleByType(AnnotationMetadata annotationMetada } // if no other AOP advice is applied return interceptorBindingAnnotations - .stream() - .noneMatch(av -> av.stringValue("kind").map(k -> k.equals("AROUND")).orElse(false)); + .stream() + .noneMatch(av -> av.stringValue("kind").map(k -> k.equals("AROUND")).orElse(false)); } else { return false; } }); } - private boolean pushConstructorArguments(GeneratorAdapter buildMethodVisitor, - ParameterElement[] parameters) { - int size = parameters.length; - boolean hasInjectScope = false; - if (size > 0) { - for (int i = 0; i < parameters.length; i++) { - ParameterElement parameter = parameters[i]; - pushConstructorArgument(buildMethodVisitor, parameter, i); - if (parameter.hasDeclaredAnnotation(InjectScope.class)) { - hasInjectScope = true; - } - } + private List getConstructorArgumentValues(VariableDef.This aThis, + List methodParameters, + List parameters, + boolean isParametrized, + VariableDef constructorMethodVar) { + List values = new ArrayList<>(); + for (int i = 0; i < parameters.size(); i++) { + ParameterElement parameter = parameters.get(i); + values.add( + getConstructorArgument(aThis, methodParameters, parameter, i, isParametrized, constructorMethodVar) + ); } - return hasInjectScope; + return values; } - private boolean pushParametersAsArray(GeneratorAdapter buildMethodVisitor, ParameterElement[] parameters) { - pushNewArrayIndexed( - buildMethodVisitor, - Object.class, - Arrays.asList(parameters), - (index, parameter) -> pushConstructorArgument(buildMethodVisitor, parameter, index, true) - ); - boolean hasInjectScope = false; + private static boolean hasInjectScope(ParameterElement[] parameters) { for (ParameterElement parameter : parameters) { - if (parameter.hasDeclaredAnnotation(InjectScope.class)) { - hasInjectScope = true; + if (hasInjectScope(parameter)) { + return true; } } - return hasInjectScope; + return false; } - private void pushConstructorArgument(GeneratorAdapter buildMethodVisitor, - ParameterElement parameter, - int index) { - pushConstructorArgument(buildMethodVisitor, parameter, index, false); + private static boolean hasInjectScope(AnnotationMetadata annotationMetadata) { + return annotationMetadata.hasDeclaredAnnotation(InjectScope.class); } - private void pushConstructorArgument(GeneratorAdapter buildMethodVisitor, - ParameterElement parameter, - int index, - boolean castToObject) { + private ExpressionDef getConstructorArgument(VariableDef.This aThis, + List methodParameters, + ParameterElement parameter, + int index, + boolean isParametrized, + VariableDef constructorMethodVar) { AnnotationMetadata annotationMetadata = parameter.getAnnotationMetadata(); if (isAnnotatedWithParameter(annotationMetadata) && isParametrized) { // load the args - buildMethodVisitor.loadArg(2); - // the argument name - buildMethodVisitor.push(parameter.getName()); - buildMethodVisitor.invokeInterface(Type.getType(Map.class), org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredMethod(Map.class, "get", Object.class))); - pushCastToType(buildMethodVisitor, parameter); - } else if (!pushValueBypassingBeanContext(buildMethodVisitor, parameter.getGenericType())) { - boolean hasGenericType = false; - boolean isArray; - Method methodToInvoke; - final ClassElement genericType = parameter.getGenericType(); - if (isValueType(annotationMetadata) && !isInnerType(genericType)) { - Optional property = parameter.stringValue(Property.class, "name"); - if (property.isPresent()) { - pushInvokeGetPropertyValueForConstructor(buildMethodVisitor, index, parameter, property.get()); + return methodParameters.get(2) + .invoke( + GET_MAP_METHOD, + ExpressionDef.constant(parameter.getName()) + ); + } + ExpressionDef expression = getValueBypassingBeanContext(parameter.getGenericType(), methodParameters); + if (expression != null) { + return expression; + } + + boolean hasGenericType = false; + boolean isArray; + Method methodToInvoke; + final ClassElement genericType = parameter.getGenericType(); + if (isValueType(annotationMetadata) && !isInnerType(genericType)) { + Optional property = parameter.stringValue(Property.class, "name"); + if (property.isPresent()) { + return getInvokeGetPropertyValueForConstructor(aThis, methodParameters, index, parameter, property.get()); + } else { + if (parameter.getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { + return getInvokeGetEvaluatedExpressionValueForConstructorArgument(aThis, index, parameter); } else { - if (parameter.getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { - pushInvokeGetEvaluatedExpressionValueForConstructorArgument(buildMethodVisitor, index, parameter); - } else { - Optional valueValue = parameter.stringValue(Value.class); - valueValue.ifPresent(s -> pushInvokeGetPropertyPlaceholderValueForConstructor(buildMethodVisitor, index, parameter, s)); + Optional valueValue = parameter.stringValue(Value.class); + if (valueValue.isPresent()) { + return getInvokeGetPropertyPlaceholderValueForConstructor(aThis, methodParameters, index, parameter, valueValue.get()); } } - return; - } else { - isArray = genericType.isArray(); - if (genericType.isAssignable(Collection.class) || isArray) { - hasGenericType = true; - ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); - if (typeArgument != null && !typeArgument.isPrimitive()) { - if (typeArgument.isAssignable(BeanRegistration.class)) { - methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_CONSTRUCTOR_ARGUMENT; - } else { - methodToInvoke = GET_BEANS_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } + } + return ExpressionDef.nullValue(); + } else { + isArray = genericType.isArray(); + if (genericType.isAssignable(Collection.class) || isArray) { + hasGenericType = true; + ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); + if (typeArgument != null && !typeArgument.isPrimitive()) { + if (typeArgument.isAssignable(BeanRegistration.class)) { + methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_CONSTRUCTOR_ARGUMENT; } else { - methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; - hasGenericType = false; + methodToInvoke = GET_BEANS_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; } - } else if (isInjectableMap(genericType)) { - hasGenericType = true; - methodToInvoke = GET_MAP_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(Stream.class)) { - hasGenericType = true; - methodToInvoke = GET_STREAM_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(Optional.class)) { - hasGenericType = true; - methodToInvoke = FIND_BEAN_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(BeanRegistration.class)) { - hasGenericType = true; - methodToInvoke = GET_BEAN_REGISTRATION_FOR_CONSTRUCTOR_ARGUMENT; } else { methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; + hasGenericType = false; } - } - // Load this for method call - buildMethodVisitor.loadThis(); - // load the first two arguments of the method (the BeanResolutionContext and the BeanContext) to be passed to the method - buildMethodVisitor.loadArg(0); - buildMethodVisitor.loadArg(1); - // pass the index of the method as the third argument - buildMethodVisitor.push(index); - if (hasGenericType) { - resolveConstructorArgumentGenericType(buildMethodVisitor, parameter.getGenericType(), index); - } - // push qualifier - pushQualifier(buildMethodVisitor, parameter, () -> resolveConstructorArgument(buildMethodVisitor, index)); - // invoke method - pushInvokeMethodOnSuperClass(buildMethodVisitor, methodToInvoke); - if (isArray && hasGenericType) { - convertToArray(parameter.getGenericType().fromArray(), buildMethodVisitor); - } - if (castToObject) { - if (parameter.isPrimitive()) { - pushCastToType(buildMethodVisitor, Object.class); - } + } else if (isInjectableMap(genericType)) { + hasGenericType = true; + methodToInvoke = GET_MAP_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(Stream.class)) { + hasGenericType = true; + methodToInvoke = GET_STREAM_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(Optional.class)) { + hasGenericType = true; + methodToInvoke = FIND_BEAN_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(BeanRegistration.class)) { + hasGenericType = true; + methodToInvoke = GET_BEAN_REGISTRATION_FOR_CONSTRUCTOR_ARGUMENT; } else { - pushCastToType(buildMethodVisitor, parameter); + methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; } } + List values = new ArrayList<>(); + // load the first two arguments of the method (the BeanResolutionContext and the BeanContext) to be passed to the method + values.add(methodParameters.get(0)); + values.add(methodParameters.get(1)); + // pass the index of the method as the third argument + values.add(ExpressionDef.constant(index)); + if (hasGenericType) { + values.add( + resolveConstructorArgumentGenericType(parameter.getGenericType(), index, constructorMethodVar) + ); + } + // push qualifier + values.add( + getQualifier(parameter, resolveConstructorArgument(index, constructorMethodVar)) + ); + ExpressionDef result = aThis.superRef().invoke(methodToInvoke, values); + if (isArray && hasGenericType) { + result = convertToArray(parameter.getGenericType().fromArray(), result); + } + return result.cast(TypeDef.erasure(parameter.getType())); } - private void pushInvokeGetPropertyValueForConstructor(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 4th argument the argument index - injectMethodVisitor.push(i); - // 5th property value - injectMethodVisitor.push(value); - // 6 cli property name - injectMethodVisitor.push(getCliPrefix(entry.getName())); + private ExpressionDef getInvokeGetPropertyValueForConstructor(VariableDef.This aThis, + List methodParameters, + int i, ParameterElement entry, String value) { - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_VALUE_FOR_CONSTRUCTOR_ARGUMENT); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); - } - - private void pushInvokeGetPropertyPlaceholderValueForConstructor(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry, String value) { - // load 'this' - injectMethodVisitor.loadThis(); - // 1st argument load BeanResolutionContext - injectMethodVisitor.loadArg(0); - // 2nd argument load BeanContext - injectMethodVisitor.loadArg(1); - // 4th argument the argument index - injectMethodVisitor.push(i); - // 5th property value - injectMethodVisitor.push(value); - - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_PROPERTY_PLACEHOLDER_VALUE_FOR_CONSTRUCTOR_ARGUMENT); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + return aThis.superRef().invoke( + GET_PROPERTY_VALUE_FOR_CONSTRUCTOR_ARGUMENT, + + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 4th argument the argument index + ExpressionDef.constant(i), + // 5th property value + ExpressionDef.constant(value), + // 6 cli property name + ExpressionDef.constant(getCliPrefix(entry.getName())) + + ).cast(TypeDef.erasure(entry.getType())); } - private void pushInvokeGetEvaluatedExpressionValueForConstructorArgument(GeneratorAdapter injectMethodVisitor, int i, ParameterElement entry) { - // load 'this' - injectMethodVisitor.loadThis(); - // 2nd argument the argument index - injectMethodVisitor.push(i); + private ExpressionDef getInvokeGetPropertyPlaceholderValueForConstructor(VariableDef.This aThis, + List methodParameters, + int i, ParameterElement entry, String value) { - pushInvokeMethodOnSuperClass(injectMethodVisitor, GET_EVALUATED_EXPRESSION_VALUE_FOR_CONSTRUCTOR_ARGUMENT); - // cast the return value to the correct type - pushCastToType(injectMethodVisitor, entry); + return aThis.superRef().invoke( + GET_PROPERTY_PLACEHOLDER_VALUE_FOR_CONSTRUCTOR_ARGUMENT, + + // 1st argument load BeanResolutionContext + methodParameters.get(0), + // 2nd argument load BeanContext + methodParameters.get(1), + // 4th argument the argument index + ExpressionDef.constant(i), + // 5th property value + ExpressionDef.constant(value) + ).cast(TypeDef.erasure(entry.getType())); } - private void resolveConstructorArgumentGenericType(GeneratorAdapter visitor, ClassElement type, int argumentIndex) { - if (!resolveArgumentGenericType(visitor, type)) { - resolveConstructorArgument(visitor, argumentIndex); - if (type.isAssignable(Map.class)) { - resolveSecondTypeArgument(visitor); - } else { - resolveFirstTypeArgument(visitor); - } - resolveInnerTypeArgumentIfNeeded(visitor, type); + private ExpressionDef getInvokeGetEvaluatedExpressionValueForConstructorArgument(VariableDef.This aThis, + int i, ParameterElement entry) { + return aThis.superRef() + .invoke(GET_EVALUATED_EXPRESSION_VALUE_FOR_CONSTRUCTOR_ARGUMENT, ExpressionDef.constant(i)) + .cast(TypeDef.erasure(entry.getType())); + } + + private ExpressionDef resolveConstructorArgumentGenericType(ClassElement type, int argumentIndex, VariableDef constructorMethodVar) { + ExpressionDef expressionDef = resolveArgumentGenericType(type); + if (expressionDef != null) { + return expressionDef; + } + ExpressionDef argumentExpression = resolveConstructorArgument(argumentIndex, constructorMethodVar); + if (type.isAssignable(Map.class)) { + argumentExpression = resolveSecondTypeArgument(argumentExpression); + } else { + argumentExpression = resolveFirstTypeArgument(argumentExpression); } + return resolveInnerTypeArgumentIfNeeded(argumentExpression, type); } - private void resolveConstructorArgument(GeneratorAdapter visitor, int argumentIndex) { - Type constructorField = Type.getType(AbstractInitializableBeanDefinition.MethodOrFieldReference.class); - Type methodRefType = Type.getType(AbstractInitializableBeanDefinition.MethodReference.class); - visitor.getStatic(beanDefinitionType, FIELD_CONSTRUCTOR, constructorField); - pushCastToType(visitor, methodRefType); - visitor.getField(methodRefType, "arguments", Type.getType(Argument[].class)); - visitor.push(argumentIndex); - visitor.arrayLoad(Type.getType(Argument.class)); + private ExpressionDef resolveConstructorArgument(int argumentIndex, VariableDef constructorMethodVar) { + return constructorMethodVar + .field("arguments", ClassTypeDef.of(Argument.class).array()) + .arrayElement(argumentIndex); } - private void resolveMethodArgumentGenericType(GeneratorAdapter visitor, ClassElement type, int methodIndex, int argumentIndex) { - if (!resolveArgumentGenericType(visitor, type)) { - resolveMethodArgument(visitor, methodIndex, argumentIndex); - if (type.isAssignable(Map.class)) { - resolveSecondTypeArgument(visitor); - } else { - resolveFirstTypeArgument(visitor); - } - resolveInnerTypeArgumentIfNeeded(visitor, type); + private ExpressionDef resolveMethodArgumentGenericType(ClassElement type, int methodIndex, int argumentIndex) { + ExpressionDef expressionDef = resolveArgumentGenericType(type); + if (expressionDef != null) { + return expressionDef; + } + expressionDef = resolveMethodArgument(methodIndex, argumentIndex); + if (type.isAssignable(Map.class)) { + expressionDef = resolveSecondTypeArgument(expressionDef); + } else { + expressionDef = resolveFirstTypeArgument(expressionDef); } + return resolveInnerTypeArgumentIfNeeded(expressionDef, type); } - private void resolveMethodArgument(GeneratorAdapter visitor, int methodIndex, int argumentIndex) { - Type methodsRef = Type.getType(AbstractInitializableBeanDefinition.MethodReference[].class); - Type methodRefType = Type.getType(AbstractInitializableBeanDefinition.MethodReference.class); - visitor.getStatic(beanDefinitionType, FIELD_INJECTION_METHODS, methodsRef); - visitor.push(methodIndex); - visitor.arrayLoad(methodsRef); - visitor.getField(methodRefType, "arguments", Type.getType(Argument[].class)); - visitor.push(argumentIndex); - visitor.arrayLoad(Type.getType(Argument.class)); + private ExpressionDef resolveMethodArgument(int methodIndex, int argumentIndex) { + return beanDefinitionTypeDef. + getStaticField(FIELD_INJECTION_METHODS, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodReference.class).array()) + .arrayElement(methodIndex) + .field("arguments", ClassTypeDef.of(Argument.class).array()) + .arrayElement(argumentIndex); } - private void resolveFieldArgumentGenericType(GeneratorAdapter visitor, ClassElement type, int fieldIndex) { - if (!resolveArgumentGenericType(visitor, type)) { - resolveFieldArgument(visitor, fieldIndex); - if (type.isAssignable(Map.class)) { - resolveSecondTypeArgument(visitor); - } else { - resolveFirstTypeArgument(visitor); - } - resolveInnerTypeArgumentIfNeeded(visitor, type); + private ExpressionDef resolveFieldArgumentGenericType(ClassElement type, int fieldIndex) { + ExpressionDef argumentExpression = resolveArgumentGenericType(type); + if (argumentExpression != null) { + return argumentExpression; + } + argumentExpression = resolveFieldArgument(fieldIndex); + if (type.isAssignable(Map.class)) { + argumentExpression = resolveSecondTypeArgument(argumentExpression); + } else { + argumentExpression = resolveFirstTypeArgument(argumentExpression); } + return resolveInnerTypeArgumentIfNeeded(argumentExpression, type); } - private void resolveAnnotationArgument(GeneratorAdapter visitor, int index) { - visitor.getStatic(beanDefinitionType, FIELD_ANNOTATION_INJECTIONS, Type.getType(AbstractInitializableBeanDefinition.FieldReference[].class)); - visitor.push(index); - visitor.arrayLoad(Type.getType(AbstractInitializableBeanDefinition.AnnotationReference.class)); - visitor.getField(Type.getType(AbstractInitializableBeanDefinition.AnnotationReference.class), "argument", Type.getType(Argument.class)); + private ExpressionDef resolveAnnotationArgument(int index) { + return beanDefinitionTypeDef.getStaticField(FIELD_ANNOTATION_INJECTIONS, TypeDef.of(AbstractInitializableBeanDefinition.AnnotationReference[].class)) + .arrayElement(index) + .field("argument", TypeDef.of(Argument.class)); } - private void resolveFieldArgument(GeneratorAdapter visitor, int fieldIndex) { - visitor.getStatic(beanDefinitionType, FIELD_INJECTION_FIELDS, Type.getType(AbstractInitializableBeanDefinition.FieldReference[].class)); - visitor.push(fieldIndex); - visitor.arrayLoad(Type.getType(AbstractInitializableBeanDefinition.FieldReference.class)); - visitor.getField(Type.getType(AbstractInitializableBeanDefinition.FieldReference.class), "argument", Type.getType(Argument.class)); + private ExpressionDef resolveFieldArgument(int fieldIndex) { + return beanDefinitionTypeDef.getStaticField(FIELD_INJECTION_FIELDS, TypeDef.of(AbstractInitializableBeanDefinition.FieldReference[].class)) + .arrayElement(fieldIndex) + .field("argument", TypeDef.of(Argument.class)); } - private boolean resolveArgumentGenericType(GeneratorAdapter visitor, ClassElement type) { + @Nullable + private ExpressionDef resolveArgumentGenericType(ClassElement type) { if (type.isArray()) { if (!type.getTypeArguments().isEmpty() && isInternalGenericTypeContainer(type.fromArray())) { // skip for arrays of BeanRegistration - return false; + return null; } final ClassElement componentType = type.fromArray(); if (componentType.isPrimitive()) { - visitor.getStatic( - TYPE_ARGUMENT, - componentType.getName().toUpperCase(Locale.ENGLISH), - TYPE_ARGUMENT - ); - } else { - - visitor.push(JavaModelUtils.getTypeReference(componentType)); - visitor.push((String) null); - invokeInterfaceStaticMethod( - visitor, - Argument.class, - METHOD_CREATE_ARGUMENT_SIMPLE + return ArgumentExpUtils.TYPE_ARGUMENT.getStaticField( + componentType.getName().toUpperCase(Locale.ENGLISH), + ArgumentExpUtils.TYPE_ARGUMENT ); } - return true; + return ArgumentExpUtils.TYPE_ARGUMENT.invokeStatic( + ArgumentExpUtils.METHOD_CREATE_ARGUMENT_SIMPLE, + + ExpressionDef.constant(TypeDef.erasure(componentType)), + ExpressionDef.nullValue() + ); } else if (type.getTypeArguments().isEmpty()) { - visitor.visitInsn(ACONST_NULL); - return true; + return ExpressionDef.nullValue(); } - return false; + return null; } - private void resolveInnerTypeArgumentIfNeeded(GeneratorAdapter visitor, ClassElement type) { + private ExpressionDef resolveInnerTypeArgumentIfNeeded(ExpressionDef argumentExpression, ClassElement type) { if (isInternalGenericTypeContainer(type.getFirstTypeArgument().orElse(null))) { - resolveFirstTypeArgument(visitor); + return resolveFirstTypeArgument(argumentExpression); } + return argumentExpression; } private boolean isInternalGenericTypeContainer(@Nullable ClassElement type) { return type != null && type.isAssignable(BeanRegistration.class); } - private void resolveFirstTypeArgument(GeneratorAdapter visitor) { - visitor.invokeInterface(Type.getType(TypeVariableResolver.class), - org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredInternalMethod(TypeVariableResolver.class, "getTypeParameters"))); - visitor.push(0); - visitor.arrayLoad(Type.getType(Argument.class)); + private ExpressionDef resolveFirstTypeArgument(ExpressionDef argumentExpression) { + return argumentExpression.invoke(GET_TYPE_PARAMETERS_METHOD).arrayElement(0); } - private void resolveSecondTypeArgument(GeneratorAdapter visitor) { - visitor.invokeInterface(Type.getType(TypeVariableResolver.class), - org.objectweb.asm.commons.Method.getMethod(ReflectionUtils.getRequiredInternalMethod(TypeVariableResolver.class, "getTypeParameters"))); - visitor.push(1); - visitor.arrayLoad(Type.getType(Argument.class)); + private ExpressionDef resolveSecondTypeArgument(ExpressionDef argumentExpression) { + return argumentExpression.invoke(GET_TYPE_PARAMETERS_METHOD).arrayElement(1); } private boolean isValueType(AnnotationMetadata annotationMetadata) { @@ -4345,219 +4202,106 @@ private boolean isParametrized(ParameterElement... parameters) { return Arrays.stream(parameters).anyMatch(p -> isAnnotatedWithParameter(p.getAnnotationMetadata())); } - private void defineBuilderMethod(boolean isParametrized) { - if (isParametrized) { - this.isParametrized = true; - } + private void addConstructor(StaticBlock staticBlock) { + if (superBeanDefinition) { + classDefBuilder.addMethod(MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) + -> aThis.superRef().invokeConstructor( - String methodDescriptor; - String methodSignature; - - if (isParametrized) { - methodDescriptor = getMethodDescriptor( - Object.class.getName(), - BeanResolutionContext.class.getName(), - BeanContext.class.getName(), - Map.class.getName() - ); - methodSignature = getMethodSignature( - getTypeDescriptor(beanTypeElement), - getTypeDescriptor(BeanResolutionContext.class.getName()), - getTypeDescriptor(BeanContext.class.getName()), - getTypeDescriptor(Map.class.getName()) - ); + ExpressionDef.constant(beanTypeDef), + beanDefinitionTypeDef.getStaticField(staticBlock.constructorRefField) + ))); } else { - methodDescriptor = getMethodDescriptor( - Object.class.getName(), - BeanResolutionContext.class.getName(), - BeanContext.class.getName() - ); - methodSignature = getMethodSignature( - getTypeDescriptor(beanTypeElement), - getTypeDescriptor(BeanResolutionContext.class.getName()), - getTypeDescriptor(BeanContext.class.getName()) - ); - } - - String methodName = isParametrized ? "doInstantiate" : METHOD_NAME_INSTANTIATE; - this.buildMethodVisitor = new GeneratorAdapter(classWriter.visitMethod( - ACC_PUBLIC, - methodName, - methodDescriptor, - methodSignature, - null), ACC_PUBLIC, methodName, methodDescriptor); - } - - private void pushBeanDefinitionMethodInvocation(GeneratorAdapter buildMethodVisitor, String methodName) { - buildMethodVisitor.loadThis(); - buildMethodVisitor.loadArg(0); - buildMethodVisitor.loadArg(1); - buildMethodVisitor.loadLocal(buildInstanceLocalVarIndex); - pushBoxPrimitiveIfNecessary(beanType, buildMethodVisitor); - buildMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, - superBeanDefinition ? superType.getInternalName() : beanDefinitionInternalName, - methodName, - METHOD_DESCRIPTOR_INITIALIZE, - false); - } - - private void addConstructor() { - - GeneratorAdapter publicConstructor = new GeneratorAdapter( - classWriter.visitMethod(ACC_PUBLIC, CONSTRUCTOR_NAME, DESCRIPTOR_DEFAULT_CONSTRUCTOR, null, null), - ACC_PUBLIC, - CONSTRUCTOR_NAME, - DESCRIPTOR_DEFAULT_CONSTRUCTOR - ); - publicConstructor.loadThis(); - publicConstructor.push(beanType); - publicConstructor.getStatic(beanDefinitionType, FIELD_CONSTRUCTOR, Type.getType(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)); - publicConstructor.invokeConstructor(superBeanDefinition ? superType : beanDefinitionType, PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR); - publicConstructor.returnValue(); - publicConstructor.visitMaxs(5, 1); - publicConstructor.visitEnd(); - - // Call protected super constructor if definition is extending another one - - if (!superBeanDefinition) { - // create protected constructor for subclasses of AbstractBeanDefinition - GeneratorAdapter protectedConstructor = new GeneratorAdapter( - classWriter.visitMethod(ACC_PROTECTED, - PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR.getName(), - PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR.getDescriptor(), null, null), - ACC_PROTECTED, - PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR.getName(), - PROTECTED_ABSTRACT_BEAN_DEFINITION_CONSTRUCTOR.getDescriptor() - ); - - AnnotationMetadata annotationMetadata = this.annotationMetadata != null ? this.annotationMetadata : AnnotationMetadata.EMPTY_METADATA; - - protectedConstructor.loadThis(); - // 1: beanType - protectedConstructor.loadArg(0); - // 2: `AbstractBeanDefinition2.MethodOrFieldReference.class` constructor - protectedConstructor.loadArg(1); - - // 3: annotationMetadata - if (annotationMetadata.isEmpty()) { - protectedConstructor.push((String) null); - } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { - String className = reference.getClassName(); - protectedConstructor.getStatic(getTypeReferenceForName(className), AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } else { - protectedConstructor.getStatic(beanDefinitionType, AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); - } - - // 4: `AbstractBeanDefinition2.MethodReference[].class` methodInjection - if (allMethodVisits.isEmpty()) { - protectedConstructor.push((String) null); - } else { - protectedConstructor.getStatic(beanDefinitionType, FIELD_INJECTION_METHODS, Type.getType(AbstractInitializableBeanDefinition.MethodReference[].class)); - } - // 5: `AbstractBeanDefinition2.FieldReference[].class` fieldInjection - if (fieldInjectionPoints.isEmpty()) { - protectedConstructor.push((String) null); - } else { - protectedConstructor.getStatic(beanDefinitionType, FIELD_INJECTION_FIELDS, Type.getType(AbstractInitializableBeanDefinition.FieldReference[].class)); - } - // 6: `AbstractBeanDefinition2.AnnotationReference[].class` annotationInjection - if (annotationInjectionPoints.isEmpty()) { - protectedConstructor.push((String) null); - } else { - protectedConstructor.getStatic(beanDefinitionType, FIELD_ANNOTATION_INJECTIONS, Type.getType(AbstractInitializableBeanDefinition.AnnotationReference[].class)); - } - // 7: `ExecutableMethod[]` executableMethods - if (executableMethodsDefinitionWriter == null) { - protectedConstructor.push((String) null); - } else { - Type execType = executableMethodsDefinitionWriter.getClassType(); - protectedConstructor.getStatic(beanDefinitionType, FIELD_EXECUTABLE_METHODS, execType); - } - // 8: `Map[]>` typeArgumentsMap - if (!hasTypeArguments()) { - protectedConstructor.push((String) null); - } else { - protectedConstructor.getStatic(beanDefinitionType, FIELD_TYPE_ARGUMENTS, Type.getType(Map.class)); - } - - // 9: `PrecalculatedInfo` - protectedConstructor.getStatic(beanDefinitionType, FIELD_PRECALCULATED_INFO, PRECALCULATED_INFO); - - if (BEAN_DEFINITION_CLASS_CONSTRUCTOR2.isPresent()) { - List> requirements = annotationMetadata.getAnnotationValuesByType(Requires.class); - if (requirements.isEmpty()) { - // 10: Pre conditions - pushNewArray(protectedConstructor, Condition.class, 0); - // 11: Post conditions - pushNewArray(protectedConstructor, Condition.class, 0); - } else { - // 10: Pre conditions - protectedConstructor.getStatic(beanDefinitionType, FIELD_PRE_START_CONDITIONS, Type.getType(Condition[].class)); - // 11: Post conditions - protectedConstructor.getStatic(beanDefinitionType, FIELD_POST_START_CONDITIONS, Type.getType(Condition[].class)); - } - // 12: Exception - protectedConstructor.getStatic(beanDefinitionType, FIELD_FAILED_INITIALIZATION, Type.getType(Throwable.class)); - - protectedConstructor.invokeConstructor(getSuperType(), org.objectweb.asm.commons.Method.getMethod(BEAN_DEFINITION_CLASS_CONSTRUCTOR2.get())); - } else { - BEAN_DEFINITION_CLASS_CONSTRUCTOR1.ifPresent(constructor1 -> - protectedConstructor.invokeConstructor(getSuperType(), org.objectweb.asm.commons.Method.getMethod(constructor1))); - } + MethodDef constructor = MethodDef.constructor() + .addModifiers(Modifier.PROTECTED) + .addParameters(Class.class, AbstractInitializableBeanDefinition.MethodOrFieldReference.class) + .build((aThis, methodParameters) -> { + + List values = new ArrayList<>(); + AnnotationMetadata annotationMetadata = this.annotationMetadata != null ? this.annotationMetadata : AnnotationMetadata.EMPTY_METADATA; + + // 1: beanType + values.add(methodParameters.get(0)); + // 2: `AbstractBeanDefinition2.MethodOrFieldReference.class` constructor + values.add(methodParameters.get(1)); + + // 3: annotationMetadata + if (annotationMetadata.isEmpty()) { + values.add(ExpressionDef.nullValue()); + } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { + values.add(AnnotationMetadataGenUtils.annotationMetadataReference(reference)); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.annotationMetadataField)); + } - protectedConstructor.returnValue(); - protectedConstructor.visitMaxs(20, 1); - protectedConstructor.visitEnd(); - } - } + // 4: `AbstractBeanDefinition2.MethodReference[].class` methodInjection + if (staticBlock.injectionMethodsField == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.injectionMethodsField)); + } + // 5: `AbstractBeanDefinition2.FieldReference[].class` fieldInjection + if (staticBlock.injectionFieldsField == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.injectionFieldsField)); + } + // 6: `AbstractBeanDefinition2.AnnotationReference[].class` annotationInjection + if (staticBlock.annotationInjectionsFieldType == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.annotationInjectionsFieldType)); + } + // 7: `ExecutableMethod[]` executableMethods + if (staticBlock.executableMethodsField == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.executableMethodsField)); + } + // 8: `Map[]>` typeArgumentsMap + if (staticBlock.typeArgumentsField == null) { + values.add(ExpressionDef.nullValue()); + } else { + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.typeArgumentsField)); + } + // 9: `PrecalculatedInfo` + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.precalculatedInfoField)); + + if (BEAN_DEFINITION_CLASS_CONSTRUCTOR2.isPresent()) { + if (staticBlock.preStartConditionsField == null) { + // 10: Pre conditions + values.add(ClassTypeDef.of(Condition.class).array().instantiate()); + // 11: Post conditions + values.add(ClassTypeDef.of(Condition.class).array().instantiate()); + } else { + // 10: Pre conditions + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.preStartConditionsField)); + // 11: Post conditions + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.postStartConditionsField)); + } + // 12: Exception + values.add(beanDefinitionTypeDef.getStaticField(staticBlock.failedInitializationField)); - private void pushPrecalculatedInfo(GeneratorAdapter staticInit, AnnotationMetadata annotationMetadata) { - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, - FIELD_PRECALCULATED_INFO, - PRECALCULATED_INFO.getDescriptor(), - null, - null - ); + return aThis.superRef(TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE).invokeConstructor(BEAN_DEFINITION_CLASS_CONSTRUCTOR2.get(), values); - staticInit.newInstance(PRECALCULATED_INFO); - staticInit.dup(); + } else if (BEAN_DEFINITION_CLASS_CONSTRUCTOR1.isPresent()) { + return aThis.superRef(TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE).invokeConstructor(BEAN_DEFINITION_CLASS_CONSTRUCTOR1.get(), values); + } else { + throw new IllegalStateException(); + } + }); + classDefBuilder.addMethod(constructor); + classDefBuilder.addMethod(MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) + -> aThis.invokeConstructor( + constructor, - // 1: `Optional` scope - String scope = annotationMetadata.getAnnotationNameByStereotype(AnnotationUtil.SCOPE).orElse(null); - if (scope != null) { - staticInit.push(scope); - staticInit.invokeStatic( - TYPE_OPTIONAL, - METHOD_OPTIONAL_OF - ); - } else { - staticInit.invokeStatic(TYPE_OPTIONAL, METHOD_OPTIONAL_EMPTY); + ExpressionDef.constant(beanTypeDef), + beanDefinitionTypeDef.getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) + ))); } - - // 2: `boolean` isAbstract - staticInit.push(isAbstract); - // 3: `boolean` isIterable - staticInit.push(isIterable(annotationMetadata)); - // 4: `boolean` isSingleton - staticInit.push( - isSingleton(scope) - ); - // 5: `boolean` isPrimary - staticInit.push( - annotationMetadata.hasDeclaredStereotype(Primary.class) - ); - // 6: `boolean` isConfigurationProperties - staticInit.push(isConfigurationProperties); - // 7: isContainerType - staticInit.push(isContainerType()); - // 8: staticInit - staticInit.push(preprocessMethods); - - // 9: hasEvaluatedExpressions - staticInit.push(evaluatedExpressionProcessor.hasEvaluatedExpressions()); - - staticInit.invokeConstructor(PRECALCULATED_INFO, PRECALCULATED_INFO_CONSTRUCTOR); - staticInit.putStatic(beanDefinitionType, FIELD_PRECALCULATED_INFO, PRECALCULATED_INFO); } private boolean isContainerType() { @@ -4572,90 +4316,87 @@ private boolean isIterable(AnnotationMetadata annotationMetadata) { return annotationMetadata.hasDeclaredStereotype(EachProperty.class) || annotationMetadata.hasDeclaredStereotype(EachBean.class); } - private void pushNewMethodReference(GeneratorAdapter staticInit, - Type beanType, - MethodElement methodElement, - AnnotationMetadata annotationMetadata, - boolean isPostConstructMethod, - boolean isPreDestroyMethod) { + private ExpressionDef getNewMethodReference(TypedElement beanType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + boolean isPostConstructMethod, + boolean isPreDestroyMethod) { annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - staticInit.newInstance(Type.getType(AbstractInitializableBeanDefinition.MethodReference.class)); - staticInit.dup(); - // 1: declaringType - staticInit.push(beanType); - // 2: methodName - staticInit.push(methodElement.getName()); - // 3: arguments - if (!methodElement.hasParameters()) { - staticInit.visitInsn(ACONST_NULL); - } else { - pushBuildArgumentsForMethod( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - staticInit, - Arrays.asList(methodElement.getParameters()), - defaultsStorage, - loadTypeMethods - ); - } - // 4: annotationMetadata if (annotationMetadata instanceof AnnotationMetadataHierarchy hierarchy) { annotationMetadata = hierarchy.merge(); } - pushAnnotationMetadata(staticInit, annotationMetadata); + List values = new ArrayList<>( + List.of( + // 1: declaringType + ExpressionDef.constant(TypeDef.erasure(beanType)), + // 2: methodName + ExpressionDef.constant(methodElement.getName()), + // 3: arguments + !methodElement.hasParameters() ? ExpressionDef.nullValue() : ArgumentExpUtils.pushBuildArgumentsForMethod( + this.annotationMetadata, + ClassElement.of(beanFullClassName), + beanDefinitionTypeDef, + Arrays.asList(methodElement.getParameters()), + loadTypeMethods + ), + // 4: annotationMetadata + getAnnotationMetadataExpression(annotationMetadata) + ) + ); if (isPreDestroyMethod || isPostConstructMethod) { // 5: isPostConstructMethod - staticInit.push(isPostConstructMethod); + values.add(ExpressionDef.constant(isPostConstructMethod)); // 6: isPreDestroyMethod - staticInit.push(isPreDestroyMethod); - staticInit.invokeConstructor(Type.getType(AbstractInitializableBeanDefinition.MethodReference.class), METHOD_REFERENCE_CONSTRUCTOR_POST_PRE); + values.add(ExpressionDef.constant(isPreDestroyMethod)); + + return ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodReference.class) + .instantiate( + METHOD_REFERENCE_CONSTRUCTOR_POST_PRE, values + ); } else { - staticInit.invokeConstructor(Type.getType(AbstractInitializableBeanDefinition.MethodReference.class), METHOD_REFERENCE_CONSTRUCTOR); + return ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodReference.class) + .instantiate( + METHOD_REFERENCE_CONSTRUCTOR, values + ); } } - private void pushNewFieldReference(GeneratorAdapter staticInit, Type declaringType, FieldElement fieldElement, AnnotationMetadata annotationMetadata) { - staticInit.newInstance(Type.getType(AbstractInitializableBeanDefinition.FieldReference.class)); - staticInit.dup(); - // 1: declaringType - staticInit.push(declaringType); - // 2: argument - pushCreateArgument( - this.annotationMetadata, - beanFullClassName, - beanDefinitionType, - classWriter, - staticInit, - fieldElement.getName(), - fieldElement.getGenericType(), - annotationMetadata, - fieldElement.getGenericType().getTypeArguments(), - defaultsStorage, - loadTypeMethods - ); - staticInit.invokeConstructor(Type.getType(AbstractInitializableBeanDefinition.FieldReference.class), FIELD_REFERENCE_CONSTRUCTOR); + private ExpressionDef getNewFieldReference(TypedElement declaringType, FieldElement fieldElement, AnnotationMetadata annotationMetadata) { + return ClassTypeDef.of(AbstractInitializableBeanDefinition.FieldReference.class) + .instantiate( + FIELD_REFERENCE_CONSTRUCTOR, + + // 1: declaringType + ExpressionDef.constant(TypeDef.erasure(declaringType)), + // 2: argument + ArgumentExpUtils.pushCreateArgument( + this.annotationMetadata, + ClassElement.of(beanFullClassName), + beanDefinitionTypeDef, + fieldElement.getName(), + fieldElement.getGenericType(), + annotationMetadata, + fieldElement.getGenericType().getTypeArguments(), + loadTypeMethods + ) + ); } - private void pushNewAnnotationReference(GeneratorAdapter staticInit, Type referencedType) { - staticInit.newInstance(Type.getType(AbstractInitializableBeanDefinition.AnnotationReference.class)); - staticInit.dup(); + private ExpressionDef getNewAnnotationReference(TypedElement referencedType) { + return ClassTypeDef.of(AbstractInitializableBeanDefinition.AnnotationReference.class) + .instantiate( + ANNOTATION_REFERENCE_CONSTRUCTOR, - // 1: argument - staticInit.push(referencedType); - invokeInterfaceStaticMethod( - staticInit, - Argument.class, - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(Argument.class, "of", Class.class))); + ClassTypeDef.of(Argument.class) + .invokeStatic( + ARGUMENT_OF_METHOD, - staticInit.invokeConstructor(Type.getType(AbstractInitializableBeanDefinition.AnnotationReference.class), - ANNOTATION_REFERENCE_CONSTRUCTOR); + ExpressionDef.constant(TypeDef.erasure(referencedType)) + ) + ); } - private void pushAnnotationMetadata(GeneratorAdapter staticInit, - AnnotationMetadata annotationMetadata) { + private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotationMetadata) { annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); // // MutableAnnotationMetadata.contributeDefaults( @@ -4664,121 +4405,40 @@ private void pushAnnotationMetadata(GeneratorAdapter staticInit, // ); if (annotationMetadata == AnnotationMetadata.EMPTY_METADATA || annotationMetadata.isEmpty()) { - staticInit.push((String) null); + return ExpressionDef.nullValue(); } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - AnnotationMetadataWriter.instantiateNewMetadataHierarchy( - beanDefinitionType, - classWriter, - staticInit, - annotationMetadataHierarchy, - defaultsStorage, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy( + beanDefinitionTypeDef, + annotationMetadataHierarchy, + loadTypeMethods); } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - AnnotationMetadataWriter.instantiateNewMetadata( - beanDefinitionType, - classWriter, - staticInit, - mutableAnnotationMetadata, - defaultsStorage, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadata( + beanDefinitionTypeDef, + mutableAnnotationMetadata, + loadTypeMethods); } else { throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata.getClass().getName()); } } - private String generateBeanDefSig(Type typeParameter) { - if (beanTypeElement.isPrimitive()) { - if (beanTypeElement.isArray()) { - typeParameter = JavaModelUtils.getTypeReference(beanTypeElement); - } else { - typeParameter = ClassUtils.getPrimitiveType(typeParameter.getClassName()) - .map(ReflectionUtils::getWrapperType) - .map(Type::getType) - .orElseThrow(() -> new IllegalStateException("Not a primitive type: " + beanFullClassName)); - } - } - SignatureVisitor sv = new ArrayAwareSignatureWriter(); - visitSuperTypeParameters(sv, typeParameter); - - // visit BeanFactory interface - for (Class interfaceType : interfaceTypes) { - Type param; - if (ProxyBeanDefinition.class == interfaceType || AdvisedBeanType.class == interfaceType) { - param = getInterceptedType().orElse(typeParameter); - } else { - param = typeParameter; - } - - SignatureVisitor bfi = sv.visitInterface(); - bfi.visitClassType(Type.getInternalName(interfaceType)); - SignatureVisitor iisv = bfi.visitTypeArgument('='); - visitTypeParameter(param, iisv); - bfi.visitEnd(); - } - return sv.toString(); - } - - private void visitSuperTypeParameters(SignatureVisitor sv, Type... typeParameters) { - // visit super class - SignatureVisitor psv = sv.visitSuperclass(); - psv.visitClassType(getSuperTypeInternalType()); - if (superType == TYPE_ABSTRACT_BEAN_DEFINITION_AND_REFERENCE || isSuperFactory) { - for (Type typeParameter : typeParameters) { - - SignatureVisitor ppsv = psv.visitTypeArgument('='); - visitTypeParameter(typeParameter, ppsv); - } - } - - psv.visitEnd(); - } - - private void visitTypeParameter(Type typeParameter, SignatureVisitor ppsv) { - final boolean isArray = typeParameter.getSort() == Type.ARRAY; - boolean isPrimitiveArray = false; - if (isArray) { - for (int i = 0; i < typeParameter.getDimensions(); i++) { - ppsv.visitArrayType(); - } - Type elementType = typeParameter.getElementType(); - while (elementType.getSort() == Type.ARRAY) { - elementType = elementType.getElementType(); - } - if (elementType.getSort() == Type.OBJECT) { - ppsv.visitClassType(elementType.getInternalName()); - } else { - // primitive - ppsv.visitBaseType(elementType.getInternalName().charAt(0)); - isPrimitiveArray = true; - } - } else { - ppsv.visitClassType(typeParameter.getInternalName()); - } - if (isPrimitiveArray && ppsv instanceof ArrayAwareSignatureWriter writer) { - writer.visitEndArray(); - } else { - ppsv.visitEnd(); - } - } - private static Method getBeanLookupMethod(String methodName, boolean requiresGenericType) { if (requiresGenericType) { return ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - methodName, - BeanResolutionContext.class, - BeanContext.class, - int.class, - Argument.class, - Qualifier.class); + AbstractInitializableBeanDefinition.class, + methodName, + BeanResolutionContext.class, + BeanContext.class, + int.class, + Argument.class, + Qualifier.class); } else { return ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - methodName, - BeanResolutionContext.class, - BeanContext.class, - int.class, - Qualifier.class + AbstractInitializableBeanDefinition.class, + methodName, + BeanResolutionContext.class, + BeanContext.class, + int.class, + Qualifier.class ); } } @@ -4786,23 +4446,23 @@ private static Method getBeanLookupMethod(String methodName, boolean requiresGen private static Method getBeanLookupMethodForArgument(String methodName, boolean requiresGenericType) { if (requiresGenericType) { return ReflectionUtils.getRequiredInternalMethod( - AbstractInitializableBeanDefinition.class, - methodName, - BeanResolutionContext.class, - BeanContext.class, - int.class, - int.class, - Argument.class, - Qualifier.class); - } - return ReflectionUtils.getRequiredInternalMethod( AbstractInitializableBeanDefinition.class, methodName, BeanResolutionContext.class, BeanContext.class, int.class, int.class, + Argument.class, Qualifier.class); + } + return ReflectionUtils.getRequiredInternalMethod( + AbstractInitializableBeanDefinition.class, + methodName, + BeanResolutionContext.class, + BeanContext.class, + int.class, + int.class, + Qualifier.class); } @Override @@ -4951,7 +4611,7 @@ public BeanElementBuilder addAssociatedBean(ClassElement type, VisitorContext vi if (visitorContext instanceof BeanElementVisitorContext context) { final Element[] originatingElements = getOriginatingElements(); return context - .addAssociatedBean(originatingElements[0], type); + .addAssociatedBean(originatingElements[0], type); } return BeanElement.super.addAssociatedBean(type, visitorContext); } @@ -4961,6 +4621,11 @@ public Element[] getOriginatingElements() { return this.originatingElements.getOriginatingElements(); } + @Override + public void addOriginatingElement(Element element) { + originatingElements.addOriginatingElement(element); + } + /** * Sets whether this bean is a proxied type. * @@ -5022,10 +4687,10 @@ private static final class FieldVisitData { final boolean requiresReflection; FieldVisitData( - TypedElement beanType, - FieldElement fieldElement, - AnnotationMetadata annotationMetadata, - boolean requiresReflection) { + TypedElement beanType, + FieldElement fieldElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection) { this.beanType = beanType; this.fieldElement = fieldElement; this.annotationMetadata = annotationMetadata; @@ -5055,10 +4720,10 @@ public static final class MethodVisitData { * @param annotationMetadata The annotation metadata */ MethodVisitData( - TypedElement beanType, - MethodElement methodElement, - boolean requiresReflection, - AnnotationMetadata annotationMetadata) { + TypedElement beanType, + MethodElement methodElement, + boolean requiresReflection, + AnnotationMetadata annotationMetadata) { this.beanType = beanType; this.requiresReflection = requiresReflection; this.methodElement = methodElement; @@ -5068,12 +4733,12 @@ public static final class MethodVisitData { } MethodVisitData( - TypedElement beanType, - MethodElement methodElement, - boolean requiresReflection, - AnnotationMetadata annotationMetadata, - boolean postConstruct, - boolean preDestroy) { + TypedElement beanType, + MethodElement methodElement, + boolean requiresReflection, + AnnotationMetadata annotationMetadata, + boolean postConstruct, + boolean preDestroy) { this.beanType = beanType; this.requiresReflection = requiresReflection; this.methodElement = methodElement; @@ -5119,19 +4784,214 @@ public boolean isPreDestroy() { } } + private static final class FactoryBuildMethodDefinition extends BuildMethodDefinition { + private final ClassElement factoryClass; + private final Element factoryElement; + private final ParameterElement[] parameters; + + private FactoryBuildMethodDefinition(ClassElement factoryClass, Element factoryElement, ParameterElement[] parameters) { + this.factoryClass = factoryClass; + this.factoryElement = factoryElement; + this.parameters = parameters; + } + + @Override + public ParameterElement[] getParameters() { + return parameters; + } + } + + private static final class ConstructorBuildMethodDefinition extends BuildMethodDefinition { + private final MethodElement constructor; + private final boolean requiresReflection; + + private ConstructorBuildMethodDefinition(MethodElement constructor, boolean requiresReflection) { + this.constructor = constructor; + this.requiresReflection = requiresReflection; + } + + @Override + ParameterElement[] getParameters() { + return constructor.getParameters(); + } + } + + private abstract static class BuildMethodDefinition { + + private BuildMethodLifecycleDefinition postConstruct; + private BuildMethodLifecycleDefinition preDestroy; + + abstract ParameterElement[] getParameters(); + + void postConstruct(boolean intercepted) { + if (postConstruct == null) { + postConstruct = new BuildMethodLifecycleDefinition(intercepted); + } + } + + void preDestroy(boolean intercepted) { + if (preDestroy == null) { + preDestroy = new BuildMethodLifecycleDefinition(intercepted); + } + } + } + + private static final class BuildMethodLifecycleDefinition { + private final boolean intercepted; + private final List injectionPoints = new ArrayList<>(); + + private BuildMethodLifecycleDefinition(boolean intercepted) { + this.intercepted = intercepted; + } + } + + private record SetterInjectionInjectCommand(TypedElement declaringType, + MethodElement methodElement, + AnnotationMetadata annotationMetadata, + boolean requiresReflection, + boolean isOptional) implements InjectMethodCommand { + + @Override + public boolean hasInjectScope() { + return BeanDefinitionWriter.hasInjectScope(methodElement.getParameters()); + } + + } + + private record InjectMethodInjectCommand(TypedElement declaringType, + MethodElement methodElement, + boolean requiresReflection, + VisitorContext visitorContext, + int methodIndex) implements InjectMethodCommand { + + @Override + public boolean hasInjectScope() { + return BeanDefinitionWriter.hasInjectScope(methodElement.getParameters()); + } + + } + + private record ConfigFieldBuilderInjectCommand(ClassElement type, + String field, + AnnotationMetadata annotationMetadata, + ConfigurationMetadataBuilder metadataBuilder, + boolean isInterface, + ConfigBuilderState configBuilderState, + List builderPoints) implements ConfigBuilderInjectCommand { + + @Override + public boolean hasInjectScope() { + return false; + } + + } + + private record ConfigMethodBuilderInjectPointCommand(ClassElement type, + String methodName, + AnnotationMetadata annotationMetadata, + ConfigurationMetadataBuilder metadataBuilder, + boolean isInterface, + ConfigBuilderState configBuilderState, + List builderPoints) implements ConfigBuilderInjectCommand { + + @Override + public boolean hasInjectScope() { + return false; + } + } + + private record ConfigBuilderMethodDurationInjectCommand(String propertyName, + ClassElement returnType, + String methodName, + String path) implements ConfigBuilderPointInjectCommand { + + } + + private record ConfigBuilderMethodInjectCommand(String propertyName, + ClassElement returnType, + String methodName, + ClassElement paramType, + Map generics, + String path) implements ConfigBuilderPointInjectCommand { + + } + + private interface ConfigBuilderInjectCommand extends InjectMethodCommand { + List builderPoints(); + } + + private interface ConfigBuilderPointInjectCommand { + } + + private record InjectFieldInjectCommand(TypedElement declaringType, + FieldElement fieldElement, + boolean requiresReflection) implements InjectMethodCommand { + + @Override + public boolean hasInjectScope() { + return BeanDefinitionWriter.hasInjectScope(fieldElement); + } + } + + private record InjectFieldValueInjectCommand(TypedElement declaringType, + FieldElement fieldElement, + boolean requiresReflection, + boolean isOptional) implements InjectMethodCommand { + + @Override + public boolean hasInjectScope() { + return BeanDefinitionWriter.hasInjectScope(fieldElement); + } + } + + private interface InjectMethodCommand { + + boolean hasInjectScope(); - private record FactoryMethodDef(Type factoryType, Element factoryMethod, String methodDescriptor, int factoryVar) { } - private static class InnerClassDef { - private final ClassWriter innerClassWriter; - private final String constructorInternalName; - private final Type innerClassType; + private record InjectMethodBuildCommand(TypedElement declaringType, MethodElement methodElement, + boolean requiresReflection, int methodIndex) { + } - public InnerClassDef(ClassWriter innerClassWriter, String constructorInternalName, Type innerClassType) { - this.innerClassWriter = innerClassWriter; - this.constructorInternalName = constructorInternalName; - this.innerClassType = innerClassType; + private record InjectMethodSignature( + VariableDef.This aThis, + List methodParameters, + VariableDef beanResolutionContext, + VariableDef beanContext, + VariableDef instanceVar + ) { + private InjectMethodSignature(VariableDef.This aThis, + List methodParameters, + VariableDef instanceVar) { + this(aThis, methodParameters, methodParameters.get(0), methodParameters.get(1), instanceVar); } } + + private record StaticBlock(@NonNull + StatementDef statement, + @NonNull + FieldDef annotationMetadataField, + @NonNull + FieldDef failedInitializationField, + @NonNull + FieldDef constructorRefField, + @Nullable + FieldDef injectionMethodsField, + @Nullable + FieldDef injectionFieldsField, + @Nullable + FieldDef annotationInjectionsFieldType, + @Nullable + FieldDef typeArgumentsField, + @Nullable + FieldDef executableMethodsField, + @NonNull + FieldDef precalculatedInfoField, + @Nullable + FieldDef preStartConditionsField, + @Nullable + FieldDef postStartConditionsField) { + } + } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/DispatchWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/DispatchWriter.java index 66e1cbf34c2..be230639f06 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/DispatchWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/DispatchWriter.java @@ -15,86 +15,87 @@ */ package io.micronaut.inject.writer; +import io.micronaut.context.AbstractExecutableMethodsDefinition; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; import io.micronaut.core.reflect.ReflectionUtils; -import io.micronaut.core.util.ArrayUtils; +import io.micronaut.core.util.CollectionUtils; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.FieldElement; import io.micronaut.inject.ast.KotlinParameterElement; import io.micronaut.inject.ast.MethodElement; import io.micronaut.inject.ast.ParameterElement; import io.micronaut.inject.ast.TypedElement; -import io.micronaut.inject.processing.JavaModelUtils; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; -import org.objectweb.asm.commons.TableSwitchGenerator; - +import io.micronaut.inject.processing.ProcessingException; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; +import io.micronaut.sourcegen.model.VariableDef; + +import javax.lang.model.element.Modifier; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.IntStream; /** * Switch based dispatch writer. * * @author Denis Stepanov - * @since 3.1 + * @since 4.7 */ @Internal -public final class DispatchWriter extends AbstractClassFileWriter implements Opcodes { - - private static final Method DISPATCH_METHOD = new Method("dispatch", getMethodDescriptor(Object.class, Arrays.asList(int.class, Object.class, Object[].class))); - - private static final Method DISPATCH_ONE_METHOD = new Method("dispatchOne", getMethodDescriptor(Object.class, Arrays.asList(int.class, Object.class, Object.class))); - - private static final Method GET_TARGET_METHOD = new Method("getTargetMethodByIndex", getMethodDescriptor(java.lang.reflect.Method.class, Collections.singletonList(int.class))); - - private static final Method GET_ACCESSIBLE_TARGET_METHOD = new Method("getAccessibleTargetMethodByIndex", getMethodDescriptor(java.lang.reflect.Method.class, Collections.singletonList(int.class))); - - private static final Method UNKNOWN_DISPATCH_AT_INDEX = new Method("unknownDispatchAtIndexException", getMethodDescriptor(RuntimeException.class, Collections.singletonList(int.class))); +public final class DispatchWriter implements ClassOutputWriter { + + private static final Method GET_ACCESSIBLE_TARGET_METHOD = ReflectionUtils.getRequiredInternalMethod( + AbstractExecutableMethodsDefinition.class, + "getAccessibleTargetMethodByIndex", + int.class + ); + + private static final MethodDef UNKNOWN_DISPATCH_AT_INDEX = MethodDef.builder("unknownDispatchAtIndexException") + .addParameter("index", int.class) + .returns(RuntimeException.class) + .build(); + + private static final Method GET_TARGET_METHOD = ReflectionUtils.getRequiredInternalMethod( + AbstractExecutableMethodsDefinition.class, + "getTargetMethodByIndex", + int.class + ); + + private static final Method DISPATCH_METHOD = ReflectionUtils.getRequiredInternalMethod( + AbstractExecutableMethodsDefinition.class, + "dispatch", + int.class, + Object.class, + Object[].class + ); private static final String FIELD_INTERCEPTABLE = "$interceptable"; - private static final Type TYPE_REFLECTION_UTILS = Type.getType(ReflectionUtils.class); + private static final ClassTypeDef TYPE_REFLECTION_UTILS = ClassTypeDef.of(ReflectionUtils.class); - private static final org.objectweb.asm.commons.Method METHOD_GET_REQUIRED_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getRequiredMethod", Class.class, String.class, Class[].class)); + private static final Method METHOD_GET_REQUIRED_METHOD = ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getRequiredMethod", Class.class, String.class, Class[].class); - private static final org.objectweb.asm.commons.Method METHOD_INVOKE_METHOD = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "invokeMethod", Object.class, java.lang.reflect.Method.class, Object[].class)); + private static final Method METHOD_INVOKE_METHOD = ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "invokeMethod", Object.class, java.lang.reflect.Method.class, Object[].class); - private static final org.objectweb.asm.commons.Method METHOD_GET_FIELD_VALUE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getField", Class.class, String.class, Object.class)); + private static final Method METHOD_GET_FIELD_VALUE = + ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getField", Class.class, String.class, Object.class); - private static final org.objectweb.asm.commons.Method METHOD_SET_FIELD_VALUE = org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "setField", Class.class, String.class, Object.class, Object.class)); + private static final Method METHOD_SET_FIELD_VALUE = ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "setField", Class.class, String.class, Object.class, Object.class); private final List dispatchTargets = new ArrayList<>(); - private final Type thisType; - - private final Type dispatchSuperType; private boolean hasInterceptedMethod; - public DispatchWriter(Type thisType) { - this(thisType, ExecutableMethodsDefinitionWriter.SUPER_TYPE); - } - - public DispatchWriter(Type thisType, Type dispatchSuperType) { - super(); - this.thisType = thisType; - this.dispatchSuperType = dispatchSuperType; - } - /** * Adds new set field dispatch target. * @@ -135,7 +136,23 @@ public int addMethod(TypedElement declaringType, MethodElement methodElement) { * @return the target index */ public int addMethod(TypedElement declaringType, MethodElement methodElement, boolean useOneDispatch) { - return addDispatchTarget(new MethodDispatchTarget(dispatchSuperType, declaringType, methodElement, useOneDispatch, !useOneDispatch)); + DispatchTarget dispatchTarget = findDispatchTarget(declaringType, methodElement, useOneDispatch); + return addDispatchTarget(dispatchTarget); + } + + private DispatchTarget findDispatchTarget(TypedElement declaringType, MethodElement methodElement, boolean useOneDispatch) { + List argumentTypes = Arrays.asList(methodElement.getSuspendParameters()); + boolean isKotlinDefault = argumentTypes.stream().anyMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); + ClassElement declaringClassType = (ClassElement) declaringType; + if (methodElement.isReflectionRequired()) { + if (isKotlinDefault) { + throw new ProcessingException(methodElement, "Kotlin default methods are not supported for reflection invocation"); + } + return new MethodReflectionDispatchTarget(declaringType, methodElement, dispatchTargets.size(), useOneDispatch); + } else if (isKotlinDefault) { + return new KotlinMethodWithDefaultsDispatchTarget(declaringClassType, methodElement, useOneDispatch); + } + return new MethodDispatchTarget(declaringClassType, methodElement, useOneDispatch); } /** @@ -153,12 +170,11 @@ public int addInterceptedMethod(TypedElement declaringType, String interceptedProxyBridgeMethodName) { hasInterceptedMethod = true; return addDispatchTarget(new InterceptableMethodDispatchTarget( - dispatchSuperType, - declaringType, - methodElement, - interceptedProxyClassName, - interceptedProxyBridgeMethodName, - thisType) + findDispatchTarget(declaringType, methodElement, false), + declaringType, + methodElement, + interceptedProxyClassName, + interceptedProxyBridgeMethodName) ); } @@ -173,156 +189,148 @@ public int addDispatchTarget(DispatchTarget dispatchTarget) { return dispatchTargets.size() - 1; } - /** - * Build dispatch method if needed. - * - * @param classWriter The classwriter - */ - public void buildDispatchMethod(ClassWriter classWriter) { - int[] cases = dispatchTargets.stream() - .filter(DispatchTarget::supportsDispatchMulti) - .mapToInt(dispatchTargets::indexOf) - .toArray(); - if (cases.length == 0) { - return; - } - GeneratorAdapter dispatchMethod = new GeneratorAdapter(classWriter.visitMethod( - ACC_PROTECTED | Opcodes.ACC_FINAL, - DISPATCH_METHOD.getName(), - DISPATCH_METHOD.getDescriptor(), - null, - null), - ACC_PROTECTED | Opcodes.ACC_FINAL, - DISPATCH_METHOD.getName(), - DISPATCH_METHOD.getDescriptor() - ); - dispatchMethod.loadArg(0); - dispatchMethod.tableSwitch(cases, new TableSwitchGenerator() { - @Override - public void generateCase(int key, Label end) { - DispatchTarget method = dispatchTargets.get(key); - method.writeDispatchMulti(dispatchMethod, key); - dispatchMethod.returnValue(); - } + @Nullable + public MethodDef buildDispatchMethod() { + List> dispatchers = getDispatchers(DispatchTarget::supportsDispatchMulti); + if (dispatchers.isEmpty()) { + return null; + } - @Override - public void generateDefault() { - dispatchMethod.loadThis(); - dispatchMethod.loadArg(0); - dispatchMethod.invokeVirtual(thisType, UNKNOWN_DISPATCH_AT_INDEX); - dispatchMethod.throwException(); - } - }, true); - dispatchMethod.visitMaxs(DEFAULT_MAX_STACK, 1); - dispatchMethod.visitEnd(); - } + return MethodDef.override(DISPATCH_METHOD) + .build((aThis, methodParameters) -> { - /** - * Build dispatch one method if needed. - * - * @param classWriter The classwriter - */ - public void buildDispatchOneMethod(ClassWriter classWriter) { - int[] cases = dispatchTargets.stream() - .filter(DispatchTarget::supportsDispatchOne) - .mapToInt(dispatchTargets::indexOf) - .toArray(); - if (cases.length == 0) { - return; - } - GeneratorAdapter dispatchMethod = new GeneratorAdapter(classWriter.visitMethod( - ACC_PROTECTED | ACC_FINAL, - DISPATCH_ONE_METHOD.getName(), - DISPATCH_ONE_METHOD.getDescriptor(), - null, - null), - ACC_PROTECTED | ACC_FINAL, - DISPATCH_ONE_METHOD.getName(), - DISPATCH_ONE_METHOD.getDescriptor() - ); - dispatchMethod.loadArg(0); - Map stateMap = new HashMap<>(); - dispatchMethod.tableSwitch(cases, new TableSwitchGenerator() { - @Override - public void generateCase(int key, Label end) { - DispatchTarget method = dispatchTargets.get(key); - if (method.writeDispatchOne(dispatchMethod, key, stateMap)) { - dispatchMethod.returnValue(); + VariableDef.MethodParameter methodIndex = methodParameters.get(0); + VariableDef.MethodParameter target = methodParameters.get(1); + VariableDef.MethodParameter argsArray = methodParameters.get(2); + + Map switchCases = CollectionUtils.newHashMap(dispatchers.size()); + + for (Map.Entry e : dispatchers) { + int caseIndex = e.getValue(); + DispatchTarget dispatchTarget = e.getKey(); + StatementDef statementDef = dispatchTarget.dispatch(caseIndex, methodIndex, target, argsArray); + switchCases.put(ExpressionDef.constant(caseIndex), statementDef); } - } - @Override - public void generateDefault() { - dispatchMethod.loadThis(); - dispatchMethod.loadArg(0); - dispatchMethod.invokeVirtual(thisType, UNKNOWN_DISPATCH_AT_INDEX); - dispatchMethod.throwException(); + return StatementDef.multi( + methodParameters.get(0).asStatementSwitch( + TypeDef.OBJECT, + switchCases, + aThis.invoke(UNKNOWN_DISPATCH_AT_INDEX, methodIndex).doThrow() + ), + ExpressionDef.nullValue().returning() + ); + }); + } + + private List> getDispatchers(Predicate predicate) { + List> result = new ArrayList<>(); + int index = 0; + for (DispatchTarget dispatchTarget : dispatchTargets) { + if (predicate.test(dispatchTarget)) { + result.add(Map.entry(dispatchTarget, index)); } - }, true); - for (DispatchTargetState state : stateMap.values()) { - state.complete(dispatchMethod); + index++; } - dispatchMethod.visitMaxs(DEFAULT_MAX_STACK, 1); - dispatchMethod.visitEnd(); + return result; } - /** - * Build get target method by index method if needed. - * - * @param classWriter The classwriter - */ - public void buildGetTargetMethodByIndex(ClassWriter classWriter) { - GeneratorAdapter getTargetMethodByIndex = new GeneratorAdapter(classWriter.visitMethod( - Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL, - GET_TARGET_METHOD.getName(), - GET_TARGET_METHOD.getDescriptor(), - null, - null), - ACC_PROTECTED | Opcodes.ACC_FINAL, - GET_TARGET_METHOD.getName(), - GET_TARGET_METHOD.getDescriptor() - ); - getTargetMethodByIndex.loadArg(0); - int[] cases = dispatchTargets.stream() - .filter(MethodDispatchTarget.class::isInstance) - .mapToInt(dispatchTargets::indexOf) - .toArray(); - getTargetMethodByIndex.tableSwitch(cases, new TableSwitchGenerator() { - @Override - public void generateCase(int key, Label end) { - MethodDispatchTarget method = (MethodDispatchTarget) dispatchTargets.get(key); - TypedElement declaringType = method.declaringType; - Type declaringTypeObject = JavaModelUtils.getTypeReference(declaringType); - MethodElement methodElement = method.methodElement; - pushTypeUtilsGetRequiredMethod(getTargetMethodByIndex, declaringTypeObject, methodElement); - getTargetMethodByIndex.returnValue(); - } + @Nullable + public MethodDef buildDispatchOneMethod() { + List> dispatchers = getDispatchers(DispatchTarget::supportsDispatchOne); + if (dispatchers.isEmpty()) { + return null; + } - @Override - public void generateDefault() { - getTargetMethodByIndex.loadThis(); - getTargetMethodByIndex.loadArg(0); - getTargetMethodByIndex.invokeVirtual(thisType, UNKNOWN_DISPATCH_AT_INDEX); - getTargetMethodByIndex.throwException(); - } - }, true); - getTargetMethodByIndex.visitMaxs(DEFAULT_MAX_STACK, 1); - getTargetMethodByIndex.visitEnd(); + return MethodDef.builder("dispatchOne") + .addModifiers(Modifier.PROTECTED, Modifier.FINAL) + .addParameters(int.class, Object.class, Object.class) + .returns(TypeDef.OBJECT) + .build((aThis, methodParameters) -> { + + VariableDef.MethodParameter methodIndex = methodParameters.get(0); + VariableDef.MethodParameter target = methodParameters.get(1); + VariableDef.MethodParameter value = methodParameters.get(2); + + Map switchCases = CollectionUtils.newHashMap(dispatchers.size()); + for (Map.Entry e : dispatchers) { + int caseIndex = e.getValue(); + DispatchTarget dispatchTarget = e.getKey(); + StatementDef statementDef = dispatchTarget.dispatchOne(caseIndex, methodIndex, target, value); + switchCases.put(ExpressionDef.constant(caseIndex), statementDef); + } + + return StatementDef.multi( + methodParameters.get(0).asStatementSwitch( + TypeDef.OBJECT, + switchCases, + aThis.invoke(UNKNOWN_DISPATCH_AT_INDEX, methodIndex).doThrow() + ), + ExpressionDef.nullValue().returning() + ); + }); } - public static void pushTypeUtilsGetRequiredMethod(GeneratorAdapter builder, Type declaringTypeObject, MethodElement methodElement) { - List argumentTypes = Arrays.asList(methodElement.getSuspendParameters()); + @Nullable + public MethodDef buildGetTargetMethodByIndex() { + // Should we include methods that don't require reflection??? + List> dispatchers = getDispatchers(dispatchTarget -> dispatchTarget.getMethodElement() != null); + if (dispatchers.isEmpty()) { + return null; + } + + return MethodDef.override(GET_TARGET_METHOD) + .build((aThis, methodParameters) -> { + + VariableDef.MethodParameter methodIndex = methodParameters.get(0); + + Map switchCases = CollectionUtils.newHashMap(dispatchers.size()); + + for (Map.Entry dispatcher : dispatchers) { + int caseIndex = dispatcher.getValue(); + DispatchTarget dispatchTarget = dispatcher.getKey(); + MethodElement methodElement = dispatchTarget.getMethodElement(); + + StatementDef statement = TYPE_REFLECTION_UTILS.invokeStatic(METHOD_GET_REQUIRED_METHOD, + + ExpressionDef.constant(ClassTypeDef.of(methodElement.getDeclaringType())), + ExpressionDef.constant(methodElement.getName()), + TypeDef.CLASS.array().instantiate( + Arrays.stream(methodElement.getSuspendParameters()) + .map(p -> ExpressionDef.constant(TypeDef.erasure(p.getType()))) + .toList() + ) + ).returning(); + switchCases.put(ExpressionDef.constant(caseIndex), statement); + } - builder.push(declaringTypeObject); - builder.push(methodElement.getName()); - if (!argumentTypes.isEmpty()) { - pushNewArray(builder, Class.class, argumentTypes, parameterElement -> { - builder.push(JavaModelUtils.getTypeReference(parameterElement)); + return StatementDef.multi( + methodParameters.get(0).asStatementSwitch( + TypeDef.OBJECT, + switchCases, + aThis.invoke(UNKNOWN_DISPATCH_AT_INDEX, methodIndex).doThrow() + ), + ExpressionDef.nullValue().returning() + ); }); + } + + public static ExpressionDef getTypeUtilsGetRequiredMethod(ClassTypeDef declaringType, MethodElement methodElement) { + List values = new ArrayList<>(); + values.add(ExpressionDef.constant(declaringType)); + values.add(ExpressionDef.constant(methodElement.getName())); + if (methodElement.getSuspendParameters().length > 0) { + values.add(TypeDef.CLASS.array().instantiate( + Arrays.stream(methodElement.getSuspendParameters()) + .map(parameterElement -> ExpressionDef.constant(TypeDef.erasure(parameterElement.getType()))) + .toList() + )); } else { - builder.getStatic(TYPE_REFLECTION_UTILS, "EMPTY_CLASS_ARRAY", Type.getType(Class[].class)); + values.add( + TYPE_REFLECTION_UTILS.getStaticField("EMPTY_CLASS_ARRAY", TypeDef.of(Class[].class)) + ); } - builder.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_GET_REQUIRED_METHOD); + return TYPE_REFLECTION_UTILS.invokeStatic(METHOD_GET_REQUIRED_METHOD, values); } @Override @@ -353,72 +361,130 @@ public interface DispatchTarget { /** * @return true if writer supports dispatch one. */ - default boolean supportsDispatchOne() { - return false; + boolean supportsDispatchOne(); + + /** + * @return true if writer supports dispatch multi. + */ + boolean supportsDispatchMulti(); + + default StatementDef dispatch(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef valuesArray) { + return dispatch(target, valuesArray); + } + + default StatementDef dispatchOne(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { + throw new IllegalStateException("Not supported"); } + StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray); + + MethodElement getMethodElement(); + + TypedElement getDeclaringType(); + + } + + /** + * Dispatch target implementation writer. + */ + @Internal + public abstract static class AbstractDispatchTarget implements DispatchTarget { + /** - * Generate {@code dispatchOne} with shared state. + * Implement dispatch. * - * @param writer The method writer - * @param methodIndex The method index - * @param stateMap State map shared for this {@code dispatchOne} method, may be written to - * @return {@code true} iff the return value is on the top of the stack, {@code false} iff - * we branched instead + * @param target The target + * @param valuesArray The values array + * @return The dispatch statement */ - default boolean writeDispatchOne(GeneratorAdapter writer, int methodIndex, Map stateMap) { - writeDispatchOne(writer, methodIndex); - return true; + @Override + public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) { + ExpressionDef expression = dispatchMultiExpression(target, valuesArray); + return expressionReturning(expression); } /** - * Generate dispatch one. - * @param methodIndex The method index + * Implement dispatch one. * - * @param writer The writer + * @param caseValue The case value + * @param caseExpression The case expression + * @param target The target + * @param value The value + * @return The dispatch statement */ - default void writeDispatchOne(GeneratorAdapter writer, int methodIndex) { - throw new IllegalStateException("Not supported"); + @Override + public StatementDef dispatchOne(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { + ExpressionDef expression = dispatchOneExpression(target, value); + return expressionReturning(expression); + } + + private StatementDef expressionReturning(ExpressionDef expression) { + MethodElement methodElement = getMethodElement(); + if (methodElement != null && methodElement.getReturnType().isVoid() && !methodElement.isSuspend()) { + return StatementDef.multi( + (StatementDef) expression, + ExpressionDef.nullValue().returning() + ); + } + return expression.returning(); } /** - * @return true if writer supports dispatch multi. + * Implements multi dispatch. + * + * @param target The target + * @param valuesArray The values + * @return THe expression */ - default boolean supportsDispatchMulti() { - return false; + protected ExpressionDef dispatchMultiExpression(ExpressionDef target, ExpressionDef valuesArray) { + MethodElement methodElement = getMethodElement(); + if (methodElement == null) { + return dispatchMultiExpression(target, List.of(valuesArray.arrayElement(0))); + } + return dispatchMultiExpression(target, + IntStream.range(0, methodElement.getSuspendParameters().length).mapToObj(valuesArray::arrayElement).toList() + ); } /** - * Generate dispatch multi. + * Implements multi dispatch. * - * @param writer The writer - * @param methodIndex The method index + * @param target The target + * @param values The values + * @return The dispatch expression */ - default void writeDispatchMulti(GeneratorAdapter writer, int methodIndex) { - throw new IllegalStateException("Not supported"); + protected ExpressionDef dispatchMultiExpression(ExpressionDef target, List values) { + return dispatchOneExpression(target, values.get(0)); } - } + /** + * Implements one dispatch. + * + * @param target The target + * @param value The value + * @return The dispatch expression + */ + protected ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef value) { + return dispatchExpression(target); + } - /** - * State carried between different {@link DispatchTarget}s. This allows for code size reduction - * by sharing bytecode in the same method. - */ - @Internal - public interface DispatchTargetState { /** - * Complete writing this state. + * Implements dispatch. * - * @param writer The method writer + * @param target The target + * @return The dispatch expression */ - void complete(GeneratorAdapter writer); + protected ExpressionDef dispatchExpression(ExpressionDef target) { + throw new IllegalStateException("Not supported"); + } + } /** * Field get dispatch target. */ @Internal - public static final class FieldGetDispatchTarget implements DispatchTarget { + public static final class FieldGetDispatchTarget extends AbstractDispatchTarget { @NonNull final FieldElement beanField; @@ -437,31 +503,30 @@ public boolean supportsDispatchMulti() { } @Override - public void writeDispatchOne(GeneratorAdapter writer, int fieldIndex) { - final Type propertyType = JavaModelUtils.getTypeReference(beanField.getType()); - final Type beanType = JavaModelUtils.getTypeReference(beanField.getOwningType()); + public MethodElement getMethodElement() { + return null; + } + + @Override + public TypedElement getDeclaringType() { + return null; + } + + @Override + public ExpressionDef dispatchExpression(ExpressionDef bean) { + final TypeDef propertyType = TypeDef.of(beanField.getType()); + final ClassTypeDef targetType = ClassTypeDef.of(beanField.getOwningType()); if (beanField.isReflectionRequired()) { - writer.push(beanType); // Bean class - writer.push(beanField.getName()); // Field name - writer.loadArg(1); // Bean instance - writer.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_GET_FIELD_VALUE); - if (beanField.isPrimitive()) { - pushCastToType(writer, propertyType); - } + return TYPE_REFLECTION_UTILS.invokeStatic( + METHOD_GET_FIELD_VALUE, + ExpressionDef.constant(targetType), // Target class + ExpressionDef.constant(beanField.getName()), // Field name, + bean // Target instance + ).cast(propertyType); } else { - // load this - writer.loadArg(1); - pushCastToType(writer, beanType); - - // get field value - writer.getField( - JavaModelUtils.getTypeReference(beanField.getOwningType()), - beanField.getName(), - propertyType); + return bean.cast(targetType).field(beanField).cast(propertyType); } - - pushBoxPrimitiveIfNecessary(propertyType, writer); } @NonNull @@ -474,7 +539,7 @@ public FieldElement getField() { * Field set dispatch target. */ @Internal - public static final class FieldSetDispatchTarget implements DispatchTarget { + public static final class FieldSetDispatchTarget extends AbstractDispatchTarget { @NonNull final FieldElement beanField; @@ -493,34 +558,32 @@ public boolean supportsDispatchMulti() { } @Override - public void writeDispatchOne(GeneratorAdapter writer, int fieldIndex) { - final Type propertyType = JavaModelUtils.getTypeReference(beanField.getType()); - final Type beanType = JavaModelUtils.getTypeReference(beanField.getOwningType()); + public MethodElement getMethodElement() { + return null; + } + + @Override + public TypedElement getDeclaringType() { + return null; + } + @Override + public StatementDef dispatchOne(int caseValue, ExpressionDef caseExpression, ExpressionDef target, ExpressionDef value) { + final TypeDef propertyType = TypeDef.of(beanField.getType()); + final ClassTypeDef targetType = ClassTypeDef.of(beanField.getOwningType()); if (beanField.isReflectionRequired()) { - writer.push(beanType); // Bean class - writer.push(beanField.getName()); // Field name - writer.loadArg(1); // Bean instance - writer.loadArg(2); // Field value - writer.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_SET_FIELD_VALUE); + return TYPE_REFLECTION_UTILS.invokeStatic(METHOD_SET_FIELD_VALUE, + ExpressionDef.constant(targetType), // Target class + ExpressionDef.constant(beanField.getName()), // Field name + target, // Target instance + value // Field value + ).after(ExpressionDef.nullValue().returning()); } else { - // load this - writer.loadArg(1); - pushCastToType(writer, beanType); - - // load value - writer.loadArg(2); - pushCastToType(writer, propertyType); - - // get field value - writer.putField( - beanType, - beanField.getName(), - propertyType); - + return target.cast(targetType) + .field(beanField) + .put(value.cast(propertyType)) + .after(ExpressionDef.nullValue().returning()); } - // push null return type - writer.push((String) null); } @NonNull @@ -533,147 +596,169 @@ public FieldElement getField() { * Method invocation dispatch target. */ @Internal - @SuppressWarnings("FinalClass") - public static class MethodDispatchTarget implements DispatchTarget { - final Type dispatchSuperType; - final TypedElement declaringType; + public static final class MethodDispatchTarget extends AbstractDispatchTarget { + final ClassElement declaringType; final MethodElement methodElement; - final boolean oneDispatch; - final boolean multiDispatch; + private final boolean useOneDispatch; - private MethodDispatchTarget(Type dispatchSuperType, - TypedElement declaringType, + private MethodDispatchTarget(ClassElement targetType, MethodElement methodElement, - boolean oneDispatch, - boolean multiDispatch) { - this.dispatchSuperType = dispatchSuperType; - this.declaringType = declaringType; + boolean useOneDispatch) { + this.declaringType = targetType; this.methodElement = methodElement; - this.oneDispatch = oneDispatch; - this.multiDispatch = multiDispatch; + this.useOneDispatch = useOneDispatch; } + @Override + public boolean supportsDispatchOne() { + return useOneDispatch; + } + + @Override + public boolean supportsDispatchMulti() { + return !useOneDispatch; + } + + @Override + public ClassElement getDeclaringType() { + return declaringType; + } + + @Override public MethodElement getMethodElement() { return methodElement; } + @Override + public ExpressionDef dispatchMultiExpression(ExpressionDef target, List values) { + ClassTypeDef targetType = ClassTypeDef.of(declaringType); + if (methodElement.isStatic()) { + return targetType.invokeStatic(methodElement, values); + } + return target.cast(targetType).invoke(methodElement, values); + } + + @Override + public ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef value) { + ClassTypeDef targetType = ClassTypeDef.of(declaringType); + if (methodElement.isStatic()) { + return targetType.invokeStatic(methodElement, TypeDef.OBJECT.array().instantiate(value)); + } + if (methodElement.getSuspendParameters().length > 0) { + return target.cast(targetType).invoke(methodElement, value); + } + return target.cast(targetType).invoke(methodElement); + } + } + + /** + * Method invocation dispatch target. + */ + @Internal + public static final class KotlinMethodWithDefaultsDispatchTarget extends AbstractDispatchTarget { + final ClassElement declaringType; + final MethodElement methodElement; + private final boolean useOneDispatch; + + private KotlinMethodWithDefaultsDispatchTarget(ClassElement targetType, + MethodElement methodElement, + boolean useOneDispatch) { + this.declaringType = targetType; + this.methodElement = methodElement; + this.useOneDispatch = useOneDispatch; + } + @Override public boolean supportsDispatchOne() { - return oneDispatch; + return useOneDispatch; } @Override public boolean supportsDispatchMulti() { - return multiDispatch; + return !useOneDispatch; } @Override - public void writeDispatchMulti(GeneratorAdapter writer, int methodIndex) { - writeDispatch(writer, methodIndex, true); + public ClassElement getDeclaringType() { + return declaringType; } @Override - public void writeDispatchOne(GeneratorAdapter writer, int methodIndex) { - writeDispatch(writer, methodIndex, false); + public MethodElement getMethodElement() { + return methodElement; } - private void writeDispatch(GeneratorAdapter writer, int methodIndex, boolean isMulti) { - String methodName = methodElement.getName(); + @Override + public ExpressionDef dispatchMultiExpression(ExpressionDef target, List values) { + return MethodGenUtils.invokeKotlinDefaultMethod(declaringType, methodElement, target, values); + } - List argumentTypes = Arrays.asList(methodElement.getSuspendParameters()); - Type declaringTypeObject = JavaModelUtils.getTypeReference(declaringType); - boolean isKotlinDefault = argumentTypes.stream().anyMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); + @Override + public ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef value) { + return MethodGenUtils.invokeKotlinDefaultMethod(declaringType, methodElement, target, List.of(value)); + } + } - final boolean reflectionRequired = methodElement.isReflectionRequired(); - ClassElement returnType = methodElement.isSuspend() ? ClassElement.of(Object.class) : methodElement.getReturnType(); - boolean isInterface = declaringType.getType().isInterface(); - Type returnTypeObject = JavaModelUtils.getTypeReference(returnType); - boolean hasArgs = !argumentTypes.isEmpty(); + /** + * Method invocation dispatch target. + */ + @Internal + public static final class MethodReflectionDispatchTarget extends AbstractDispatchTarget { + private final TypedElement declaringType; + private final MethodElement methodElement; + private final int methodIndex; + private final boolean useOneDispatch; + + private MethodReflectionDispatchTarget(TypedElement declaringType, + MethodElement methodElement, + int methodIndex, + boolean useOneDispatch) { + this.declaringType = declaringType; + this.methodElement = methodElement; + this.methodIndex = methodIndex; + this.useOneDispatch = useOneDispatch; + } - // load this - boolean isStaticMethodInvocation = methodElement.isStatic() || isKotlinDefault; + @Override + public boolean supportsDispatchOne() { + return useOneDispatch; + } - if (!isStaticMethodInvocation) { - writer.loadArg(1); - } + @Override + public boolean supportsDispatchMulti() { + return !useOneDispatch; + } - if (reflectionRequired) { - if (isStaticMethodInvocation) { - writer.push((String) null); - } - writer.loadThis(); - writer.push(methodIndex); - writer.invokeVirtual(dispatchSuperType, GET_ACCESSIBLE_TARGET_METHOD); - if (hasArgs) { - if (isMulti) { - writer.loadArg(2); - } else { - writer.push(1); - writer.newArray(Type.getType(Object.class)); // new Object[1] - writer.dup(); // one ref to store and one to return - writer.push(0); - writer.loadArg(2); - writer.visitInsn(AASTORE); // objects[0] = argumentAtIndex2 - } - } else { - writer.getStatic(Type.getType(ArrayUtils.class), "EMPTY_OBJECT_ARRAY", Type.getType(Object[].class)); - } - writer.invokeStatic(TYPE_REFLECTION_UTILS, METHOD_INVOKE_METHOD); - } else { - if (!isStaticMethodInvocation) { - pushCastToType(writer, declaringTypeObject); - } - int[] defaultsMasksLocal = null; - if (hasArgs) { - if (isKotlinDefault) { - writer.loadArg(1); // First parameter is the current instance - pushCastToType(writer, declaringTypeObject); - defaultsMasksLocal = WriterUtils.computeKotlinDefaultsMask(writer, (paramIndex, parameterElement) -> { - writer.loadArg(2); - writer.push(paramIndex); - writer.visitInsn(AALOAD); - }, null, argumentTypes); - } - if (isMulti) { - int argCount = argumentTypes.size(); - Iterator argIterator = argumentTypes.iterator(); - for (int i = 0; i < argCount; i++) { - writer.loadArg(2); - writer.push(i); - writer.visitInsn(AALOAD); - // cast the argument value to the correct type - pushCastToType(writer, argIterator.next()); - } - } else { - writer.loadArg(2); - // cast the argument value to the correct type - pushCastToType(writer, argumentTypes.iterator().next()); - } - } - Method method = new Method(methodName, getMethodDescriptor(returnType, argumentTypes)); - if (isKotlinDefault) { - method = WriterUtils.asDefaultKotlinMethod(method, declaringTypeObject, defaultsMasksLocal.length); - for (int defaultsMaskLocal : defaultsMasksLocal) { - writer.loadLocal(defaultsMaskLocal, Type.INT_TYPE); // Bit mask of defaults - } - writer.push((String) null); // Last parameter is just a marker and is always null - writer.invokeStatic(declaringTypeObject, method); - } else { - if (isStaticMethodInvocation) { - writer.invokeStatic(declaringTypeObject, method); - } else { - writer.visitMethodInsn(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL, - declaringTypeObject.getInternalName(), method.getName(), - method.getDescriptor(), isInterface); - } - } - } + @Override + public TypedElement getDeclaringType() { + return declaringType; + } - if (returnTypeObject.equals(Type.VOID_TYPE)) { - writer.push((String) null); - } else if (!reflectionRequired) { - pushBoxPrimitiveIfNecessary(returnType, writer); - } + @Override + public MethodElement getMethodElement() { + return methodElement; + } + + @Override + public ExpressionDef dispatchMultiExpression(ExpressionDef target, ExpressionDef valuesArray) { + return TYPE_REFLECTION_UTILS.invokeStatic( + METHOD_INVOKE_METHOD, + + methodElement.isStatic() ? ExpressionDef.nullValue() : target, + new VariableDef.This().invoke(GET_ACCESSIBLE_TARGET_METHOD, ExpressionDef.constant(methodIndex)), + valuesArray + ); + } + + @Override + public ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef value) { + return TYPE_REFLECTION_UTILS.invokeStatic( + METHOD_INVOKE_METHOD, + + methodElement.isStatic() ? ExpressionDef.nullValue() : target, + new VariableDef.This().invoke(GET_ACCESSIBLE_TARGET_METHOD, ExpressionDef.constant(methodIndex)), + methodElement.getSuspendParameters().length > 0 ? TypeDef.OBJECT.array().instantiate(value) : TypeDef.OBJECT.array().instantiate() + ); } } @@ -682,82 +767,74 @@ private void writeDispatch(GeneratorAdapter writer, int methodIndex, boolean isM * Interceptable method invocation dispatch target. */ @Internal - public static final class InterceptableMethodDispatchTarget extends MethodDispatchTarget { - final String interceptedProxyClassName; - final String interceptedProxyBridgeMethodName; - final Type thisType; - - private InterceptableMethodDispatchTarget(Type dispatchSuperType, + public static final class InterceptableMethodDispatchTarget extends AbstractDispatchTarget { + private final TypedElement declaringType; + private final DispatchTarget dispatchTarget; + private final String interceptedProxyClassName; + private final String interceptedProxyBridgeMethodName; + private final MethodElement methodElement; + + private InterceptableMethodDispatchTarget(DispatchTarget dispatchTarget, TypedElement declaringType, MethodElement methodElement, String interceptedProxyClassName, - String interceptedProxyBridgeMethodName, - Type thisType) { - super(dispatchSuperType, declaringType, methodElement, false, true); + String interceptedProxyBridgeMethodName) { + this.declaringType = declaringType; + this.methodElement = methodElement; + this.dispatchTarget = dispatchTarget; this.interceptedProxyClassName = interceptedProxyClassName; this.interceptedProxyBridgeMethodName = interceptedProxyBridgeMethodName; - this.thisType = thisType; } @Override - public void writeDispatchMulti(GeneratorAdapter writer, int methodIndex) { - List argumentTypes = Arrays.asList(methodElement.getSuspendParameters()); - ClassElement returnType = methodElement.isSuspend() ? ClassElement.of(Object.class) : methodElement.getReturnType(); - Type returnTypeObject = JavaModelUtils.getTypeReference(returnType); - - // load this - writer.loadArg(1); - // duplicate target - writer.dup(); - - String methodDescriptor = getMethodDescriptor(returnType, argumentTypes); - Label invokeTargetBlock = new Label(); - - Type interceptedProxyType = getObjectType(interceptedProxyClassName); + public boolean supportsDispatchOne() { + return false; + } - // load this.$interceptable field value - writer.loadThis(); - writer.getField(thisType, FIELD_INTERCEPTABLE, Type.getType(boolean.class)); - // check if it equals true - writer.push(true); - writer.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock); + @Override + public boolean supportsDispatchMulti() { + return true; + } - // target instanceOf intercepted proxy - writer.loadArg(1); - writer.instanceOf(interceptedProxyType); - // check if instanceOf - writer.push(true); - writer.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, invokeTargetBlock); + @Override + public TypedElement getDeclaringType() { + return declaringType; + } - pushCastToType(writer, interceptedProxyType); + @Override + public MethodElement getMethodElement() { + return methodElement; + } - // load arguments - Iterator iterator = argumentTypes.iterator(); - for (int i = 0; i < argumentTypes.size(); i++) { - writer.loadArg(2); - writer.push(i); - writer.visitInsn(AALOAD); + @Override + public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) { + VariableDef.Field interceptableField = new VariableDef.This() + .field(FIELD_INTERCEPTABLE, TypeDef.of(boolean.class)); - pushCastToType(writer, iterator.next()); - } + ClassTypeDef proxyType = ClassTypeDef.of(interceptedProxyClassName); - writer.visitMethodInsn(INVOKEVIRTUAL, - interceptedProxyType.getInternalName(), interceptedProxyBridgeMethodName, - methodDescriptor, false); + return interceptableField.isTrue().and(target.instanceOf(proxyType)) + .doIfElse( + invokeProxyBridge(proxyType, target, valuesArray), + dispatchTarget.dispatch(target, valuesArray) + ); + } - if (returnTypeObject.equals(Type.VOID_TYPE)) { - writer.visitInsn(ACONST_NULL); - } else { - pushBoxPrimitiveIfNecessary(returnType, writer); + private StatementDef invokeProxyBridge(ClassTypeDef proxyType, ExpressionDef target, ExpressionDef valuesArray) { + boolean suspend = methodElement.isSuspend(); + ExpressionDef.InvokeInstanceMethod invoke = target.cast(proxyType).invoke( + interceptedProxyBridgeMethodName, + Arrays.stream(methodElement.getSuspendParameters()).map(p -> TypeDef.of(p.getType())).toList(), + suspend ? TypeDef.OBJECT : TypeDef.of(methodElement.getReturnType()), + IntStream.range(0, methodElement.getSuspendParameters().length).mapToObj(valuesArray::arrayElement).toList() + ); + if (dispatchTarget.getMethodElement().getReturnType().isVoid() && !suspend) { + return StatementDef.multi( + invoke, + ExpressionDef.nullValue().returning() + ); } - writer.returnValue(); - - writer.visitLabel(invokeTargetBlock); - - // remove parent - writer.pop(); - - super.writeDispatchMulti(writer, methodIndex); + return invoke.returning(); } } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/EvaluatedExpressionProcessor.java b/core-processor/src/main/java/io/micronaut/inject/writer/EvaluatedExpressionProcessor.java index c12428126e7..5c362263b6e 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/EvaluatedExpressionProcessor.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/EvaluatedExpressionProcessor.java @@ -33,20 +33,18 @@ import io.micronaut.inject.ast.MethodElement; import io.micronaut.inject.ast.ParameterElement; import io.micronaut.inject.visitor.VisitorContext; +import io.micronaut.sourcegen.model.AnnotationDef; +import io.micronaut.sourcegen.model.ClassDef; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; /** * Internal utility class for writing annotation metadata with evaluated expressions. */ @Internal public final class EvaluatedExpressionProcessor { - protected static final Type TYPE_BUILD_TIME_INIT = Type.getType(BuildTimeInit.class); private final Collection evaluatedExpressions = new ArrayList<>(2); private final DefaultExpressionCompilationContextFactory expressionCompilationContextFactory; private final VisitorContext visitorContext; @@ -136,17 +134,15 @@ public boolean hasEvaluatedExpressions() { return !this.evaluatedExpressions.isEmpty(); } - public void registerExpressionForBuildTimeInit(ClassWriter classWriter) { + public void registerExpressionForBuildTimeInit(ClassDef.ClassDefBuilder classDefBuilder) { String[] expressionClassNames = getEvaluatedExpressions() .stream().map(ExpressionWithContext::expressionClassName).toArray(String[]::new); if (ArrayUtils.isNotEmpty(expressionClassNames)) { - AnnotationVisitor annotationVisitor = classWriter.visitAnnotation(TYPE_BUILD_TIME_INIT.getDescriptor(), true); - AnnotationVisitor av = annotationVisitor.visitArray("value"); - for (String expressionClassName : expressionClassNames) { - av.visit("ignored", expressionClassName); - } - av.visitEnd(); - annotationVisitor.visitEnd(); + classDefBuilder.addAnnotation( + AnnotationDef.builder(BuildTimeInit.class) + .addMember("value", expressionClassNames) + .build() + ); } } } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java index b59336b95c9..dddc259f7aa 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java @@ -17,38 +17,44 @@ import io.micronaut.context.AbstractExecutableMethodsDefinition; import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.annotation.Internal; import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.type.Argument; +import io.micronaut.inject.ExecutableMethod; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; import io.micronaut.inject.annotation.AnnotationMetadataReference; -import io.micronaut.inject.annotation.AnnotationMetadataWriter; import io.micronaut.inject.annotation.MutableAnnotationMetadata; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.MethodElement; -import io.micronaut.inject.ast.ParameterElement; import io.micronaut.inject.ast.TypedElement; -import io.micronaut.inject.processing.JavaModelUtils; -import io.micronaut.inject.visitor.VisitorContext; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; -import org.objectweb.asm.commons.TableSwitchGenerator; - +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; +import io.micronaut.sourcegen.model.TypeDef; + +import javax.lang.model.element.Modifier; import java.io.IOException; import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Writes out a {@link io.micronaut.inject.ExecutableMethodsDefinition} class. @@ -57,29 +63,28 @@ * @since 3.0 */ @Internal -public class ExecutableMethodsDefinitionWriter extends AbstractClassFileWriter implements Opcodes { +public class ExecutableMethodsDefinitionWriter implements ClassOutputWriter { public static final String CLASS_SUFFIX = "$Exec"; - public static final Method GET_EXECUTABLE_AT_INDEX_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "getExecutableMethodByIndex", int.class) - ); - - public static final Type SUPER_TYPE = Type.getType(AbstractExecutableMethodsDefinition.class); + public static final Method GET_EXECUTABLE_AT_INDEX_METHOD = ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "getExecutableMethodByIndex", int.class); - private static final Method SUPER_CONSTRUCTOR = Method.getMethod(ReflectionUtils.getRequiredInternalConstructor( - AbstractExecutableMethodsDefinition.class, - AbstractExecutableMethodsDefinition.MethodReference[].class) - ); + private static final Constructor METHOD_REFERENCE_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractExecutableMethodsDefinition.MethodReference.class, + Class.class, + AnnotationMetadata.class, + String.class, + Argument.class, + Argument[].class, + boolean.class, + boolean.class); - private static final Method WITH_INTERCEPTED_CONSTRUCTOR = new Method(CONSTRUCTOR_NAME, getConstructorDescriptor(boolean.class)); + private static final Constructor SUPER_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor( + AbstractExecutableMethodsDefinition.class, + AbstractExecutableMethodsDefinition.MethodReference[].class); - private static final Method GET_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "getMethod", String.class, Class[].class) - ); + private static final Method GET_METHOD = ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "getMethod", String.class, Class[].class); - private static final Method AT_INDEX_MATCHED_METHOD = Method.getMethod( - ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "methodAtIndexMatches", int.class, String.class, Class[].class) - ); + private static final Method AT_INDEX_MATCHED_METHOD = ReflectionUtils.getRequiredInternalMethod(AbstractExecutableMethodsDefinition.class, "methodAtIndexMatches", int.class, String.class, Class[].class); private static final String FIELD_METHODS_REFERENCES = "$METHODS_REFERENCES"; private static final String FIELD_INTERCEPTABLE = "$interceptable"; @@ -87,11 +92,9 @@ public class ExecutableMethodsDefinitionWriter extends AbstractClassFileWriter i private static final int MIN_METHODS_TO_GENERATE_GET_METHOD = 5; private final String className; - private final String internalName; - private final Type thisType; + private final ClassTypeDef thisType; private final String beanDefinitionReferenceClassName; - private final Map loadTypeMethods = new LinkedHashMap<>(); private final List addedMethods = new ArrayList<>(); private final DispatchWriter methodDispatchWriter; @@ -99,22 +102,22 @@ public class ExecutableMethodsDefinitionWriter extends AbstractClassFileWriter i private final Set methodNames = new HashSet<>(); private final AnnotationMetadata annotationMetadataWithDefaults; private final EvaluatedExpressionProcessor evaluatedExpressionProcessor; - private ClassWriter classWriter; + private ClassDef.ClassDefBuilder classDefBuilder; + + private final OriginatingElements originatingElements; - public ExecutableMethodsDefinitionWriter(VisitorContext visitorContext, - EvaluatedExpressionProcessor evaluatedExpressionProcessor, + public ExecutableMethodsDefinitionWriter(EvaluatedExpressionProcessor evaluatedExpressionProcessor, AnnotationMetadata annotationMetadataWithDefaults, String beanDefinitionClassName, String beanDefinitionReferenceClassName, OriginatingElements originatingElements) { - super(originatingElements); + this.originatingElements = originatingElements; this.annotationMetadataWithDefaults = annotationMetadataWithDefaults; this.evaluatedExpressionProcessor = evaluatedExpressionProcessor; this.className = beanDefinitionClassName + CLASS_SUFFIX; - this.internalName = getInternalName(className); - this.thisType = Type.getObjectType(internalName); + this.thisType = ClassTypeDef.of(className); this.beanDefinitionReferenceClassName = beanDefinitionReferenceClassName; - this.methodDispatchWriter = new DispatchWriter(thisType); + this.methodDispatchWriter = new DispatchWriter(); } /** @@ -127,12 +130,12 @@ public String getClassName() { /** * @return The generated class type. */ - public Type getClassType() { - return thisType; + public ClassTypeDef getClassTypeDef() { + return ClassTypeDef.of(className); } private MethodElement getMethodElement(int index) { - return ((DispatchWriter.MethodDispatchTarget) methodDispatchWriter.getDispatchTargets().get(index)).methodElement; + return methodDispatchWriter.getDispatchTargets().get(index).getMethodElement(); } /** @@ -202,11 +205,11 @@ public int visitExecutableMethod(TypedElement declaringType, evaluatedExpressionProcessor.processEvaluatedExpressions(methodElement); String methodKey = methodElement.getName() + - "(" + - Arrays.stream(methodElement.getSuspendParameters()) - .map(p -> toTypeString(p.getType())) - .collect(Collectors.joining(",")) + - ")"; + "(" + + Arrays.stream(methodElement.getSuspendParameters()) + .map(p -> toTypeString(p.getType())) + .collect(Collectors.joining(",")) + + ")"; int index = addedMethods.indexOf(methodKey); if (index > -1) { @@ -222,8 +225,8 @@ public int visitExecutableMethod(TypedElement declaringType, @Override public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException { - try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, getOriginatingElements())) { - outputStream.write(classWriter.toByteArray()); + try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, originatingElements.getOriginatingElements())) { + outputStream.write(new ByteCodeWriter().write(classDefBuilder.build())); } } @@ -231,188 +234,146 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE * Invoke to build the class model. */ public final void visitDefinitionEnd() { - classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - classWriter.visit(V17, ACC_SYNTHETIC | ACC_FINAL, - internalName, - null, - SUPER_TYPE.getInternalName(), - null); - classWriter.visitAnnotation(TYPE_GENERATED.getDescriptor(), false); - - Type methodsFieldType = Type.getType(AbstractExecutableMethodsDefinition.MethodReference[].class); - - buildStaticInit(classWriter, methodsFieldType); - buildConstructor(classWriter, methodsFieldType); - methodDispatchWriter.buildDispatchMethod(classWriter); - methodDispatchWriter.buildGetTargetMethodByIndex(classWriter); - if (methodDispatchWriter.getDispatchTargets().size() > MIN_METHODS_TO_GENERATE_GET_METHOD) { - buildGetMethod(classWriter); - } + Map loadTypeMethods = new LinkedHashMap<>(); + + ClassTypeDef thisType = ClassTypeDef.of(className); - for (GeneratorAdapter method : loadTypeMethods.values()) { - method.visitMaxs(3, 1); - method.visitEnd(); + classDefBuilder = ClassDef.builder(className) + .addAnnotation(Generated.class) + .superclass(ClassTypeDef.of(AbstractExecutableMethodsDefinition.class)); + + ClassTypeDef methodReferenceType = ClassTypeDef.of(AbstractExecutableMethodsDefinition.MethodReference.class); + TypeDef.Array methodsFieldType = methodReferenceType.array(); + + List metadataMethods = new ArrayList<>(); + for (DispatchWriter.DispatchTarget dispatchTarget : methodDispatchWriter.getDispatchTargets()) { + TypedElement declaringType = dispatchTarget.getDeclaringType(); + MethodElement methodElement = dispatchTarget.getMethodElement(); + Objects.requireNonNull(methodElement); + + int index = 1; + String prefix = "$metadata$"; + String methodName = prefix + methodElement.getName(); + while (methodNames.contains(methodName)) { + methodName = prefix + methodElement.getName() + "$" + (index++); + } + methodNames.add(methodName); + metadataMethods.add(MethodDef.builder(methodName) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .returns(methodReferenceType) + .build((aThis, methodParameters) -> newNewMethodReference((ClassElement) declaringType, methodElement, loadTypeMethods).returning())); } - classWriter.visitEnd(); - } + metadataMethods.forEach(classDefBuilder::addMethod); - private void buildStaticInit(ClassWriter classWriter, Type methodsFieldType) { - GeneratorAdapter staticInit = visitStaticInitializer(classWriter); - classWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, FIELD_METHODS_REFERENCES, methodsFieldType.getDescriptor(), null, null); - pushNewArray(staticInit, AbstractExecutableMethodsDefinition.MethodReference.class, methodDispatchWriter.getDispatchTargets(), dispatchTarget -> { - DispatchWriter.MethodDispatchTarget method = (DispatchWriter.MethodDispatchTarget) dispatchTarget; - pushNewMethodReference( - classWriter, - staticInit, - method.declaringType, - method.methodElement - ); - }); - staticInit.putStatic(thisType, FIELD_METHODS_REFERENCES, methodsFieldType); - staticInit.returnValue(); - staticInit.visitMaxs(DEFAULT_MAX_STACK, 1); - staticInit.visitEnd(); - } + FieldDef methodReferencesField = FieldDef.builder(FIELD_METHODS_REFERENCES, methodsFieldType) + .addModifiers(Modifier.STATIC, Modifier.FINAL, Modifier.PRIVATE) + .initializer( + methodsFieldType.instantiate( + metadataMethods.stream().map(thisType::invokeStatic).toList() + ) + ) + .build(); + + classDefBuilder.addField(methodReferencesField); + + if (methodDispatchWriter.isHasInterceptedMethod()) { + FieldDef interceptable = FieldDef.builder(FIELD_INTERCEPTABLE, TypeDef.Primitive.BOOLEAN) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + classDefBuilder.addField(interceptable); - private void buildConstructor(ClassWriter classWriter, Type methodsFieldType) { - boolean includeInterceptedField = methodDispatchWriter.isHasInterceptedMethod(); - if (includeInterceptedField) { - classWriter.visitField(ACC_FINAL | ACC_PRIVATE, FIELD_INTERCEPTABLE, Type.getType(boolean.class).getDescriptor(), null, null); - - // Create default constructor call other one with 'false' - GeneratorAdapter defaultConstructorWriter = startConstructor(classWriter); - defaultConstructorWriter.loadThis(); - defaultConstructorWriter.push(false); - defaultConstructorWriter.invokeConstructor(thisType, WITH_INTERCEPTED_CONSTRUCTOR); - defaultConstructorWriter.returnValue(); - defaultConstructorWriter.visitMaxs(1, 1); - defaultConstructorWriter.visitEnd(); - - GeneratorAdapter withInterceptedConstructor = startConstructor(classWriter, boolean.class); - withInterceptedConstructor.loadThis(); - withInterceptedConstructor.getStatic(thisType, FIELD_METHODS_REFERENCES, methodsFieldType); - withInterceptedConstructor.invokeConstructor(SUPER_TYPE, SUPER_CONSTRUCTOR); - withInterceptedConstructor.loadThis(); - withInterceptedConstructor.loadArg(0); - withInterceptedConstructor.putField(thisType, FIELD_INTERCEPTABLE, Type.getType(boolean.class)); - withInterceptedConstructor.returnValue(); - withInterceptedConstructor.visitMaxs(1, 1); - withInterceptedConstructor.visitEnd(); + MethodDef constructorWithInterceptable = MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .addParameters(boolean.class) + .addStatement((aThis, methodParameters) -> aThis.superRef() + .invokeConstructor(SUPER_CONSTRUCTOR, thisType.getStaticField(methodReferencesField))) + .addStatement((aThis, methodParameters) -> aThis.field(interceptable).put(methodParameters.get(0))) + .build(); + + classDefBuilder.addMethod( + constructorWithInterceptable + ); + classDefBuilder.addMethod( + MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) -> StatementDef.multi( + aThis.invokeConstructor(constructorWithInterceptable, ExpressionDef.falseValue()) + ) + ) + ); } else { - GeneratorAdapter constructorWriter = startConstructor(classWriter); - constructorWriter.loadThis(); - constructorWriter.getStatic(thisType, FIELD_METHODS_REFERENCES, methodsFieldType); - constructorWriter.invokeConstructor(SUPER_TYPE, SUPER_CONSTRUCTOR); - constructorWriter.returnValue(); - constructorWriter.visitMaxs(1, 1); - constructorWriter.visitEnd(); + classDefBuilder.addMethod( + MethodDef.constructor() + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) -> aThis.superRef() + .invokeConstructor(SUPER_CONSTRUCTOR, thisType.getStaticField(methodReferencesField)) + ) + ); } - } - private void buildGetMethod(ClassWriter classWriter) { - GeneratorAdapter findMethod = new GeneratorAdapter(classWriter.visitMethod( - Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, - GET_METHOD.getName(), - GET_METHOD.getDescriptor(), - null, - null), - ACC_PRIVATE | Opcodes.ACC_FINAL, - GET_METHOD.getName(), - GET_METHOD.getDescriptor() - ); - findMethod.loadThis(); - findMethod.loadArg(0); - findMethod.invokeVirtual(Type.getType(Object.class), new Method("hashCode", Type.INT_TYPE, new Type[]{})); + MethodDef dispatchMethod = methodDispatchWriter.buildDispatchMethod(); + if (dispatchMethod != null) { + classDefBuilder.addMethod(dispatchMethod); + } + MethodDef getTargetMethodByIndex = methodDispatchWriter.buildGetTargetMethodByIndex(); + if (getTargetMethodByIndex != null) { + classDefBuilder.addMethod(getTargetMethodByIndex); + } - Map> hashToMethods = new TreeMap<>(); - for (DispatchWriter.DispatchTarget dispatchTarget : methodDispatchWriter.getDispatchTargets()) { - DispatchWriter.MethodDispatchTarget method = (DispatchWriter.MethodDispatchTarget) dispatchTarget; - int hash = method.methodElement.getName().hashCode(); - hashToMethods.computeIfAbsent(hash, h -> new ArrayList<>()).add(method); + if (methodDispatchWriter.getDispatchTargets().size() > MIN_METHODS_TO_GENERATE_GET_METHOD) { + classDefBuilder.addMethod(buildGetMethod()); } - int[] hashCodeArray = hashToMethods.keySet().stream().mapToInt(i -> i).toArray(); - findMethod.tableSwitch(hashCodeArray, new TableSwitchGenerator() { - @Override - public void generateCase(int hashCode, Label end) { - for (DispatchWriter.MethodDispatchTarget method : hashToMethods.get(hashCode)) { - int index = methodDispatchWriter.getDispatchTargets().indexOf(method); - if (index < 0) { - throw new IllegalStateException(); + loadTypeMethods.values().forEach(classDefBuilder::addMethod); + } + + private MethodDef buildGetMethod() { + return MethodDef.override(GET_METHOD) + .build((aThis, methodParameters) -> { + Map switchCases = new HashMap<>(); + Map> hashToMethods = new TreeMap<>(); + for (DispatchWriter.DispatchTarget dispatchTarget : methodDispatchWriter.getDispatchTargets()) { + MethodElement methodElement = dispatchTarget.getMethodElement(); + if (methodElement == null) { + continue; } - findMethod.loadThis(); - findMethod.push(index); - findMethod.loadArg(0); - findMethod.loadArg(1); - findMethod.invokeVirtual(SUPER_TYPE, AT_INDEX_MATCHED_METHOD); - findMethod.push(true); - Label falseLabel = new Label(); - findMethod.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, falseLabel); - findMethod.loadThis(); - findMethod.push(index); - findMethod.invokeVirtual(SUPER_TYPE, GET_EXECUTABLE_AT_INDEX_METHOD); - findMethod.returnValue(); - findMethod.visitLabel(falseLabel); + hashToMethods.computeIfAbsent(methodElement.getName(), name -> new ArrayList<>()).add(dispatchTarget); } - findMethod.goTo(end); - } - @Override - public void generateDefault() { - } - }); - findMethod.push((String) null); - findMethod.returnValue(); - findMethod.visitMaxs(DEFAULT_MAX_STACK, 1); - findMethod.visitEnd(); - } + for (Map.Entry> e : hashToMethods.entrySet()) { + List statements = new ArrayList<>(); + for (DispatchWriter.DispatchTarget dispatchTarget : e.getValue()) { + int index = methodDispatchWriter.getDispatchTargets().indexOf(dispatchTarget); + StatementDef statementDef = aThis.superRef().invoke( + AT_INDEX_MATCHED_METHOD, + + ExpressionDef.constant(index), + methodParameters.get(0), + methodParameters.get(1) + ).ifTrue( + aThis.superRef().invoke(GET_EXECUTABLE_AT_INDEX_METHOD, ExpressionDef.constant(index)).returning() + ); + statements.add(statementDef); + } - private void pushNewMethodReference(ClassWriter classWriter, - GeneratorAdapter staticInit, - TypedElement declaringType, - MethodElement methodElement) { - int index = 1; - String prefix = "$metadata$"; - String methodName = prefix + methodElement.getName(); - while (methodNames.contains(methodName)) { - methodName = prefix + methodElement.getName() + "$" + (index++); - } - methodNames.add(methodName); - - Method newMethod = new Method(methodName, Type.getType(AbstractExecutableMethodsDefinition.MethodReference.class), new Type[0]); - - GeneratorAdapter newMethodAdapter = new GeneratorAdapter(classWriter.visitMethod( - Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, - newMethod.getName(), - newMethod.getDescriptor(), - null, - null), - ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, - newMethod.getName(), - newMethod.getDescriptor() - ); + switchCases.put(ExpressionDef.constant(e.getKey()), StatementDef.multi(statements)); + } - pushNewMethodReference0(classWriter, newMethodAdapter, declaringType, methodElement, new LinkedHashMap<>()); + return StatementDef.multi( + methodParameters.get(0) + .asStatementSwitch(TypeDef.of(ExecutableMethod.class), switchCases), + ExpressionDef.nullValue().returning() + ); + }); + } - newMethodAdapter.returnValue(); - newMethodAdapter.visitMaxs(DEFAULT_MAX_STACK, 1); - newMethodAdapter.visitEnd(); + private ExpressionDef newNewMethodReference(ClassElement declaringType, + MethodElement methodElement, + Map loadTypeMethods) { - staticInit.invokeStatic(thisType, newMethod); - } + ClassTypeDef methodReferenceType = ClassTypeDef.of(AbstractExecutableMethodsDefinition.MethodReference.class); - private void pushNewMethodReference0(ClassWriter classWriter, - GeneratorAdapter staticInit, - TypedElement declaringType, - MethodElement methodElement, - Map defaultsStorage) { - - staticInit.newInstance(Type.getType(AbstractExecutableMethodsDefinition.MethodReference.class)); - staticInit.dup(); - // 1: declaringType - Type typeReference = JavaModelUtils.getTypeReference(declaringType.getType()); - staticInit.push(typeReference); - // 2: annotationMetadata AnnotationMetadata annotationMetadata = methodElement.getTargetAnnotationMetadata(); if (annotationMetadata instanceof AnnotationMetadataHierarchy hierarchy) { @@ -427,84 +388,66 @@ private void pushNewMethodReference0(ClassWriter classWriter, } } - pushAnnotationMetadata(annotationMetadataWithDefaults, classWriter, staticInit, annotationMetadata, defaultsStorage); - // 3: methodName - staticInit.push(methodElement.getName()); - // 4: return argument - ClassElement genericReturnType = methodElement.getGenericReturnType(); - pushReturnTypeArgument(annotationMetadataWithDefaults, thisType, classWriter, staticInit, declaringType.getName(), genericReturnType, defaultsStorage, loadTypeMethods); - // 5: arguments - ParameterElement[] parameters = methodElement.getSuspendParameters(); - if (parameters.length == 0) { - staticInit.visitInsn(ACONST_NULL); - } else { - pushBuildArgumentsForMethod( - annotationMetadataWithDefaults, - typeReference.getClassName(), - thisType, - classWriter, - staticInit, - Arrays.asList(parameters), - defaultsStorage, - loadTypeMethods - ); - } - // 6: isAbstract - staticInit.push(methodElement.isAbstract()); - // 7: isSuspend - staticInit.push(methodElement.isSuspend()); - - invokeConstructor( - staticInit, - AbstractExecutableMethodsDefinition.MethodReference.class, - Class.class, - AnnotationMetadata.class, - String.class, - Argument.class, - Argument[].class, - boolean.class, - boolean.class); + return methodReferenceType.instantiate( + METHOD_REFERENCE_CONSTRUCTOR, + + // 1: declaringType + ExpressionDef.constant(ClassTypeDef.of(declaringType)), + // 2: annotationMetadata + annotationMetadata(annotationMetadataWithDefaults, annotationMetadata, loadTypeMethods), + // 3: methodName + ExpressionDef.constant(methodElement.getName()), + // 4: return argument + ArgumentExpUtils.pushReturnTypeArgument(annotationMetadataWithDefaults, thisType, declaringType, methodElement.getGenericReturnType(), loadTypeMethods), + // 5: arguments + (methodElement.getSuspendParameters().length == 0 ? ExpressionDef.nullValue() : ArgumentExpUtils.pushBuildArgumentsForMethod( + annotationMetadataWithDefaults, + declaringType.getType(), + thisType, + Arrays.asList(methodElement.getSuspendParameters()), + loadTypeMethods + )), + // 6: isAbstract + ExpressionDef.constant(methodElement.isAbstract()), + // 7: isSuspend + ExpressionDef.constant(methodElement.isSuspend()) + ); } - private void pushAnnotationMetadata(AnnotationMetadata annotationMetadataWithDefaults, - ClassWriter classWriter, - GeneratorAdapter staticInit, - AnnotationMetadata annotationMetadata, - Map defaultsStorage) { + private ExpressionDef annotationMetadata(AnnotationMetadata annotationMetadataWithDefaults, + AnnotationMetadata annotationMetadata, + Map loadTypeMethods) { if (annotationMetadata == AnnotationMetadata.EMPTY_METADATA || annotationMetadata.isEmpty()) { - staticInit.push((String) null); + return ExpressionDef.nullValue(); } else if (annotationMetadata instanceof AnnotationMetadataReference annotationMetadataReference) { - String className = annotationMetadataReference.getClassName(); - staticInit.getStatic(getTypeReferenceForName(className), AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class)); + return AnnotationMetadataGenUtils.annotationMetadataReference(annotationMetadataReference); } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { MutableAnnotationMetadata.contributeDefaults( annotationMetadataWithDefaults, annotationMetadataHierarchy ); - - AnnotationMetadataWriter.instantiateNewMetadataHierarchy( - thisType, - classWriter, - staticInit, - annotationMetadataHierarchy, - defaultsStorage, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(thisType, annotationMetadataHierarchy, loadTypeMethods); } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { MutableAnnotationMetadata.contributeDefaults( annotationMetadataWithDefaults, annotationMetadata ); - - AnnotationMetadataWriter.instantiateNewMetadata( - thisType, - classWriter, - staticInit, - mutableAnnotationMetadata, - defaultsStorage, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadata(thisType, mutableAnnotationMetadata, loadTypeMethods); } else { throw new IllegalStateException("Unknown metadata: " + annotationMetadata); } } + + /** + * @param p The class element + * @return The string representation + */ + private static String toTypeString(ClassElement p) { + String name = p.getName(); + if (p.isArray()) { + return name + IntStream.range(0, p.getArrayDimensions()).mapToObj(ignore -> "[]").collect(Collectors.joining()); + } + return name; + } } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java new file mode 100644 index 00000000000..8ce39f8c2f2 --- /dev/null +++ b/core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.inject.writer; + +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.TypeDef; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * The expressions utils. + * + * @author Denis Stepanov + * @since 4.8 + */ +@Internal +public final class ExpressionsUtils { + + private static final ClassTypeDef MAP_TYPE = ClassTypeDef.of(Map.class); + private static final ClassTypeDef MAP_ENTRY_TYPE = ClassTypeDef.of(Map.Entry.class); + private static final ClassTypeDef LIST_TYPE = ClassTypeDef.of(List.class); + + /** + * Create a map of a string key expression. + * + * @param map The map + * @param skipEmpty Should skip empty value entry + * @param empty Replace the empty entry value with + * @param objAsExpression The object to expression mapper + * @param The value type + * @return The expression + */ + public static ExpressionDef stringMapOf(@NonNull + Map map, + boolean skipEmpty, + @Nullable T empty, + @NonNull Function objAsExpression) { + return stringMapOf(map, skipEmpty, empty, null, objAsExpression); + } + + /** + * Create a map of a string key expression. + * + * @param map The map + * @param skipEmpty Should skip empty value entry + * @param empty Replace the empty entry value with + * @param valuePredicate The value predicate + * @param objAsExpression The object to expression mapper + * @param The value type + * @return The expression + */ + public static ExpressionDef stringMapOf(@NonNull + Map map, + boolean skipEmpty, + @Nullable T empty, + @Nullable + Predicate valuePredicate, + @NonNull Function objAsExpression) { + Set> entrySet = map != null ? map.entrySet() + .stream() + .filter(e -> !skipEmpty || (e.getKey() != null) && (valuePredicate == null || valuePredicate.test(e.getValue()))) + .map(e -> e.getValue() == null && empty != null ? new AbstractMap.SimpleEntry<>(e.getKey().toString(), empty) : new AbstractMap.SimpleEntry<>(e.getKey().toString(), e.getValue())) + .collect(Collectors.toCollection(() -> new TreeSet<>(Map.Entry.comparingByKey()))) : null; + if (entrySet == null || entrySet.isEmpty()) { + return MAP_TYPE.invokeStatic("of", MAP_TYPE); + } + if (entrySet.size() < 11) { + List parameterTypes = new ArrayList<>(entrySet.size()); + List values = new ArrayList<>(entrySet.size()); + for (Map.Entry entry : entrySet) { + parameterTypes.add(TypeDef.OBJECT); + parameterTypes.add(TypeDef.OBJECT); + values.add(ExpressionDef.constant(entry.getKey())); + values.add(objAsExpression.apply(entry.getValue())); + } + return MAP_TYPE.invokeStatic("of", parameterTypes, MAP_TYPE, values); + } + return MAP_TYPE.invokeStatic("ofEntries", + List.of(MAP_ENTRY_TYPE.array()), + MAP_TYPE, + MAP_ENTRY_TYPE + .array() + .instantiate( + entrySet.stream().map(e -> + mapEntry( + ExpressionDef.constant(e.getKey()), + objAsExpression.apply(e.getValue()) + ) + ).toList() + ) + ); + } + + /** + * The map entry expression. + * + * @param key The key + * @param value The value + * @return the expression + */ + public static ExpressionDef mapEntry(ExpressionDef key, ExpressionDef value) { + return MAP_TYPE.invokeStatic( + "entry", + List.of(TypeDef.OBJECT, TypeDef.OBJECT), + MAP_ENTRY_TYPE, + key, + value + ); + } + + /** + * The list of string expression. + * @param strings The strings + * @return the expression + */ + public static ExpressionDef listOfString(List strings) { + return listOf(strings.stream().map(ExpressionDef::constant).toList()); + } + + /** + * The list of expression. + * @param values The values + * @return the expression + */ + public static ExpressionDef listOf(List values) { + if (values != null) { + values = values.stream().filter(Objects::nonNull).toList(); + } + if (values == null || values.isEmpty()) { + return LIST_TYPE.invokeStatic("of", LIST_TYPE); + } + if (values.size() < 11) { + List parameterTypes = new ArrayList<>(values.size()); + for (ExpressionDef ignore : values) { + parameterTypes.add(TypeDef.OBJECT); + } + return LIST_TYPE.invokeStatic("of", parameterTypes, LIST_TYPE, values); + } else { + return LIST_TYPE.invokeStatic("of", List.of(TypeDef.OBJECT.array()), LIST_TYPE, + TypeDef.OBJECT.array().instantiate(values) + ); + } + } + +} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java new file mode 100644 index 00000000000..c45196b6f36 --- /dev/null +++ b/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java @@ -0,0 +1,296 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.inject.writer; + +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.reflect.InstantiationUtils; +import io.micronaut.core.reflect.ReflectionUtils; +import io.micronaut.inject.ast.ClassElement; +import io.micronaut.inject.ast.KotlinParameterElement; +import io.micronaut.inject.ast.MethodElement; +import io.micronaut.inject.ast.ParameterElement; +import io.micronaut.inject.ast.PrimitiveElement; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.TypeDef; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * The writer utils. + * + * @author Denis Stepanov + * @since 4.7 + */ +@Internal +public final class MethodGenUtils { + + private static final TypeDef KOTLIN_CONSTRUCTOR_MARKER = TypeDef.of("kotlin.jvm.internal.DefaultConstructorMarker"); + + private static final java.lang.reflect.Method INSTANTIATE_METHOD = ReflectionUtils.getRequiredInternalMethod( + InstantiationUtils.class, + "instantiate", + Class.class, + Class[].class, + Object[].class + ); + + /** + * The number of Kotlin defaults masks. + * + * @param parameters The parameters + * @return The number if masks + * @since 4.6.2 + */ + public static int calculateNumberOfKotlinDefaultsMasks(List parameters) { + return (int) Math.ceil(parameters.size() / 32.0); + } + + /** + * Checks if parameter include Kotlin defaults. + * + * @param arguments The arguments + * @return true if include + * @since 4.6.2 + */ + public static boolean hasKotlinDefaultsParameters(List arguments) { + return arguments.stream().anyMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); + } + + public static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringType, + MethodElement methodElement, + ExpressionDef target, + List values, + List hasValuesExpressions) { + int numberOfMasks = MethodGenUtils.calculateNumberOfKotlinDefaultsMasks(List.of(methodElement.getSuspendParameters())); + ExpressionDef[] masks = MethodGenUtils.computeKotlinDefaultsMask(numberOfMasks, List.of(methodElement.getSuspendParameters()), hasValuesExpressions); + List newValues = new ArrayList<>(); + newValues.add(target); + newValues.addAll(values); + newValues.addAll(List.of(masks)); // Bit mask of defaults + newValues.add(ExpressionDef.nullValue()); // Last parameter is just a marker and is always null + + MethodDef defaultKotlinMethod = MethodGenUtils.asDefaultKotlinMethod(TypeDef.of(declaringType), methodElement, numberOfMasks); + + return ClassTypeDef.of(declaringType).invokeStatic(defaultKotlinMethod, newValues); + } + + public static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringType, + MethodElement methodElement, + ExpressionDef target, + List values) { + return invokeKotlinDefaultMethod(declaringType, methodElement, target, values, values.stream().map(ExpressionDef::isNonNull).toList()); + } + + public static ExpressionDef invokeBeanConstructor(MethodElement constructor, + boolean allowKotlinDefaults, + @Nullable + List values) { + return invokeBeanConstructor(constructor, constructor.isReflectionRequired(), allowKotlinDefaults, values, values == null ? null : values.stream().map(ExpressionDef::isNonNull).toList()); + } + + public static ExpressionDef invokeBeanConstructor(MethodElement constructor, + boolean requiresReflection, + boolean allowKotlinDefaults, + @Nullable + List values, + @Nullable + List hasValuesExpressions) { + ClassTypeDef beanType = (ClassTypeDef) TypeDef.erasure(constructor.getOwningType()); + + boolean isConstructor = constructor.getName().equals(""); + boolean isCompanion = constructor.getOwningType().getSimpleName().endsWith("$Companion"); + List constructorArguments = Arrays.asList(constructor.getParameters()); + allowKotlinDefaults = allowKotlinDefaults && hasKotlinDefaultsParameters(constructorArguments); + + List constructorValues = constructorValues(constructor.getParameters(), values, allowKotlinDefaults); + + if (requiresReflection && !isCompanion) { // Companion and reflection not implemented + return ClassTypeDef.of(InstantiationUtils.class).invokeStatic( + INSTANTIATE_METHOD, + + ExpressionDef.constant(beanType), + TypeDef.CLASS.array().instantiate( + Arrays.stream(constructor.getParameters()).map(param -> + ExpressionDef.constant(TypeDef.erasure(param.getType())) + ).toList() + ), + TypeDef.OBJECT.array().instantiate(constructorValues) + ); + } + + if (isConstructor) { + if (allowKotlinDefaults) { + int numberOfMasks = calculateNumberOfKotlinDefaultsMasks(constructorArguments); + // Calculate the Kotlin defaults mask + // Every bit indicated true/false if the parameter should have the default value set + ExpressionDef[] masksExpressions = computeKotlinDefaultsMask(numberOfMasks, constructorArguments, hasValuesExpressions); + + List newValues = new ArrayList<>(); + newValues.addAll(constructorValues); + newValues.addAll(List.of(masksExpressions)); // Bit mask of defaults + newValues.add(ExpressionDef.nullValue()); // Last parameter is just a marker and is always null + List defaultKotlinConstructorParameters = getDefaultKotlinConstructorParameters(constructor.getParameters(), masksExpressions.length); + return beanType.instantiate( + defaultKotlinConstructorParameters, + newValues + ); + } + return beanType.instantiate(constructor, constructorValues); + } else if (constructor.isStatic()) { + return beanType.invokeStatic(constructor, constructorValues); + } else if (isCompanion) { + if (constructor.isStatic()) { + return beanType.invokeStatic(constructor, constructorValues); + } + return ((ClassTypeDef) TypeDef.erasure(constructor.getReturnType())) + .getStaticField("Companion", beanType) + .invoke(constructor, constructorValues); + } + throw new IllegalStateException("Unknown constructor"); + } + + private static List constructorValues(ParameterElement[] constructorArguments, + @Nullable + List values, + boolean addKotlinDefaults) { + List expressions = new ArrayList<>(constructorArguments.length); + for (int i = 0; i < constructorArguments.length; i++) { + ParameterElement constructorArgument = constructorArguments[i]; + ExpressionDef value = values == null ? null : values.get(i); + if (value != null) { + if (!addKotlinDefaults || value instanceof ExpressionDef.Constant constant && constant.value() != null || !constructorArgument.isPrimitive()) { + expressions.add(value); + } else { + expressions.add( + ClassTypeDef.of(Objects.class) + .invokeStatic( + ReflectionUtils.getRequiredMethod(Objects.class, "requireNonNullElse", Object.class, Object.class), + + value.cast(TypeDef.OBJECT), // Remove any previous casts + getDefaultValue(constructorArgument) + ).cast(value.type()) + ); + } + continue; + } + expressions.add(getDefaultValue(constructorArgument)); + } + return expressions; + } + + private static ExpressionDef getDefaultValue(ParameterElement constructorArgument) { + ClassElement type = constructorArgument.getType(); + if (type.isPrimitive() && !type.isArray()) { + if (type.equals(PrimitiveElement.BOOLEAN)) { + return ExpressionDef.falseValue(); + } + return TypeDef.Primitive.INT.constant(0).cast(TypeDef.erasure(type)); + } + return ExpressionDef.nullValue(); + } + + private static List getDefaultKotlinConstructorParameters(ParameterElement[] constructorArguments, int numberOfMasks) { + List parameters = new ArrayList<>(constructorArguments.length + numberOfMasks + 1); + for (ParameterElement constructorArgument : constructorArguments) { + parameters.add(TypeDef.erasure(constructorArgument.getType())); + } + for (int i = 0; i < numberOfMasks; i++) { + parameters.add(TypeDef.Primitive.INT); + } + parameters.add(KOTLIN_CONSTRUCTOR_MARKER); + return parameters; + } + + /** + * Create a method for Kotlin default invocation. + * + * @param owningType The owing type + * @param method The method + * @param numberOfMasks The number of default masks + * @return A new method + */ + static MethodDef asDefaultKotlinMethod(TypeDef owningType, MethodElement method, int numberOfMasks) { + ParameterElement[] prevParameters = method.getSuspendParameters(); + List parameters = new ArrayList<>(1 + prevParameters.length + numberOfMasks + 1); + parameters.add(owningType); + for (ParameterElement constructorArgument : prevParameters) { + parameters.add(TypeDef.erasure(constructorArgument.getType())); + } + for (int i = 0; i < numberOfMasks; i++) { + parameters.add(TypeDef.Primitive.INT); + } + parameters.add(TypeDef.OBJECT); + return MethodDef.builder(method.getName() + "$default") + .addParameters(parameters) + .returns(method.isSuspend() ? TypeDef.OBJECT : TypeDef.erasure(method.getReturnType())) + .build(); + } + + public static ExpressionDef[] computeKotlinDefaultsMask(int numberOfMasks, + List parameters, + @Nullable + List hasValuesExpressions) { + ExpressionDef[] masksLocal = new ExpressionDef[numberOfMasks]; + for (int i = 0; i < numberOfMasks; i++) { + int fromIndex = i * 32; + List params = parameters.subList(fromIndex, Math.min(fromIndex + 32, parameters.size())); + if (hasValuesExpressions == null) { + masksLocal[i] = TypeDef.Primitive.INT.constant((int) ((long) Math.pow(2, params.size() + 1) - 1)); + } else { + ExpressionDef maskValue = TypeDef.Primitive.INT.constant(0); + int maskIndex = 1; + int paramIndex = fromIndex; + for (ParameterElement parameter : params) { + if (parameter instanceof KotlinParameterElement kp && kp.hasDefault()) { + maskValue = writeMask(hasValuesExpressions, kp, paramIndex, maskIndex, maskValue); + } + maskIndex *= 2; + paramIndex++; + } + masksLocal[i] = maskValue; + } + } + return masksLocal; + } + + private static ExpressionDef writeMask(@Nullable + List hasValuesExpressions, + KotlinParameterElement kp, + int paramIndex, + int maskIndex, + ExpressionDef maskValue) { + TypeDef.Primitive intType = TypeDef.Primitive.INT; + if (hasValuesExpressions != null) { + return maskValue.math("|", + hasValuesExpressions.get(paramIndex).ifTrue( + intType.constant(0), + intType.constant(maskIndex) + ) + ); + } else if (kp.getType().isPrimitive() && !kp.getType().isArray()) { + // We cannot recognize the default from a primitive value + return maskValue; + } + return maskValue; + } + +} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/StringSwitchWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/StringSwitchWriter.java deleted file mode 100644 index f70f5ee4fd1..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/writer/StringSwitchWriter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2017-2021 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.writer; - -import io.micronaut.core.annotation.Internal; -import org.objectweb.asm.Label; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; -import org.objectweb.asm.commons.TableSwitchGenerator; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -/** - * String switch writer. - * - * @author Denis Stepanov - * @since 3.1 - */ -@Internal -public abstract class StringSwitchWriter { - - /** - * @return Get cases keys - */ - protected abstract Set getKeys(); - - /** - * Push the string value that is being evaluated. - */ - protected abstract void pushStringValue(); - - /** - * Generate on case matches statement. - * - * @param value The string that matched - * @param end The end label - */ - protected abstract void onMatch(String value, Label end); - - /** - * Generate default statement. - */ - protected void generateDefault() { - } - - /** - * Write the string switch implementation. - * - * @param writer The writer - */ - public void write(GeneratorAdapter writer) { - Set keys = getKeys(); - if (keys.isEmpty()) { - return; - } - if (keys.size() == 1) { - Label end = new Label(); - String key = keys.iterator().next(); - generateValueCase(writer, key, end); - writer.visitLabel(end); - return; - } - Map> hashToValue = new HashMap<>(); - for (String string : keys) { - hashToValue.computeIfAbsent(string.hashCode(), hashCode -> new TreeSet<>()).add(string); - } - int[] hashCodeArray = hashToValue.keySet().stream().mapToInt(i -> i).toArray(); - Arrays.sort(hashCodeArray); - pushStringValue(); - writer.invokeVirtual(Type.getType(Object.class), new Method("hashCode", Type.INT_TYPE, new Type[]{})); - writer.tableSwitch(hashCodeArray, new TableSwitchGenerator() { - @Override - public void generateCase(int hashCode, Label end) { - for (String string : hashToValue.get(hashCode)) { - generateValueCase(writer, string, end); - } - writer.goTo(end); - } - - @Override - public void generateDefault() { - StringSwitchWriter.this.generateDefault(); - } - }); - } - - /** - * Generate the switch case. - * - * @param writer The writer - * @param string The string matched - * @param end The end label - */ - protected void generateValueCase(GeneratorAdapter writer, String string, Label end) { - pushStringValue(); - writer.push(string); - writer.invokeVirtual(Type.getType(Object.class), new Method("equals", Type.BOOLEAN_TYPE, new Type[]{Type.getType(Object.class)})); - writer.push(true); - Label falseLabel = new Label(); - writer.ifCmp(Type.BOOLEAN_TYPE, GeneratorAdapter.NE, falseLabel); - onMatch(string, end); - writer.visitLabel(falseLabel); - } - -} diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/WriterUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/WriterUtils.java deleted file mode 100644 index 865bcdbae71..00000000000 --- a/core-processor/src/main/java/io/micronaut/inject/writer/WriterUtils.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.inject.writer; - -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.reflect.InstantiationUtils; -import io.micronaut.core.reflect.ReflectionUtils; -import io.micronaut.inject.ast.ClassElement; -import io.micronaut.inject.ast.KotlinParameterElement; -import io.micronaut.inject.ast.MethodElement; -import io.micronaut.inject.ast.ParameterElement; -import io.micronaut.inject.ast.PrimitiveElement; -import io.micronaut.inject.processing.JavaModelUtils; -import org.objectweb.asm.Label; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; - -import static io.micronaut.expressions.parser.ast.util.TypeDescriptors.BOOLEAN; -import static io.micronaut.inject.writer.AbstractClassFileWriter.getConstructorDescriptor; -import static io.micronaut.inject.writer.AbstractClassFileWriter.getMethodDescriptor; -import static io.micronaut.inject.writer.AbstractClassFileWriter.getTypeReference; -import static io.micronaut.inject.writer.AbstractClassFileWriter.pushNewArray; -import static io.micronaut.inject.writer.AbstractClassFileWriter.pushNewArrayIndexed; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; -import static org.objectweb.asm.commons.GeneratorAdapter.EQ; - -/** - * The writer utils. - * - * @author Denis Stepanov - * @since 4.3.0 - */ -@Internal -public final class WriterUtils { - private static final String METHOD_NAME_INSTANTIATE = "instantiate"; - - /** - * The number of Kotlin defaults masks. - * @param parameters The parameters - * @return The number if masks - * @since 4.6.2 - */ - public static int calculateNumberOfKotlinDefaultsMasks(List parameters) { - return (int) Math.ceil(parameters.size() / 32.0); - } - - /** - * Checks if parameter include Kotlin defaults. - * @param arguments The arguments - * @return true if include - * @since 4.6.2 - */ - public static boolean hasKotlinDefaultsParameters(List arguments) { - return arguments.stream().anyMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); - } - - public static void invokeBeanConstructor(GeneratorAdapter writer, - MethodElement constructor, - boolean allowKotlinDefaults, - @Nullable - BiConsumer argumentsPusher) { - invokeBeanConstructor(writer, constructor, constructor.isReflectionRequired(), allowKotlinDefaults, argumentsPusher, null); - } - - public static void invokeBeanConstructor(GeneratorAdapter writer, - MethodElement constructor, - boolean requiresReflection, - boolean allowKotlinDefaults, - @Nullable - BiConsumer argumentsPusher, - @Nullable - BiFunction argumentValueIsPresentPusher) { - Type beanType = getTypeReference(constructor.getOwningType()); - boolean isConstructor = constructor.getName().equals(""); - ClassElement declaringType = constructor.getOwningType(); - boolean isCompanion = declaringType.getSimpleName().endsWith("$Companion"); - - List constructorArguments = Arrays.asList(constructor.getParameters()); - Collection argumentTypes = constructorArguments.stream().map(pe -> - JavaModelUtils.getTypeReference(pe.getType()) - ).toList(); - boolean isKotlinDefault = allowKotlinDefaults && hasKotlinDefaultsParameters(constructorArguments); - - int[] masksLocal = null; - if (isKotlinDefault) { - // Calculate the Kotlin defaults mask - // Every bit indicated true/false if the parameter should have the default value set - masksLocal = computeKotlinDefaultsMask(writer, argumentsPusher, argumentValueIsPresentPusher, constructorArguments); - } - - if (requiresReflection && !isCompanion) { // Companion reflection not implemented - writer.push(beanType); - pushNewArray(writer, Class.class, constructorArguments, arg -> writer.push(getTypeReference(arg))); - pushNewArrayIndexed(writer, Object.class, constructorArguments, (i, parameter) -> { - pushValue(writer, argumentsPusher, parameter, i); - if (parameter.isPrimitive()) { - writer.unbox(getTypeReference(parameter)); - } - }); - writer.invokeStatic( - Type.getType(InstantiationUtils.class), - org.objectweb.asm.commons.Method.getMethod( - ReflectionUtils.getRequiredInternalMethod( - InstantiationUtils.class, - METHOD_NAME_INSTANTIATE, - Class.class, - Class[].class, - Object[].class - ) - ) - ); - if (JavaModelUtils.isPrimitive(beanType)) { - writer.unbox(beanType); - } else { - writer.checkCast(beanType); - } - return; - } - - if (!constructor.isStatic()) { - if (isConstructor) { - writer.newInstance(beanType); - writer.dup(); - } else if (isCompanion) { - writer.getStatic( - getTypeReference(constructor.getReturnType()), - "Companion", - JavaModelUtils.getTypeReference(declaringType) - ); - } - } - - int index = 0; - for (ParameterElement constructorArgument : constructorArguments) { - pushValue(writer, argumentsPusher, constructorArgument, index); - index++; - } - - if (isConstructor) { - final String constructorDescriptor = getConstructorDescriptor(constructorArguments); - Method method = new Method("", constructorDescriptor); - if (isKotlinDefault) { - method = asDefaultKotlinConstructor(method, masksLocal.length); - for (int maskLocal : masksLocal) { - writer.loadLocal(maskLocal, Type.INT_TYPE); // Bit mask of defaults - } - writer.push((String) null); // Last parameter is just a marker and is always null - } - writer.invokeConstructor(beanType, method); - } else if (constructor.isStatic()) { - final String methodDescriptor = getMethodDescriptor(getTypeReference(constructor.getReturnType()), argumentTypes); - Method method = new Method(constructor.getName(), methodDescriptor); - boolean isInterface = constructor.getDeclaringType().isInterface(); - writer.visitMethodInsn(INVOKESTATIC, - getTypeReference(declaringType).getInternalName(), method.getName(), - method.getDescriptor(), isInterface); - } else if (isCompanion) { - if (constructor.isStatic()) { - writer.invokeStatic( - JavaModelUtils.getTypeReference(declaringType), - new Method(constructor.getName(), getMethodDescriptor(getTypeReference(constructor.getReturnType()), argumentTypes)) - ); - } else { - writer.invokeVirtual( - JavaModelUtils.getTypeReference(declaringType), - new Method(constructor.getName(), getMethodDescriptor(getTypeReference(constructor.getReturnType()), argumentTypes)) - ); - } - } - } - - private static void pushValue(GeneratorAdapter writer, - @Nullable - BiConsumer argumentsPusher, - ParameterElement parameter, - int index) { - if (argumentsPusher == null) { - pushDefaultTypeValue(writer, parameter.getType()); - } else { - argumentsPusher.accept(index, parameter); - } - } - - /** - * Pushed a default value. - * - * @param writer The writer - * @param type The type - */ - public static void pushDefaultTypeValue(GeneratorAdapter writer, ClassElement type) { - if (type.isPrimitive() && !type.isArray()) { - if (type.equals(PrimitiveElement.BOOLEAN)) { - writer.push(false); - } else { - writer.push(0); - writer.cast(Type.INT_TYPE, JavaModelUtils.getTypeReference(type)); - } - } else { - writer.push((String) null); - } - } - - private static Method asDefaultKotlinConstructor(Method method, int numberOfMasks) { - Type[] argumentTypes = method.getArgumentTypes(); - int length = argumentTypes.length; - Type[] newArgumentTypes = Arrays.copyOf(argumentTypes, length + numberOfMasks + 1); - for (int i = 0; i < numberOfMasks; i++) { - newArgumentTypes[length + i] = Type.INT_TYPE; - } - newArgumentTypes[length + numberOfMasks] = Type.getObjectType("kotlin/jvm/internal/DefaultConstructorMarker"); - return new Method(method.getName(), method.getReturnType(), newArgumentTypes); - } - - /** - * Create a method for Kotlin default invocation. - * @param method The method - * @param declaringTypeObject The declaring type - * @param numberOfMasks The number of default masks - * @return A new method - */ - static Method asDefaultKotlinMethod(Method method, Type declaringTypeObject, int numberOfMasks) { - Type[] argumentTypes = method.getArgumentTypes(); - int length = argumentTypes.length; - Type[] newArgumentTypes = new Type[length + 2 + numberOfMasks]; - System.arraycopy(argumentTypes, 0, newArgumentTypes, 1, length); - newArgumentTypes[0] = declaringTypeObject; - for (int i = 0; i < numberOfMasks; i++) { - newArgumentTypes[1 + length + i] = Type.INT_TYPE; - } - newArgumentTypes[length + 1 + numberOfMasks] = Type.getObjectType("java/lang/Object"); - return new Method(method.getName() + "$default", method.getReturnType(), newArgumentTypes); - } - - /** - * Computes Kotlin default method mask. - * - * @param writer The writer - * @param argumentValuePusher The argument value pusher - * @param argumentValueIsPresentPusher The argument is present pusher - * @param parameters The arguments - * @return The masks - */ - public static int[] computeKotlinDefaultsMask(GeneratorAdapter writer, - @Nullable - BiConsumer argumentValuePusher, - @Nullable - BiFunction argumentValueIsPresentPusher, - List parameters) { - int numberOfMasks = calculateNumberOfKotlinDefaultsMasks(parameters); - int[] masksLocal = new int[numberOfMasks]; - for (int i = 0; i < numberOfMasks; i++) { - int maskLocal = writer.newLocal(Type.INT_TYPE); - masksLocal[i] = maskLocal; - int fromIndex = i * 32; - List params = parameters.subList(fromIndex, Math.min(fromIndex + 32, parameters.size())); - if (argumentValueIsPresentPusher == null && argumentValuePusher == null) { - writer.push((int) ((long) Math.pow(2, params.size() + 1) - 1)); - writer.storeLocal(maskLocal); - } else { - writer.push(0); - writer.storeLocal(maskLocal); - int maskIndex = 1; - int paramIndex = fromIndex; - for (ParameterElement parameter : params) { - if (parameter instanceof KotlinParameterElement kp && kp.hasDefault()) { - writeMask(writer, argumentValuePusher, argumentValueIsPresentPusher, kp, paramIndex, maskIndex, maskLocal); - } - maskIndex *= 2; - paramIndex++; - } - } - } - return masksLocal; - } - - private static void writeMask(GeneratorAdapter writer, - BiConsumer argumentValuePusher, - BiFunction argumentValueIsPresentPusher, - KotlinParameterElement kp, - int paramIndex, - int maskIndex, - int maskLocal) { - Label elseLabel = writer.newLabel(); - if (argumentValueIsPresentPusher != null && argumentValueIsPresentPusher.apply(paramIndex, kp)) { - // Is present boolean pushed to the stack - writer.push(true); - writer.ifCmp(BOOLEAN, EQ, elseLabel); - } else if (kp.getType().isPrimitive() && !kp.getType().isArray()) { - // We cannot recognize the default from a primitive value - return; - } else { - argumentValuePusher.accept(paramIndex, kp); - writer.ifNonNull(elseLabel); - } - writer.push(maskIndex); - writer.loadLocal(maskLocal, Type.INT_TYPE); - writer.math(GeneratorAdapter.OR, Type.INT_TYPE); - writer.storeLocal(maskLocal); - writer.visitLabel(elseLabel); - } - -} diff --git a/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java b/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java index 67a0d2aeab2..1da4b2a4d18 100644 --- a/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java +++ b/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java @@ -199,6 +199,28 @@ public static R invokeMethod(T instance, Method method, Object... argumen } } + /** + * Invokes an inaccessible method. + * + * @param instance The instance + * @param method The method + * @param arguments The arguments + * @param The return type + * @param The instance type + * @return The result + */ + @UsedByGeneratedCode + public static R invokeInaccessibleMethod(T instance, Method method, Object... arguments) { + try { + method.setAccessible(true); + return (R) method.invoke(instance, arguments); + } catch (IllegalAccessException e) { + throw new InvocationException("Illegal access invoking method [" + method + "]: " + e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new InvocationException("Exception occurred invoking method [" + method + "]: " + e.getMessage(), e); + } + } + /** * Finds a method on the given type for the given name. * diff --git a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java index 4b8fe9770c3..86cab941c81 100644 --- a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java +++ b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java @@ -16,17 +16,28 @@ package io.micronaut.graal.reflect; import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.Generated; import io.micronaut.core.graal.GraalReflectionConfigurer; +import io.micronaut.inject.annotation.AnnotationMetadataGenUtils; import io.micronaut.inject.ast.ClassElement; -import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.inject.writer.AbstractAnnotationMetadataWriter; +import io.micronaut.inject.writer.ClassOutputWriter; import io.micronaut.inject.writer.ClassWriterOutputVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; +import io.micronaut.sourcegen.bytecode.ByteCodeWriter; +import io.micronaut.sourcegen.model.AnnotationDef; +import io.micronaut.sourcegen.model.ClassDef; +import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.FieldDef; +import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.StatementDef; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.getAnnotationMetadataMethodDef; /** * Generates Runtime executed Graal configuration. @@ -34,17 +45,17 @@ * @author graemerocher * @since 3.5.0 */ -final class GraalReflectionMetadataWriter extends AbstractAnnotationMetadataWriter { +final class GraalReflectionMetadataWriter implements ClassOutputWriter { + private final ClassElement originatingElement; + private final AnnotationMetadata annotationMetadata; private final String className; - private final String classInternalName; public GraalReflectionMetadataWriter(ClassElement originatingElement, - AnnotationMetadata annotationMetadata, - VisitorContext visitorContext) { - super(resolveName(originatingElement), originatingElement, annotationMetadata, true, visitorContext); - this.className = targetClassType.getClassName(); - this.classInternalName = targetClassType.getInternalName(); + AnnotationMetadata annotationMetadata) { + this.annotationMetadata = annotationMetadata; + this.originatingElement = originatingElement; + this.className = resolveName(originatingElement); } private static String resolveName(ClassElement originatingElement) { @@ -53,49 +64,39 @@ private static String resolveName(ClassElement originatingElement) { @Override public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException { - try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, getOriginatingElements())) { - ClassWriter classWriter = generateClassBytes(); - outputStream.write(classWriter.toByteArray()); + ClassTypeDef thisType = ClassTypeDef.of(className); + try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, originatingElement)) { + ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(className) + .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", GraalReflectionConfigurer.class.getName()).build()) + .addSuperinterface(ClassTypeDef.of(GraalReflectionConfigurer.class)) + .addMethod(getAnnotationMetadataMethodDef(thisType, annotationMetadata)); + + Map loadTypeMethods = new LinkedHashMap<>(); + // write the static initializers for the annotation metadata + List staticInit = new ArrayList<>(); + AnnotationMetadataGenUtils.writeAnnotationDefault(staticInit, thisType, annotationMetadata, loadTypeMethods); + + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField( + thisType, + annotationMetadata, + loadTypeMethods + ); + + loadTypeMethods.values().forEach(classDefBuilder::addMethod); + + if (annotationMetadataField != null) { + classDefBuilder.addField(annotationMetadataField); + if (!staticInit.isEmpty()) { + classDefBuilder.addStaticInitializer(StatementDef.multi(staticInit)); + } + } + outputStream.write(new ByteCodeWriter().write(classDefBuilder.build())); } classWriterOutputVisitor.visitServiceDescriptor( GraalReflectionConfigurer.class, className, - getOriginatingElement() - ); - } - - private ClassWriter generateClassBytes() { - var classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); - startService( - classWriter, - getInternalName(GraalReflectionConfigurer.class.getName()), - classInternalName, - Type.getType(Object.class), - getInternalName(GraalReflectionConfigurer.class.getName()) + originatingElement ); - writeAnnotationMetadataStaticInitializer(classWriter); - writeConstructor(classWriter); - writeGetAnnotationMetadataMethod(classWriter); - - for (GeneratorAdapter method : loadTypeMethods.values()) { - method.visitMaxs(3, 1); - method.visitEnd(); - } - - return classWriter; } - private void writeConstructor(ClassWriter classWriter) { - GeneratorAdapter cv = startConstructor(classWriter); - - // ALOAD 0 - cv.loadThis(); - invokeConstructor(cv, Object.class); - - // RETURN - cv.visitInsn(RETURN); - // MAXSTACK = 2 - // MAXLOCALS = 1 - cv.visitMaxs(2, 1); - } } diff --git a/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java b/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java index ee18b57c1c4..41e630ea3e9 100644 --- a/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java +++ b/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java @@ -212,8 +212,7 @@ public void visitClass(ClassElement element, VisitorContext context) { ); GraalReflectionMetadataWriter writer = new GraalReflectionMetadataWriter( element, - annotationMetadata, - context + annotationMetadata ); try { writer.accept(context); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84bb572b30c..8fdb2263673 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,6 +64,7 @@ tomlj="1.1.1" vertx = "4.5.11" wiremock = "2.33.2" mimepull = "1.10.0" +sourcegen-bytecode-generator = "1.5.0" # # Versions which start with managed- are managed by Micronaut in the sense @@ -301,6 +302,8 @@ mimepull = { module = "org.jvnet.mimepull:mimepull", version.ref = "mimepull" } wiremock = { module = "com.github.tomakehurst:wiremock-jre8", version.ref = "wiremock" } +sourcegen-bytecode-generator = { module = "io.micronaut.sourcegen:micronaut-sourcegen-bytecode-writer", version.ref = "sourcegen-bytecode-generator" } + [bundles] asm = ["asm", "asm-commons"] diff --git a/inject-groovy-test/src/main/groovy/io/micronaut/ast/transform/test/AbstractBeanDefinitionSpec.groovy b/inject-groovy-test/src/main/groovy/io/micronaut/ast/transform/test/AbstractBeanDefinitionSpec.groovy index c06c091a6c5..f5e64de3e31 100644 --- a/inject-groovy-test/src/main/groovy/io/micronaut/ast/transform/test/AbstractBeanDefinitionSpec.groovy +++ b/inject-groovy-test/src/main/groovy/io/micronaut/ast/transform/test/AbstractBeanDefinitionSpec.groovy @@ -27,6 +27,7 @@ import io.micronaut.context.DefaultApplicationContext import io.micronaut.context.Qualifier import io.micronaut.context.event.ApplicationEventPublisherFactory import io.micronaut.core.annotation.AnnotationMetadata +import io.micronaut.core.annotation.AnnotationMetadataProvider import io.micronaut.core.beans.BeanIntrospection import io.micronaut.core.naming.NameUtils import io.micronaut.inject.BeanDefinition @@ -219,23 +220,20 @@ abstract class AbstractBeanDefinitionSpec extends Specification { @CompileStatic protected AnnotationMetadata writeAndLoadMetadata(String className, AnnotationMetadata toWrite) { - ByteArrayOutputStream stream = new ByteArrayOutputStream() - new AnnotationMetadataWriter(className, null, toWrite, true) - .writeTo(stream) + byte[] bytecode = AnnotationMetadataWriter.write(className, toWrite) className = className + AnnotationMetadata.CLASS_NAME_SUFFIX ClassLoader classLoader = new ClassLoader() { @Override protected Class findClass(String name) throws ClassNotFoundException { if (name == className) { - byte[] bytes = stream.toByteArray() + byte[] bytes = bytecode return super.defineClass(name, bytes, 0, bytes.length) } return super.findClass(name) } } - AnnotationMetadata metadata = (AnnotationMetadata) classLoader.loadClass(className).newInstance() - return metadata + return ((AnnotationMetadataProvider) classLoader.loadClass(className).newInstance()).getAnnotationMetadata() } /** diff --git a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/InjectTransform.groovy b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/InjectTransform.groovy index f1c39452cca..4485dbd2998 100644 --- a/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/InjectTransform.groovy +++ b/inject-groovy/src/main/groovy/io/micronaut/ast/groovy/InjectTransform.groovy @@ -84,8 +84,7 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware { def writer = new BeanConfigurationWriter( classNode.packageName, groovyPackageElement, - groovyPackageElement.getAnnotationMetadata(), - visitorContext + groovyPackageElement.getAnnotationMetadata() ) try { writer.accept(outputVisitor) diff --git a/inject-java-test/src/main/groovy/io/micronaut/annotation/processing/test/AbstractTypeElementSpec.groovy b/inject-java-test/src/main/groovy/io/micronaut/annotation/processing/test/AbstractTypeElementSpec.groovy index 0d825976416..fdb703528a3 100644 --- a/inject-java-test/src/main/groovy/io/micronaut/annotation/processing/test/AbstractTypeElementSpec.groovy +++ b/inject-java-test/src/main/groovy/io/micronaut/annotation/processing/test/AbstractTypeElementSpec.groovy @@ -22,7 +22,6 @@ import io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder import io.micronaut.annotation.processing.JavaNativeElementsHelper import io.micronaut.annotation.processing.ModelUtils import io.micronaut.annotation.processing.TypeElementVisitorProcessor -import io.micronaut.annotation.processing.visitor.JavaClassElement import io.micronaut.annotation.processing.visitor.JavaElementFactory import io.micronaut.annotation.processing.visitor.JavaVisitorContext import io.micronaut.aop.internal.InterceptorRegistryBean @@ -34,6 +33,7 @@ import io.micronaut.context.Qualifier import io.micronaut.context.env.Environment import io.micronaut.context.event.ApplicationEventPublisherFactory import io.micronaut.core.annotation.AnnotationMetadata +import io.micronaut.core.annotation.AnnotationMetadataProvider import io.micronaut.core.annotation.Experimental import io.micronaut.core.annotation.NonNull import io.micronaut.core.annotation.Nullable @@ -539,23 +539,20 @@ class Test { @CompileStatic protected AnnotationMetadata writeAndLoadMetadata(String className, AnnotationMetadata toWrite) { - ByteArrayOutputStream stream = new ByteArrayOutputStream() - new AnnotationMetadataWriter(className, null, toWrite, true) - .writeTo(stream) + byte[] bytecode = AnnotationMetadataWriter.write(className, toWrite) className = className + AnnotationMetadata.CLASS_NAME_SUFFIX ClassLoader classLoader = new ClassLoader() { @Override protected Class findClass(String name) throws ClassNotFoundException { if (name == className) { - byte[] bytes = stream.toByteArray() + byte[] bytes = bytecode return defineClass(name, bytes, 0, bytes.length) } return super.findClass(name) } } - AnnotationMetadata metadata = (AnnotationMetadata) classLoader.loadClass(className).newInstance() - return metadata + return ((AnnotationMetadataProvider) classLoader.loadClass(className).newInstance()).getAnnotationMetadata() } protected JavaAnnotationMetadataBuilder newJavaAnnotationBuilder() { diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/PackageConfigurationInjectProcessor.java b/inject-java/src/main/java/io/micronaut/annotation/processing/PackageConfigurationInjectProcessor.java index ee3c3976d8a..c90112ebea6 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/PackageConfigurationInjectProcessor.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/PackageConfigurationInjectProcessor.java @@ -91,8 +91,7 @@ public Object visitPackage(PackageElement packageElement, Object p) { var writer = new BeanConfigurationWriter( packageName, javaPackageElement, - javaPackageElement.getAnnotationMetadata(), - javaVisitorContext + javaPackageElement.getAnnotationMetadata() ); try { writer.accept(classWriterOutputVisitor); diff --git a/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy index 6cc965d7466..14ab2946334 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy @@ -30,6 +30,7 @@ import io.micronaut.core.annotation.AnnotationValue import io.micronaut.core.annotation.TypeHint import io.micronaut.inject.BeanDefinition import io.micronaut.retry.annotation.Recoverable +import spock.lang.PendingFeature import spock.lang.Unroll import java.lang.annotation.Documented @@ -62,6 +63,7 @@ class Test { annotationMetadata.hasDeclaredAnnotation(Outer.Inner) } + @PendingFeature @Unroll void "test read/write annotation array type #type"() { given: diff --git a/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java b/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java index bd3eb10f623..c931ddf5e59 100644 --- a/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java +++ b/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java @@ -2183,9 +2183,11 @@ private Object resolvePropertyValue(BeanResolutionContext resolutionContext, Bea ConfigurationPath previousPath = isNotInnerConfiguration ? resolutionContext.setConfigurationPath(null) : null; try { if (argument.isDeclaredNullable() || isOptional) { - return resolutionContext.findBean(argument, qualifier).orElse(null); + K k = resolutionContext.findBean(argument, qualifier).orElse(null); + return k; } - return resolutionContext.getBean(argument, qualifier); + K bean = resolutionContext.getBean(argument, qualifier); + return bean; } finally { if (previousPath != null) { resolutionContext.setConfigurationPath(previousPath); diff --git a/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java b/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java index cab98699d45..298d84b379b 100644 --- a/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java +++ b/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java @@ -1307,6 +1307,36 @@ public BeanPropertyRef(@NonNull Argument

argument, this.writeArgument = writeArgument == null && (setMethodIndex != -1 || withMethodIndex != -1) ? argument : writeArgument; this.readArgument = readArgument == null && (getMethodIndex != -1) ? argument : readArgument; } + + @UsedByGeneratedCode + public static BeanPropertyRef readWrite(@NonNull Argument argument, + int getMethodIndex, + int setMethodIndex, + int withMethodIndex, + boolean readyOnly, + boolean mutable) { + return new BeanPropertyRef<>(argument, argument, argument, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); + } + + @UsedByGeneratedCode + public static BeanPropertyRef readOnly(@NonNull Argument readArgument, + int getMethodIndex, + int setMethodIndex, + int withMethodIndex, + boolean readyOnly, + boolean mutable) { + return new BeanPropertyRef<>(readArgument, readArgument, null, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); + } + + @UsedByGeneratedCode + public static BeanPropertyRef writeOnly(@NonNull Argument writeArgument, + int getMethodIndex, + int setMethodIndex, + int withMethodIndex, + boolean readyOnly, + boolean mutable) { + return new BeanPropertyRef<>(writeArgument, null, writeArgument, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); + } } /** From b58f0569ff11ab77cb77b21fbc94fdf2229fde1f Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 14:10:50 +0100 Subject: [PATCH 02/13] Fix constants --- gradle/libs.versions.toml | 2 +- .../inject/annotation/AnnotationMetadataWriterSpec.groovy | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fdb2263673..76907d7a5aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ tomlj="1.1.1" vertx = "4.5.11" wiremock = "2.33.2" mimepull = "1.10.0" -sourcegen-bytecode-generator = "1.5.0" +sourcegen-bytecode-generator = "1.5.2" # # Versions which start with managed- are managed by Micronaut in the sense diff --git a/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy index 14ab2946334..6cc965d7466 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/annotation/AnnotationMetadataWriterSpec.groovy @@ -30,7 +30,6 @@ import io.micronaut.core.annotation.AnnotationValue import io.micronaut.core.annotation.TypeHint import io.micronaut.inject.BeanDefinition import io.micronaut.retry.annotation.Recoverable -import spock.lang.PendingFeature import spock.lang.Unroll import java.lang.annotation.Documented @@ -63,7 +62,6 @@ class Test { annotationMetadata.hasDeclaredAnnotation(Outer.Inner) } - @PendingFeature @Unroll void "test read/write annotation array type #type"() { given: From 18c8c17f871eb77ad67eb7614efc54b2a914b6cc Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 15:58:03 +0100 Subject: [PATCH 03/13] CR --- .../micronaut/aop/writer/AopProxyWriter.java | 16 ++-- .../inject/writer/BeanDefinitionVisitor.java | 8 -- .../inject/writer/BeanDefinitionWriter.java | 82 ++++++++++++------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java index 96e3b564e79..fef5b2c9a82 100644 --- a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java +++ b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java @@ -86,6 +86,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -206,6 +207,7 @@ public class AopProxyWriter implements ProxyingBeanDefinitionVisitor, ClassOutpu private static final String FIELD_BEAN_QUALIFIER = "$beanQualifier"; private static final String FIELD_PROXY_METHODS = "$proxyMethods"; private static final String FIELD_PROXY_BEAN_DEFINITION = "$proxyBeanDefinition"; + private static final ClassTypeDef METHOD_INTERCEPTOR_CHAIN_TYPE = ClassTypeDef.of(MethodInterceptorChain.class); private final String packageName; private final String targetClassShortName; @@ -481,11 +483,6 @@ public String getBeanTypeName() { return proxyBeanDefinitionWriter.getBeanTypeName(); } - @Override - public Type getProvidedType() { - return proxyBeanDefinitionWriter.getProvidedType(); - } - @Override public void setValidated(boolean validated) { proxyBeanDefinitionWriter.setValidated(validated); @@ -750,7 +747,7 @@ private void buildMethodOverride( ExpressionDef.InvokeInstanceMethod invocation; if (argumentCount > 0) { // invoke MethodInterceptorChain constructor with parameters - invocation = ClassTypeDef.of(MethodInterceptorChain.class).instantiate( + invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN, // 1st argument: interceptors @@ -764,7 +761,7 @@ private void buildMethodOverride( ).invoke(METHOD_PROCEED); } else { // invoke MethodInterceptorChain constructor without parameters - invocation = ClassTypeDef.of(MethodInterceptorChain.class).instantiate( + invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS, // 1st argument: interceptors @@ -794,11 +791,12 @@ public void visitBeanDefinitionEnd() { if (!isInterface) { proxyBuilder.superclass(targetType); } - List interfaces = new ArrayList<>(); - interfaceTypes.stream().map(TypeDef::erasure).forEach(interfaces::add); + List interfaces = new ArrayList<>(); + interfaceTypes.stream().map(typedElement -> (ClassTypeDef) TypeDef.erasure(typedElement)).forEach(interfaces::add); if (isInterface && implementInterface) { interfaces.add(targetType); } + interfaces.sort(Comparator.comparing(ClassTypeDef::getName)); interfaces.forEach(proxyBuilder::addSuperinterface); proxyBuilder.addAnnotation(Generated.class); diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java index f3657b1f5e8..3725db288c7 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionVisitor.java @@ -158,14 +158,6 @@ void visitDefaultConstructor( */ String getBeanTypeName(); - /** - * The provided type of the bean. Usually this is the same as {@link #getBeanTypeName()}, except in the case of - * factory beans which produce a different type. - * - * @return The provided type - */ - Type getProvidedType(); - /** * Make the bean definition as validated by jakarta.validation. * diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java index c5b66c15565..28c827d244e 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java @@ -168,6 +168,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -948,11 +949,6 @@ public String getBeanTypeName() { return beanFullClassName; } - @Override - public Type getProvidedType() { - throw new IllegalStateException("Not supported!"); - } - @Override public void setValidated(boolean validated) { if (validated) { @@ -1596,40 +1592,64 @@ private StatementDef buildInstance(VariableDef.This aThis, BuildMethodDefinition buildMethodDefinition, Function onBeanInstance, boolean isParametrized) { - Class constructorType; - if (constructor instanceof MethodElement) { - constructorType = AbstractInitializableBeanDefinition.MethodReference.class; - } else { - constructorType = AbstractInitializableBeanDefinition.FieldReference.class; - } + StatementDef.DefineAndAssign[] constructorDef= new StatementDef.DefineAndAssign[] { null }; + Supplier constructorDefSupplier = new Supplier() { + + @Override + public VariableDef get() { + if (constructorDef[0] == null) { + Class constructorType; + if (constructor instanceof MethodElement) { + constructorType = AbstractInitializableBeanDefinition.MethodReference.class; + } else { + constructorType = AbstractInitializableBeanDefinition.FieldReference.class; + } + constructorDef[0] = beanDefinitionTypeDef + .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) + .cast(constructorType) + .newLocal("constructorDef"); + } + return constructorDef[0].variable(); + } + }; if (buildMethodDefinition instanceof FactoryBuildMethodDefinition factoryBuildMethodDefinition) { if (factoryBuildMethodDefinition.parameters.length > 0) { - return beanDefinitionTypeDef - .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) - .cast(constructorType) - .newLocal("constructorDef", constructorMethodVar -> { - List values = getConstructorArgumentValues(aThis, methodParameters, List.of(buildMethodDefinition.getParameters()), isParametrized, constructorMethodVar); - return buildFactoryGet(aThis, methodParameters, onBeanInstance, factoryBuildMethodDefinition, values); - }); + List values = getConstructorArgumentValues(aThis, methodParameters, + List.of(buildMethodDefinition.getParameters()), isParametrized, constructorDefSupplier); + StatementDef statement = buildFactoryGet(aThis, methodParameters, onBeanInstance, factoryBuildMethodDefinition, values); + if (constructorDef[0] != null) { + return StatementDef.multi( + constructorDef[0], + statement + ); + } + return statement; } return buildFactoryGet(aThis, methodParameters, onBeanInstance, factoryBuildMethodDefinition, List.of()); } if (buildMethodDefinition instanceof ConstructorBuildMethodDefinition constructorBuildMethodDefinition) { if (constructorBuildMethodDefinition.constructor.hasParameters()) { - return beanDefinitionTypeDef - .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) - .cast(constructorType) - .newLocal("constructorDef", constructorMethodVar -> { - List values = getConstructorArgumentValues(aThis, methodParameters, List.of(buildMethodDefinition.getParameters()), isParametrized, constructorMethodVar); - return buildConstructorInstantiate(aThis, methodParameters, onBeanInstance, constructorBuildMethodDefinition, values); - }); + List values = getConstructorArgumentValues(aThis, methodParameters, + List.of(buildMethodDefinition.getParameters()), isParametrized, constructorDefSupplier); + StatementDef statement = buildConstructorInstantiate(aThis, methodParameters, onBeanInstance, constructorBuildMethodDefinition, values); + if (constructorDef[0] != null) { + return StatementDef.multi( + constructorDef[0], + statement + ); + } + return statement; } return buildConstructorInstantiate(aThis, methodParameters, onBeanInstance, constructorBuildMethodDefinition, List.of()); } throw new IllegalStateException("Unknown build method definition: " + buildMethodDefinition); } - private StatementDef buildConstructorInstantiate(VariableDef.This aThis, List methodParameters, Function onBeanInstance, ConstructorBuildMethodDefinition constructorBuildMethodDefinition, List values) { + private StatementDef buildConstructorInstantiate(VariableDef.This aThis, + List methodParameters, + Function onBeanInstance, + ConstructorBuildMethodDefinition constructorBuildMethodDefinition, + List values) { List parameters = List.of(constructorBuildMethodDefinition.constructor.getSuspendParameters()); if (isConstructorIntercepted(constructorBuildMethodDefinition.constructor)) { ClassTypeDef factoryInterceptor = createConstructorInterceptor(constructorBuildMethodDefinition); @@ -3909,12 +3929,12 @@ private List getConstructorArgumentValues(VariableDef.T List methodParameters, List parameters, boolean isParametrized, - VariableDef constructorMethodVar) { + Supplier constructorMethodVarSupplier) { List values = new ArrayList<>(); for (int i = 0; i < parameters.size(); i++) { ParameterElement parameter = parameters.get(i); values.add( - getConstructorArgument(aThis, methodParameters, parameter, i, isParametrized, constructorMethodVar) + getConstructorArgument(aThis, methodParameters, parameter, i, isParametrized, constructorMethodVarSupplier) ); } return values; @@ -3938,7 +3958,7 @@ private ExpressionDef getConstructorArgument(VariableDef.This aThis, ParameterElement parameter, int index, boolean isParametrized, - VariableDef constructorMethodVar) { + Supplier constructorMethodVarSupplier) { AnnotationMetadata annotationMetadata = parameter.getAnnotationMetadata(); if (isAnnotatedWithParameter(annotationMetadata) && isParametrized) { // load the args @@ -4011,12 +4031,12 @@ private ExpressionDef getConstructorArgument(VariableDef.This aThis, values.add(ExpressionDef.constant(index)); if (hasGenericType) { values.add( - resolveConstructorArgumentGenericType(parameter.getGenericType(), index, constructorMethodVar) + resolveConstructorArgumentGenericType(parameter.getGenericType(), index, constructorMethodVarSupplier.get()) ); } // push qualifier values.add( - getQualifier(parameter, resolveConstructorArgument(index, constructorMethodVar)) + getQualifier(parameter, resolveConstructorArgument(index, constructorMethodVarSupplier.get())) ); ExpressionDef result = aThis.superRef().invoke(methodToInvoke, values); if (isArray && hasGenericType) { From 87cac5729d505df638246516b33f95764d7094f6 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 15:59:51 +0100 Subject: [PATCH 04/13] CR --- .../inject/annotation/AnnotationMetadataWriter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java index 94ed7d17182..6ed12f86e0a 100644 --- a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java @@ -27,6 +27,7 @@ import io.micronaut.sourcegen.model.StatementDef; import io.micronaut.sourcegen.model.TypeDef; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -42,6 +43,8 @@ @Internal public class AnnotationMetadataWriter { + private static final Method GET_ANNOTATION_METADATA_METHOD = ReflectionUtils.getRequiredMethod(AnnotationMetadataProvider.class, "getAnnotationMetadata"); + /** * Create a new {@link AnnotationMetadataProvider} class that is including the annotation metadata. * @@ -61,9 +64,8 @@ public static byte[] write(String className, AnnotationMetadata annotationMetada .addSuperinterface(TypeDef.of(AnnotationMetadataProvider.class)) .addField(annotationMetadataField) .addStaticInitializer(StatementDef.multi(statements)) - .addMethod(MethodDef.override( - ReflectionUtils.getRequiredMethod(AnnotationMetadataProvider.class, "getAnnotationMetadata") - ).build((aThis, methodParameters) -> aThis.type().getStaticField(annotationMetadataField).returning()) + .addMethod(MethodDef.override(GET_ANNOTATION_METADATA_METHOD) + .build((aThis, methodParameters) -> aThis.type().getStaticField(annotationMetadataField).returning()) ); loadTypeMethods.values().forEach(classDefBuilder::addMethod); ClassDef classDef = classDefBuilder.build(); From e46a1a0a632327964e6121f6a94a8688d9dddd76 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 16:00:47 +0100 Subject: [PATCH 05/13] CR --- .../micronaut/inject/annotation/AnnotationMetadataWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java index 6ed12f86e0a..063da111a27 100644 --- a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java @@ -29,7 +29,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -53,7 +53,7 @@ public class AnnotationMetadataWriter { * @return The generated bytecode */ public static byte[] write(String className, AnnotationMetadata annotationMetadata) { - Map loadTypeMethods = new HashMap<>(); + Map loadTypeMethods = new LinkedHashMap<>(); ClassTypeDef type = ClassTypeDef.of(className + AnnotationMetadata.CLASS_NAME_SUFFIX); FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(type, annotationMetadata, loadTypeMethods); From 26298f35778a07f774bb928f668ab1e02f93d090 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 16:49:16 +0100 Subject: [PATCH 06/13] CR --- .../visitor/BeanIntrospectionWriter.java | 155 +++++------------- ...bstractInitializableBeanIntrospection.java | 30 ---- 2 files changed, 44 insertions(+), 141 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java index f53a65ac625..c5221a6ffaf 100644 --- a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java @@ -160,39 +160,6 @@ final class BeanIntrospectionWriter implements OriginatingElements, ClassOutputW boolean.class ); - private static final java.lang.reflect.Method BEAN_PROPERTY_REF_READ_ONLY_STATIC = ReflectionUtils.getRequiredMethod( - AbstractInitializableBeanIntrospection.BeanPropertyRef.class, - "readOnly", - Argument.class, - int.class, - int.class, - int.class, - boolean.class, - boolean.class - ); - - private static final java.lang.reflect.Method BEAN_PROPERTY_REF_WRITE_ONLY_STATIC = ReflectionUtils.getRequiredMethod( - AbstractInitializableBeanIntrospection.BeanPropertyRef.class, - "writeOnly", - Argument.class, - int.class, - int.class, - int.class, - boolean.class, - boolean.class - ); - - private static final java.lang.reflect.Method BEAN_PROPERTY_REF_READ_WRITE_STATIC = ReflectionUtils.getRequiredMethod( - AbstractInitializableBeanIntrospection.BeanPropertyRef.class, - "readWrite", - Argument.class, - int.class, - int.class, - int.class, - boolean.class, - boolean.class - ); - private static final java.lang.reflect.Method INSTANTIATE_METHOD = ReflectionUtils.getRequiredMethod( AbstractInitializableBeanIntrospection.class, "instantiate" @@ -444,95 +411,57 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE } } - private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyData, Map loadTypeMethods) { + private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyData, List staticStatements, Map loadTypeMethods) { ClassTypeDef beanPropertyRefDef = ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class); boolean mutable = !beanPropertyData.isReadOnly || hasAssociatedConstructorArgument(beanPropertyData.name, beanPropertyData.type); - if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.type.equals(beanPropertyData.writeType)) { - return beanPropertyRefDef.invokeStatic( - BEAN_PROPERTY_REF_READ_WRITE_STATIC, + StatementDef.DefineAndAssign defineAndAssign = ArgumentExpUtils.pushCreateArgument( + annotationMetadata, + beanClassElement, + introspectionTypeDef, + beanPropertyData.name, + beanPropertyData.type, + loadTypeMethods + ).newLocal(beanPropertyData.name + "Arg"); - ArgumentExpUtils.pushCreateArgument( - annotationMetadata, - beanClassElement, - introspectionTypeDef, - beanPropertyData.name, - beanPropertyData.type, - loadTypeMethods - ), - ExpressionDef.constant(beanPropertyData.getDispatchIndex), - ExpressionDef.constant(beanPropertyData.setDispatchIndex), - ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), - ExpressionDef.constant(beanPropertyData.isReadOnly), - ExpressionDef.constant(mutable) - ); - } - if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.writeType == null) { - return beanPropertyRefDef.invokeStatic( - BEAN_PROPERTY_REF_READ_ONLY_STATIC, + staticStatements.add(defineAndAssign); - ArgumentExpUtils.pushCreateArgument( - annotationMetadata, - beanClassElement, - introspectionTypeDef, - beanPropertyData.name, - beanPropertyData.type, - loadTypeMethods - ), - ExpressionDef.constant(beanPropertyData.getDispatchIndex), - ExpressionDef.constant(beanPropertyData.setDispatchIndex), - ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), - ExpressionDef.constant(beanPropertyData.isReadOnly), - ExpressionDef.constant(mutable) - ); - } - if (beanPropertyData.type.equals(beanPropertyData.writeType) && beanPropertyData.readType == null) { - return beanPropertyRefDef.invokeStatic( - BEAN_PROPERTY_REF_WRITE_ONLY_STATIC, - - ArgumentExpUtils.pushCreateArgument( - annotationMetadata, - beanClassElement, - introspectionTypeDef, - beanPropertyData.name, - beanPropertyData.type, - loadTypeMethods - ), - ExpressionDef.constant(beanPropertyData.getDispatchIndex), - ExpressionDef.constant(beanPropertyData.setDispatchIndex), - ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), - ExpressionDef.constant(beanPropertyData.isReadOnly), - ExpressionDef.constant(mutable) - ); - } - return beanPropertyRefDef.instantiate( - BEAN_PROPERTY_REF_CONSTRUCTOR, + VariableDef mainArgument = defineAndAssign.variable(); + ExpressionDef readArgument = null; + ExpressionDef writeArgument = null; - ArgumentExpUtils.pushCreateArgument( - annotationMetadata, - beanClassElement, - introspectionTypeDef, - beanPropertyData.name, - beanPropertyData.type, - loadTypeMethods - ), - beanPropertyData.readType == null ? ExpressionDef.nullValue() : ArgumentExpUtils.pushCreateArgument( + if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.type.equals(beanPropertyData.writeType)) { + readArgument = mainArgument; + writeArgument = mainArgument; + } else if (beanPropertyData.type.equals(beanPropertyData.readType) && beanPropertyData.writeType == null) { + readArgument = mainArgument; + } else if (beanPropertyData.type.equals(beanPropertyData.writeType) && beanPropertyData.readType == null) { + writeArgument = mainArgument; + } else { + readArgument = beanPropertyData.readType == null ? null : ArgumentExpUtils.pushCreateArgument( annotationMetadata, beanClassElement, introspectionTypeDef, beanPropertyData.name, beanPropertyData.readType, loadTypeMethods - ), - beanPropertyData.writeType == null ? ExpressionDef.nullValue() : ArgumentExpUtils.pushCreateArgument( + ); + writeArgument = beanPropertyData.writeType == null ? null : ArgumentExpUtils.pushCreateArgument( annotationMetadata, beanClassElement, introspectionTypeDef, beanPropertyData.name, beanPropertyData.writeType, loadTypeMethods - ), + ); + } + return beanPropertyRefDef.instantiate( + BEAN_PROPERTY_REF_CONSTRUCTOR, + + mainArgument, + readArgument == null ? ExpressionDef.nullValue() : readArgument, + writeArgument == null ? ExpressionDef.nullValue() : writeArgument, ExpressionDef.constant(beanPropertyData.getDispatchIndex), ExpressionDef.constant(beanPropertyData.setDispatchIndex), ExpressionDef.constant(beanPropertyData.withMethodDispatchIndex), @@ -604,6 +533,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV Map loadTypeMethods = new LinkedHashMap<>(); + ClassTypeDef thisType = ClassTypeDef.of(introspectionName); ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(introspectionName).addModifiers(Modifier.FINAL, Modifier.PUBLIC); classDefBuilder.superclass(isEnum ? ClassTypeDef.of(AbstractEnumBeanIntrospectionAndReference.class) : ClassTypeDef.of(AbstractInitializableBeanIntrospectionAndReference.class)); @@ -619,6 +549,8 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV FieldDef beanMethodsField; FieldDef enumsField; + List staticStatements = new ArrayList<>(); + if (constructor != null) { if (!constructor.getAnnotationMetadata().isEmpty()) { constructorAnnotationMetadataField = FieldDef.builder(FIELD_CONSTRUCTOR_ANNOTATION_METADATA, AnnotationMetadata.class) @@ -657,16 +589,18 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV if (!beanProperties.isEmpty()) { beanPropertiesField = FieldDef.builder(FIELD_BEAN_PROPERTIES_REFERENCES, AbstractInitializableBeanIntrospection.BeanPropertyRef[].class) .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) - .initializer( + .build(); + classDefBuilder.addField(beanPropertiesField); + staticStatements.add( + thisType.getStaticField(beanPropertiesField).put( ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class).array() .instantiate( beanProperties.stream() - .map(e -> pushBeanPropertyReference(e, loadTypeMethods)) + .map(e -> pushBeanPropertyReference(e, staticStatements, loadTypeMethods)) .toList() ) ) - .build(); - classDefBuilder.addField(beanPropertiesField); + ); } else { beanPropertiesField = null; } @@ -726,8 +660,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV annotationIndexFields.put(annotationName, field); } - List statements = new ArrayList<>(); - AnnotationMetadataGenUtils.writeAnnotationDefault(statements, introspectionTypeDef, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.writeAnnotationDefault(staticStatements, introspectionTypeDef, annotationMetadata, loadTypeMethods); FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(introspectionTypeDef, annotationMetadata, loadTypeMethods); if (annotationMetadataField != null) { @@ -736,8 +669,8 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV ); } - if (!statements.isEmpty()) { - classDefBuilder.addStaticInitializer(StatementDef.multi(statements)); + if (!staticStatements.isEmpty()) { + classDefBuilder.addStaticInitializer(StatementDef.multi(staticStatements)); } classDefBuilder.addMethod( diff --git a/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java b/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java index 298d84b379b..cab98699d45 100644 --- a/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java +++ b/inject/src/main/java/io/micronaut/inject/beans/AbstractInitializableBeanIntrospection.java @@ -1307,36 +1307,6 @@ public BeanPropertyRef(@NonNull Argument

argument, this.writeArgument = writeArgument == null && (setMethodIndex != -1 || withMethodIndex != -1) ? argument : writeArgument; this.readArgument = readArgument == null && (getMethodIndex != -1) ? argument : readArgument; } - - @UsedByGeneratedCode - public static BeanPropertyRef readWrite(@NonNull Argument argument, - int getMethodIndex, - int setMethodIndex, - int withMethodIndex, - boolean readyOnly, - boolean mutable) { - return new BeanPropertyRef<>(argument, argument, argument, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); - } - - @UsedByGeneratedCode - public static BeanPropertyRef readOnly(@NonNull Argument readArgument, - int getMethodIndex, - int setMethodIndex, - int withMethodIndex, - boolean readyOnly, - boolean mutable) { - return new BeanPropertyRef<>(readArgument, readArgument, null, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); - } - - @UsedByGeneratedCode - public static BeanPropertyRef writeOnly(@NonNull Argument writeArgument, - int getMethodIndex, - int setMethodIndex, - int withMethodIndex, - boolean readyOnly, - boolean mutable) { - return new BeanPropertyRef<>(writeArgument, null, writeArgument, getMethodIndex, setMethodIndex, withMethodIndex, readyOnly, mutable); - } } /** From f34eacaf6c07840ba4ac75a7f7488d5f95d1dbdc Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 4 Dec 2024 19:12:35 +0100 Subject: [PATCH 07/13] Improvements --- .../micronaut/aop/writer/AopProxyWriter.java | 134 ++++++++---------- .../inject/writer/BeanDefinitionWriter.java | 85 ++++++----- 2 files changed, 99 insertions(+), 120 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java index fef5b2c9a82..82c35a85b58 100644 --- a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java +++ b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java @@ -55,7 +55,6 @@ import io.micronaut.inject.ast.PrimitiveElement; import io.micronaut.inject.ast.TypedElement; import io.micronaut.inject.configuration.ConfigurationMetadataBuilder; -import io.micronaut.inject.processing.JavaModelUtils; import io.micronaut.inject.qualifiers.Qualified; import io.micronaut.inject.visitor.VisitorContext; import io.micronaut.inject.writer.ArgumentExpUtils; @@ -63,15 +62,16 @@ import io.micronaut.inject.writer.ClassOutputWriter; import io.micronaut.inject.writer.ClassWriterOutputVisitor; import io.micronaut.inject.writer.ExecutableMethodsDefinitionWriter; +import io.micronaut.inject.writer.MethodGenUtils; import io.micronaut.inject.writer.OriginatingElements; import io.micronaut.inject.writer.ProxyingBeanDefinitionVisitor; -import io.micronaut.inject.writer.MethodGenUtils; import io.micronaut.sourcegen.bytecode.ByteCodeWriter; import io.micronaut.sourcegen.model.ClassDef; import io.micronaut.sourcegen.model.ClassTypeDef; import io.micronaut.sourcegen.model.ExpressionDef; import io.micronaut.sourcegen.model.FieldDef; import io.micronaut.sourcegen.model.MethodDef; +import io.micronaut.sourcegen.model.ParameterDef; import io.micronaut.sourcegen.model.StatementDef; import io.micronaut.sourcegen.model.TypeDef; import io.micronaut.sourcegen.model.VariableDef; @@ -623,10 +623,7 @@ public String getBeanDefinitionReferenceClassName() { */ public void visitIntroductionMethod(TypedElement declaringBean, MethodElement methodElement) { - visitAroundMethod( - declaringBean, - methodElement - ); + visitAroundMethod(declaringBean, methodElement); } /** @@ -638,11 +635,6 @@ public void visitIntroductionMethod(TypedElement declaringBean, public void visitAroundMethod(TypedElement beanType, MethodElement methodElement) { - ClassElement returnType = methodElement.isSuspend() ? ClassElement.of(Object.class) : methodElement.getReturnType(); - Type returnTypeObject = JavaModelUtils.getTypeReference(returnType); - boolean isPrimitive = returnType.isPrimitive(); - boolean isVoidReturn = isPrimitive && returnTypeObject.equals(Type.VOID_TYPE); - final Optional overridden = methodElement.getOwningType() .getEnclosedElement(ElementQuery.ALL_METHODS .onlyInstance() @@ -671,8 +663,8 @@ public void visitAroundMethod(TypedElement beanType, String methodName = methodElement.getName(); List argumentTypeList = Arrays.asList(methodElement.getSuspendParameters()); - int argumentCount = argumentTypeList.size(); - MethodRef methodKey = new MethodRef(methodName, argumentTypeList, returnTypeObject); + ClassElement returnType = methodElement.isSuspend() ? ClassElement.of(Object.class) : methodElement.getReturnType(); + MethodRef methodKey = new MethodRef(methodName, argumentTypeList, TypeDef.erasure(returnType)); if (!proxiedMethodsRefSet.contains(methodKey)) { @@ -687,13 +679,12 @@ public void visitAroundMethod(TypedElement beanType, interceptedProxyBridgeMethodName = "$$access$$" + methodName; // now build a bridge to invoke the original method - ClassTypeDef declaringTypeDef = (ClassTypeDef) TypeDef.erasure(methodElement.getOwningType()); proxyBuilder.addMethod( MethodDef.builder(interceptedProxyBridgeMethodName) .addModifiers(Modifier.PUBLIC) - .addParameters(argumentTypeList.stream().map(p -> TypeDef.erasure(p.getType())).toList()) + .addParameters(argumentTypeList.stream().map(p -> ParameterDef.of(p.getName(), TypeDef.erasure(p.getType()))).toList()) .returns(TypeDef.erasure(returnType)) - .build((aThis, methodParameters) -> aThis.superRef(declaringTypeDef) + .build((aThis, methodParameters) -> aThis.superRef((ClassTypeDef) TypeDef.erasure(methodElement.getOwningType())) .invoke(methodElement, methodParameters).returning()) ); } @@ -713,72 +704,61 @@ public void visitAroundMethod(TypedElement beanType, proxiedMethodsRefSet.add(methodKey); proxyTargetMethods.add(methodKey); - buildMethodOverride(returnType, methodName, index, argumentTypeList, argumentCount, isVoidReturn); + proxyBuilder.addMethod( + buildMethodOverride(methodElement, index) + ); } } - private void buildMethodOverride( - TypedElement returnType, - String methodName, - int index, - List argumentTypeList, - int argumentCount, - boolean isVoidReturn) { - // override the original method - proxyBuilder.addMethod( - MethodDef.builder(methodName) - .addModifiers(Modifier.PUBLIC) - .overrides() - .returns(TypeDef.erasure(returnType)) - .addParameters(argumentTypeList.stream().map(p -> TypeDef.erasure(p.getType())).toList()) - .build((aThis, methodParameters) -> { - - ExpressionDef targetArgument; - if (isProxyTarget) { - if (hotswap || lazy) { - targetArgument = aThis.invoke(METHOD_INTERCEPTED_TARGET); - } else { - targetArgument = aThis.field(targetField); - } - } else { - targetArgument = aThis; - } + private MethodDef buildMethodOverride(MethodElement methodElement, int index) { + return MethodDef.override(methodElement) + .build((aThis, methodParameters) -> { - ExpressionDef.InvokeInstanceMethod invocation; - if (argumentCount > 0) { - // invoke MethodInterceptorChain constructor with parameters - invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( - CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN, - - // 1st argument: interceptors - aThis.field(interceptorsField).arrayElement(index), - // 2nd argument: this or target - targetArgument, - // 3rd argument: the executable method - aThis.field(proxyMethodsField).arrayElement(index), - // 4th argument: array of the argument values - TypeDef.OBJECT.array().instantiate(methodParameters) - ).invoke(METHOD_PROCEED); + ExpressionDef targetArgument; + if (isProxyTarget) { + if (hotswap || lazy) { + targetArgument = aThis.invoke(METHOD_INTERCEPTED_TARGET); } else { - // invoke MethodInterceptorChain constructor without parameters - invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( - CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS, - - // 1st argument: interceptors - aThis.field(interceptorsField).arrayElement(index), - // 2nd argument: this or target - targetArgument, - // 3rd argument: the executable method - aThis.field(proxyMethodsField).arrayElement(index) - // fourth argument: array of the argument values - ).invoke(METHOD_PROCEED); - } - if (isVoidReturn) { - return invocation; + targetArgument = aThis.field(targetField); } + } else { + targetArgument = aThis; + } + + ExpressionDef.InvokeInstanceMethod invocation; + if (methodParameters.isEmpty()) { + // invoke MethodInterceptorChain constructor without parameters + invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( + CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN_NO_PARAMS, + + // 1st argument: interceptors + aThis.field(interceptorsField).arrayElement(index), + // 2nd argument: this or target + targetArgument, + // 3rd argument: the executable method + aThis.field(proxyMethodsField).arrayElement(index) + // fourth argument: array of the argument values + ).invoke(METHOD_PROCEED); + } else { + // invoke MethodInterceptorChain constructor with parameters + invocation = METHOD_INTERCEPTOR_CHAIN_TYPE.instantiate( + CONSTRUCTOR_METHOD_INTERCEPTOR_CHAIN, + + // 1st argument: interceptors + aThis.field(interceptorsField).arrayElement(index), + // 2nd argument: this or target + targetArgument, + // 3rd argument: the executable method + aThis.field(proxyMethodsField).arrayElement(index), + // 4th argument: array of the argument values + TypeDef.OBJECT.array().instantiate(methodParameters) + ).invoke(METHOD_PROCEED); + } + if (!methodElement.getReturnType().isVoid() || methodElement.isSuspend()) { return invocation.returning(); - }) - ); + } + return invocation; + }); } /** @@ -1528,10 +1508,10 @@ private static final class MethodRef { private final String name; private final List argumentTypes; private final List genericArgumentTypes; - private final Type returnType; + private final TypeDef returnType; private final List rawTypes; - public MethodRef(String name, List parameterElements, Type returnType) { + public MethodRef(String name, List parameterElements, TypeDef returnType) { this.name = name; this.argumentTypes = parameterElements.stream().map(ParameterElement::getType).toList(); this.genericArgumentTypes = parameterElements.stream().map(ParameterElement::getGenericType).toList(); diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java index 28c827d244e..ebe518dc6c7 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java @@ -1889,8 +1889,6 @@ private ClassTypeDef createFactoryInterceptor(FactoryBuildMethodDefinition facto private StaticBlock getStaticInitializer() { List statements = new ArrayList<>(); - AnnotationMetadataGenUtils.writeAnnotationDefault(statements, beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); classDefBuilder.addField(annotationMetadataField); @@ -3115,26 +3113,30 @@ private boolean isMultiValueProperty(ClassElement type) { } private ExpressionDef getQualifier(Element element, ExpressionDef argumentExpression) { + return getQualifier(element, () -> argumentExpression); + } + + private ExpressionDef getQualifier(Element element, Supplier argumentExpressionSupplier) { final List qualifierNames = element.getAnnotationNamesByStereotype(AnnotationUtil.QUALIFIER); if (!qualifierNames.isEmpty()) { if (qualifierNames.size() == 1) { // simple qualifier final String annotationName = qualifierNames.iterator().next(); - return getQualifierForAnnotation(element, annotationName, argumentExpression); + return getQualifierForAnnotation(element, annotationName, argumentExpressionSupplier.get()); } // composite qualifier return TYPE_QUALIFIERS.invokeStatic( METHOD_QUALIFIER_BY_QUALIFIERS, TYPE_QUALIFIER.array().instantiate( - qualifierNames.stream().map(name -> getQualifierForAnnotation(element, name, argumentExpression)).toList() + qualifierNames.stream().map(name -> getQualifierForAnnotation(element, name, argumentExpressionSupplier.get())).toList() ) ); } if (element.hasAnnotation(AnnotationUtil.ANN_INTERCEPTOR_BINDING_QUALIFIER)) { return TYPE_QUALIFIERS.invokeStatic( METHOD_QUALIFIER_BY_INTERCEPTOR_BINDING, - getAnnotationMetadataFromProvider(argumentExpression) + getAnnotationMetadataFromProvider(argumentExpressionSupplier.get()) ); } String[] byType = element.hasDeclaredAnnotation(io.micronaut.context.annotation.Type.NAME) ? element.stringValues(io.micronaut.context.annotation.Type.NAME) : null; @@ -3981,47 +3983,44 @@ private ExpressionDef getConstructorArgument(VariableDef.This aThis, Optional property = parameter.stringValue(Property.class, "name"); if (property.isPresent()) { return getInvokeGetPropertyValueForConstructor(aThis, methodParameters, index, parameter, property.get()); - } else { - if (parameter.getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { - return getInvokeGetEvaluatedExpressionValueForConstructorArgument(aThis, index, parameter); - } else { - Optional valueValue = parameter.stringValue(Value.class); - if (valueValue.isPresent()) { - return getInvokeGetPropertyPlaceholderValueForConstructor(aThis, methodParameters, index, parameter, valueValue.get()); - } - } + } + if (parameter.getValue(Value.class, EvaluatedExpressionReference.class).isPresent()) { + return getInvokeGetEvaluatedExpressionValueForConstructorArgument(aThis, index, parameter); + } + Optional valueValue = parameter.stringValue(Value.class); + if (valueValue.isPresent()) { + return getInvokeGetPropertyPlaceholderValueForConstructor(aThis, methodParameters, index, parameter, valueValue.get()); } return ExpressionDef.nullValue(); - } else { - isArray = genericType.isArray(); - if (genericType.isAssignable(Collection.class) || isArray) { - hasGenericType = true; - ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); - if (typeArgument != null && !typeArgument.isPrimitive()) { - if (typeArgument.isAssignable(BeanRegistration.class)) { - methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_CONSTRUCTOR_ARGUMENT; - } else { - methodToInvoke = GET_BEANS_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } + } + isArray = genericType.isArray(); + if (genericType.isAssignable(Collection.class) || isArray) { + hasGenericType = true; + ClassElement typeArgument = genericType.isArray() ? genericType.fromArray() : genericType.getFirstTypeArgument().orElse(null); + if (typeArgument != null && !typeArgument.isPrimitive()) { + if (typeArgument.isAssignable(BeanRegistration.class)) { + methodToInvoke = GET_BEAN_REGISTRATIONS_FOR_CONSTRUCTOR_ARGUMENT; } else { - methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; - hasGenericType = false; + methodToInvoke = GET_BEANS_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; } - } else if (isInjectableMap(genericType)) { - hasGenericType = true; - methodToInvoke = GET_MAP_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(Stream.class)) { - hasGenericType = true; - methodToInvoke = GET_STREAM_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(Optional.class)) { - hasGenericType = true; - methodToInvoke = FIND_BEAN_FOR_CONSTRUCTOR_ARGUMENT; - } else if (genericType.isAssignable(BeanRegistration.class)) { - hasGenericType = true; - methodToInvoke = GET_BEAN_REGISTRATION_FOR_CONSTRUCTOR_ARGUMENT; } else { methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; + hasGenericType = false; } + } else if (isInjectableMap(genericType)) { + hasGenericType = true; + methodToInvoke = GET_MAP_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(Stream.class)) { + hasGenericType = true; + methodToInvoke = GET_STREAM_OF_TYPE_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(Optional.class)) { + hasGenericType = true; + methodToInvoke = FIND_BEAN_FOR_CONSTRUCTOR_ARGUMENT; + } else if (genericType.isAssignable(BeanRegistration.class)) { + hasGenericType = true; + methodToInvoke = GET_BEAN_REGISTRATION_FOR_CONSTRUCTOR_ARGUMENT; + } else { + methodToInvoke = GET_BEAN_FOR_CONSTRUCTOR_ARGUMENT; } List values = new ArrayList<>(); // load the first two arguments of the method (the BeanResolutionContext and the BeanContext) to be passed to the method @@ -4031,12 +4030,12 @@ private ExpressionDef getConstructorArgument(VariableDef.This aThis, values.add(ExpressionDef.constant(index)); if (hasGenericType) { values.add( - resolveConstructorArgumentGenericType(parameter.getGenericType(), index, constructorMethodVarSupplier.get()) + resolveConstructorArgumentGenericType(parameter.getGenericType(), index, constructorMethodVarSupplier) ); } // push qualifier values.add( - getQualifier(parameter, resolveConstructorArgument(index, constructorMethodVarSupplier.get())) + getQualifier(parameter, () -> resolveConstructorArgument(index, constructorMethodVarSupplier.get())) ); ExpressionDef result = aThis.superRef().invoke(methodToInvoke, values); if (isArray && hasGenericType) { @@ -4091,12 +4090,12 @@ private ExpressionDef getInvokeGetEvaluatedExpressionValueForConstructorArgument .cast(TypeDef.erasure(entry.getType())); } - private ExpressionDef resolveConstructorArgumentGenericType(ClassElement type, int argumentIndex, VariableDef constructorMethodVar) { + private ExpressionDef resolveConstructorArgumentGenericType(ClassElement type, int argumentIndex, Supplier constructorMethodVarSupplier) { ExpressionDef expressionDef = resolveArgumentGenericType(type); if (expressionDef != null) { return expressionDef; } - ExpressionDef argumentExpression = resolveConstructorArgument(argumentIndex, constructorMethodVar); + ExpressionDef argumentExpression = resolveConstructorArgument(argumentIndex, constructorMethodVarSupplier.get()); if (type.isAssignable(Map.class)) { argumentExpression = resolveSecondTypeArgument(argumentExpression); } else { From 9629bec3346a65ad315a3bc80269b2d835955c95 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Thu, 5 Dec 2024 14:58:06 +0100 Subject: [PATCH 08/13] Improvements --- .../io/micronaut/inject/writer/BeanDefinitionWriter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java index ebe518dc6c7..5cf809175c4 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java @@ -1125,7 +1125,7 @@ public void visitBeanDefinitionEnd() { classDefBuilder.addMethod( MethodDef.override( LOAD_REFERENCE_METHOD - ).build((aThis, methodParameters) -> beanDefinitionTypeDef.instantiate().returning()) + ).build((aThis, methodParameters) -> aThis.type().instantiate().returning()) ); if (annotationMetadata.hasDeclaredAnnotation(Context.class)) { @@ -1604,7 +1604,7 @@ public VariableDef get() { } else { constructorType = AbstractInitializableBeanDefinition.FieldReference.class; } - constructorDef[0] = beanDefinitionTypeDef + constructorDef[0] = aThis.type() .getStaticField(FIELD_CONSTRUCTOR, ClassTypeDef.of(AbstractInitializableBeanDefinition.MethodOrFieldReference.class)) .cast(constructorType) .newLocal("constructorDef"); @@ -2286,7 +2286,7 @@ private StatementDef addInnerConfigurationMethod() { classDefBuilder.addMethod( MethodDef.override(IS_INNER_CONFIGURATION_METHOD) - .build((aThis, methodParameters) -> beanDefinitionTypeDef.getStaticField(innerClassesField) + .build((aThis, methodParameters) -> aThis.type().getStaticField(innerClassesField) .invoke(CONTAINS_METHOD, methodParameters.get(0)) .returning()) ); @@ -2310,7 +2310,7 @@ private StatementDef addGetExposedTypes() { classDefBuilder.addMethod( MethodDef.override(GET_EXPOSED_TYPES_METHOD) - .build((aThis, methodParameters) -> beanDefinitionTypeDef.getStaticField(exposedTypesField).returning()) + .build((aThis, methodParameters) -> aThis.type().getStaticField(exposedTypesField).returning()) ); return beanDefinitionTypeDef.getStaticField(exposedTypesField).put(getClassesAsSetExpression(exposedTypes)); From f084bd06954a07732e1b158c689d38ec8fa34832 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Thu, 5 Dec 2024 15:21:24 +0100 Subject: [PATCH 09/13] Bytecode generator 1.5.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 76907d7a5aa..3e67523b3fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ tomlj="1.1.1" vertx = "4.5.11" wiremock = "2.33.2" mimepull = "1.10.0" -sourcegen-bytecode-generator = "1.5.2" +sourcegen-bytecode-generator = "1.5.3" # # Versions which start with managed- are managed by Micronaut in the sense From f7c6dbbaa0b6f517eda6463f9e2cfb4328186f07 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Mon, 6 Jan 2025 10:23:53 +0200 Subject: [PATCH 10/13] Fix type-pollution test --- .../java/io/micronaut/http/server/stack/SearchController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/jmh/java/io/micronaut/http/server/stack/SearchController.java b/benchmarks/src/jmh/java/io/micronaut/http/server/stack/SearchController.java index 03855e9bf24..0461235202e 100644 --- a/benchmarks/src/jmh/java/io/micronaut/http/server/stack/SearchController.java +++ b/benchmarks/src/jmh/java/io/micronaut/http/server/stack/SearchController.java @@ -8,6 +8,7 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; +import java.util.ArrayList; import java.util.List; @Controller("/search") @@ -30,7 +31,7 @@ private static MutableHttpResponse find(List haystack, String ne } @Introspected - record Input(List haystack, String needle) { + record Input(ArrayList haystack, String needle) { } @Introspected From 555e5c60d8573bcd90e91d418bb8f7c5268944c1 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 8 Jan 2025 14:01:18 +0200 Subject: [PATCH 11/13] Bump sourcegen --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e67523b3fa..a1d89e5fd23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ tomlj="1.1.1" vertx = "4.5.11" wiremock = "2.33.2" mimepull = "1.10.0" -sourcegen-bytecode-generator = "1.5.3" +sourcegen-bytecode-generator = "1.5.4" # # Versions which start with managed- are managed by Micronaut in the sense From 0965f40c69ecf9eabdaf0f47316f65dfb0f265b2 Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 8 Jan 2025 14:08:02 +0200 Subject: [PATCH 12/13] Correct GraalReflectionMetadataWriter --- .../micronaut/graal/reflect/GraalReflectionMetadataWriter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java index 86cab941c81..29b615e7180 100644 --- a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java +++ b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java @@ -30,6 +30,7 @@ import io.micronaut.sourcegen.model.MethodDef; import io.micronaut.sourcegen.model.StatementDef; +import javax.lang.model.element.Modifier; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -67,6 +68,7 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE ClassTypeDef thisType = ClassTypeDef.of(className); try (OutputStream outputStream = classWriterOutputVisitor.visitClass(className, originatingElement)) { ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(className) + .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", GraalReflectionConfigurer.class.getName()).build()) .addSuperinterface(ClassTypeDef.of(GraalReflectionConfigurer.class)) .addMethod(getAnnotationMetadataMethodDef(thisType, annotationMetadata)); From d8a3402e4209f8fd05ce72a093837be8ec566bea Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Wed, 8 Jan 2025 17:59:46 +0200 Subject: [PATCH 13/13] CR --- .../micronaut/aop/writer/AopProxyWriter.java | 9 +- .../AnnotationMetadataGenUtils.java | 457 ++++++++++-------- .../annotation/AnnotationMetadataWriter.java | 7 +- .../visitor/BeanIntrospectionWriter.java | 54 +-- .../inject/writer/ArgumentExpUtils.java | 57 ++- .../writer/BeanConfigurationWriter.java | 16 +- .../inject/writer/BeanDefinitionWriter.java | 36 +- .../ExecutableMethodsDefinitionWriter.java | 31 +- .../{ExpressionsUtils.java => GenUtils.java} | 5 +- .../inject/writer/MethodGenUtils.java | 51 +- .../core/reflect/ReflectionUtils.java | 1 + .../GraalReflectionMetadataWriter.java | 20 +- 12 files changed, 397 insertions(+), 347 deletions(-) rename core-processor/src/main/java/io/micronaut/inject/writer/{ExpressionsUtils.java => GenUtils.java} (99%) diff --git a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java index 82c35a85b58..5f6bbd489cb 100644 --- a/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java +++ b/core-processor/src/main/java/io/micronaut/aop/writer/AopProxyWriter.java @@ -94,6 +94,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -982,7 +983,7 @@ private StatementDef initializeProxyMethodsAndInterceptors(VariableDef.This aThi } else { executableMethodsInstance = executableMethodsType.instantiate(); } - int[] index = {0}; + AtomicInteger index = new AtomicInteger(); return executableMethodsInstance.newLocal("executableMethods", executableMethodsVar -> StatementDef.multi( aThis.field(proxyMethodsField).assign( ClassTypeDef.of(ExecutableMethod.class).array().instantiate( @@ -1008,7 +1009,7 @@ private StatementDef initializeProxyMethodsAndInterceptors(VariableDef.This aThi // First argument. The interceptor registry parameters.get(interceptorRegistryArgumentIndex), // Second argument i.e. proxyMethods[0] - aThis.field(proxyMethodsField).arrayElement(index[0]++), + aThis.field(proxyMethodsField).arrayElement(index.getAndIncrement()), // Third argument i.e. interceptors parameters.get(interceptorsListArgumentIndex) ); @@ -1025,7 +1026,7 @@ private StatementDef initializeProxyTargetMethodsAndInterceptors(VariableDef.Thi if (proxiedMethods.size() != proxyMethodCount) { throw new IllegalStateException("Expected proxy methods count to match actual methods"); } - int[] index = {0}; + AtomicInteger index = new AtomicInteger(); return StatementDef.multi( aThis.field(proxyMethodsField).assign( ClassTypeDef.of(ExecutableMethod.class).array().instantiate( @@ -1050,7 +1051,7 @@ private StatementDef initializeProxyTargetMethodsAndInterceptors(VariableDef.Thi // First argument. The interceptor registry parameters.get(interceptorRegistryArgumentIndex), // Second argument i.e. proxyMethods[0] - aThis.field(proxyMethodsField).arrayElement(index[0]++), + aThis.field(proxyMethodsField).arrayElement(index.getAndIncrement()), // Third argument i.e. interceptors parameters.get(interceptorsListArgumentIndex) ) diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java index 5fcca663499..206785467a3 100644 --- a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataGenUtils.java @@ -26,7 +26,7 @@ import io.micronaut.core.expressions.EvaluatedExpressionReference; import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.util.CollectionUtils; -import io.micronaut.inject.writer.ExpressionsUtils; +import io.micronaut.inject.writer.GenUtils; import io.micronaut.sourcegen.model.ClassTypeDef; import io.micronaut.sourcegen.model.ExpressionDef; import io.micronaut.sourcegen.model.FieldDef; @@ -47,6 +47,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * Responsible for writing class files that are instances of {@link AnnotationMetadata}. @@ -55,7 +56,7 @@ * @since 1.0 */ @Internal -public class AnnotationMetadataGenUtils { +public final class AnnotationMetadataGenUtils { /** * Field name for annotation metadata. @@ -141,18 +142,32 @@ public class AnnotationMetadataGenUtils { "ANNOTATION_DEFAULT_VALUES_PROVIDER" ); - @Internal - public static ExpressionDef instantiateNewMetadata(ClassTypeDef owningType, - MutableAnnotationMetadata annotationMetadata, - Map loadTypeMethods) { - return instantiateInternal(owningType, annotationMetadata, true, loadTypeMethods); + private AnnotationMetadataGenUtils() { } - @Internal - public static ExpressionDef instantiateNewMetadataHierarchy( - ClassTypeDef owningType, - AnnotationMetadataHierarchy hierarchy, - Map loadTypeMethods) { + /** + * Instantiate new metadata expression. + * + * @param annotationMetadata The annotation metadata + * @param loadClassValueExpressionFn The load type expression fn + * @return The expression + */ + @NonNull + public static ExpressionDef instantiateNewMetadata(MutableAnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { + return instantiateInternal(annotationMetadata, loadClassValueExpressionFn); + } + + /** + * Instantiate new metadata hierarchy expression. + * + * @param hierarchy The annotation metadata hierarchy + * @param loadClassValueExpressionFn The load type expression fn + * @return The expression + */ + @NonNull + public static ExpressionDef instantiateNewMetadataHierarchy(AnnotationMetadataHierarchy hierarchy, + Function loadClassValueExpressionFn) { if (hierarchy.isEmpty()) { return emptyMetadata(); @@ -160,59 +175,183 @@ public static ExpressionDef instantiateNewMetadataHierarchy( List notEmpty = CollectionUtils.iterableToList(hierarchy) .stream().filter(h -> !h.isEmpty()).toList(); if (notEmpty.size() == 1) { - return pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, notEmpty.get(0)); + return pushNewAnnotationMetadataOrReference(notEmpty.get(0), loadClassValueExpressionFn); } return TYPE_DEFAULT_ANNOTATION_METADATA_HIERARCHY.instantiate( CONSTRUCTOR_ANNOTATION_METADATA_HIERARCHY, TYPE_ANNOTATION_METADATA.array().instantiate( - pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, hierarchy.getRootMetadata()), - pushNewAnnotationMetadataOrReference(owningType, loadTypeMethods, hierarchy.getDeclaredMetadata()) + pushNewAnnotationMetadataOrReference(hierarchy.getRootMetadata(), loadClassValueExpressionFn), + pushNewAnnotationMetadataOrReference(hierarchy.getDeclaredMetadata(), loadClassValueExpressionFn) ) ); } - @Internal + /** + * The annotation metadata reference expression. + * + * @param annotationMetadata The annotation metadata + * @return The expression + */ + @NonNull public static ExpressionDef annotationMetadataReference(AnnotationMetadataReference annotationMetadata) { return ClassTypeDef.of(annotationMetadata.getClassName()).getStaticField(FIELD_ANNOTATION_METADATA); } - @Internal - private static ExpressionDef pushNewAnnotationMetadataOrReference( - ClassTypeDef owningType, - Map loadTypeMethods, - AnnotationMetadata annotationMetadata) { - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - // Synthetic property getters / setters can consist of field + (setter / getter) annotation hierarchy - annotationMetadata = MutableAnnotationMetadata.of(annotationMetadataHierarchy); + /** + * The empty annotation metadata expression. + * + * @return The expression + */ + @NonNull + public static ExpressionDef emptyMetadata() { + return TYPE_ANNOTATION_METADATA.getStaticField("EMPTY_METADATA", TYPE_ANNOTATION_METADATA); + } + + /** + * Create a new load class value expression function. + * + * @param declaringType The declaring type + * @param loadTypeMethods The load type methods + * @return The function + */ + @NonNull + public static Function createLoadClassValueExpressionFn(ClassTypeDef declaringType, + Map loadTypeMethods) { + return typeName -> invokeLoadClassValueMethod(declaringType, loadTypeMethods, typeName); + } + + /** + * Creates a `getAnnotationMetadata` method. + * + * @param owningType The owning type + * @param annotationMetadata The annotation metadata + * @return The new method + */ + @NonNull + public static MethodDef createGetAnnotationMetadataMethodDef(ClassTypeDef owningType, AnnotationMetadata annotationMetadata) { + return MethodDef.builder("getAnnotationMetadata").returns(TYPE_ANNOTATION_METADATA) + .addModifiers(Modifier.PUBLIC) + .build((aThis, methodParameters) -> { + // in order to save memory of a method doesn't have any annotations of its own but merely references class metadata + // then we set up an annotation metadata reference from the method to the class (or inherited method) metadata + AnnotationMetadata targetAnnotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (targetAnnotationMetadata.isEmpty()) { + return AnnotationMetadataGenUtils.EMPTY_METADATA.returning(); + } + if (targetAnnotationMetadata instanceof AnnotationMetadataReference reference) { + return annotationMetadataReference(reference).returning(); + } + return owningType.getStaticField(FIELD_ANNOTATION_METADATA).returning(); + }); + } + + /** + * Create annotation metadata field and initialize it to the metadata provided. + * + * @param annotationMetadata The annotation metadata + * @param loadClassValueExpressionFn The function to get the class value + * @return The new field + */ + @Nullable + public static FieldDef createAnnotationMetadataFieldAndInitialize(AnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { + if (annotationMetadata instanceof AnnotationMetadataReference) { + return null; } + FieldDef.FieldDefBuilder fieldDefBuilder = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); + + ExpressionDef initializer; + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); if (annotationMetadata.isEmpty()) { - return emptyMetadata(); + initializer = AnnotationMetadataGenUtils.EMPTY_METADATA; } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - return instantiateNewMetadata(owningType, mutableAnnotationMetadata, loadTypeMethods); - } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { - return annotationMetadataReference(reference); + initializer = AnnotationMetadataGenUtils.instantiateNewMetadata( + mutableAnnotationMetadata, + loadClassValueExpressionFn + ); + } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + initializer = AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(annotationMetadataHierarchy, loadClassValueExpressionFn); } else { throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); } + fieldDefBuilder.initializer(initializer); + + return fieldDefBuilder.build(); } - public static ExpressionDef emptyMetadata() { - return TYPE_ANNOTATION_METADATA.getStaticField("EMPTY_METADATA", TYPE_ANNOTATION_METADATA); + /** + * Adds the annotation metadata defaults statement/s. + * + * @param statements The statements + * @param annotationMetadata The annotation metadata + * @param loadClassValueExpressionFn The load type expression fn + */ + public static void addAnnotationDefaults(List statements, + AnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (annotationMetadata.isEmpty()) { + return; + } + if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + annotationMetadata = annotationMetadataHierarchy.merge(); + } + if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + AnnotationMetadataGenUtils.addAnnotationDefaults( + statements, + mutableAnnotationMetadata, + loadClassValueExpressionFn + ); + } else { + throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); + } } - @Internal - public static void writeAnnotationDefaults( - List statements, - ClassTypeDef owningType, - MutableAnnotationMetadata annotationMetadata, - Map loadTypeMethods) { + @NonNull + private static ExpressionDef.InvokeStaticMethod invokeLoadClassValueMethod(ClassTypeDef declaringType, + Map loadTypeMethods, + String typeName) { + final MethodDef loadTypeGeneratorMethod = loadTypeMethods.computeIfAbsent(typeName, type -> { + + final String methodName = LOAD_CLASS_PREFIX + loadTypeMethods.size(); + + // This logic will generate a method such as the following, allowing non-dynamic classloading: + // + // AnnotationClassValue $micronaut_load_class_value_0() { + // try { + // return new AnnotationClassValue(test.MyClass.class); + // } catch(Throwable e) { + // return new AnnotationClassValue("test.MyClass"); + // } + // } + + return MethodDef.builder(methodName) + .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) + .returns(TYPE_ANNOTATION_CLASS_VALUE) + .buildStatic(methodParameters -> StatementDef.doTry( + TYPE_ANNOTATION_CLASS_VALUE.instantiate( + CONSTRUCTOR_CLASS_VALUE_WITH_CLASS, + ExpressionDef.constant(TypeDef.of(typeName)) + ).returning() + ).doCatch(Throwable.class, exceptionVar -> TYPE_ANNOTATION_CLASS_VALUE.instantiate( + CONSTRUCTOR_CLASS_VALUE, + ExpressionDef.constant(typeName) + ).returning())); + }); + + return declaringType.invokeStatic(loadTypeGeneratorMethod); + } + + private static void addAnnotationDefaults(List statements, + MutableAnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { final Map> annotationDefaultValues = annotationMetadata.annotationDefaultValues; if (CollectionUtils.isNotEmpty(annotationDefaultValues)) { - writeAnnotationDefaultsInternal(statements, owningType, loadTypeMethods, annotationDefaultValues, new HashSet<>()); + writeAnnotationDefaultsInternal(statements, annotationDefaultValues, new HashSet<>(), loadClassValueExpressionFn); } if (annotationMetadata.annotationRepeatableContainer != null && !annotationMetadata.annotationRepeatableContainer.isEmpty()) { Map annotationRepeatableContainer = new LinkedHashMap<>(annotationMetadata.annotationRepeatableContainer); @@ -222,7 +361,7 @@ public static void writeAnnotationDefaults( statements.add( TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( METHOD_REGISTER_REPEATABLE_ANNOTATIONS, - stringMapOf(owningType, annotationRepeatableContainer, loadTypeMethods) + stringMapOf(annotationRepeatableContainer, loadClassValueExpressionFn) ) ); } @@ -230,27 +369,24 @@ public static void writeAnnotationDefaults( } private static void writeAnnotationDefaultsInternal(List statements, - ClassTypeDef owningType, - Map loadTypeMethods, Map> annotationDefaultValues, - Set writtenAnnotations) { + Set writtenAnnotations, + Function loadClassValueExpressionFn) { for (Map.Entry> entry : annotationDefaultValues.entrySet()) { - writeAnnotationDefaultsInternal(statements, - owningType, - loadTypeMethods, + addAnnotationDefaultsInternal(statements, writtenAnnotations, entry.getKey(), - entry.getValue()); + entry.getValue(), + loadClassValueExpressionFn); } } @NonNull - private static void writeAnnotationDefaultsInternal(List statements, - ClassTypeDef owningType, - Map loadTypeMethods, - Set writtenAnnotations, - String annotationName, - Map annotationValues) { + private static void addAnnotationDefaultsInternal(List statements, + Set writtenAnnotations, + String annotationName, + Map annotationValues, + Function loadClassValueExpressionFn) { final boolean typeOnly = CollectionUtils.isEmpty(annotationValues); // skip already registered @@ -265,13 +401,12 @@ private static void writeAnnotationDefaultsInternal(List statement for (Map.Entry values : annotationValues.entrySet()) { Object value = values.getValue(); if (value instanceof AnnotationValue annotationValue && CollectionUtils.isNotEmpty(annotationValue.getDefaultValues())) { - writeAnnotationDefaultsInternal( + addAnnotationDefaultsInternal( statements, - owningType, - loadTypeMethods, writtenAnnotations, annotationValue.getAnnotationName(), - annotationValue.getDefaultValues() + annotationValue.getDefaultValues(), + loadClassValueExpressionFn ); } } @@ -280,29 +415,24 @@ private static void writeAnnotationDefaultsInternal(List statement statements.add( TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( METHOD_REGISTER_ANNOTATION_DEFAULTS, - invokeLoadClassValueMethod(owningType, loadTypeMethods, new AnnotationClassValue<>(annotationName)), - stringMapOf(owningType, annotationValues, loadTypeMethods) + loadClassValueExpressionFn.apply(annotationName), + stringMapOf(annotationValues, loadClassValueExpressionFn) ) ); } else { statements.add( TYPE_DEFAULT_ANNOTATION_METADATA.invokeStatic( METHOD_REGISTER_ANNOTATION_TYPE, - invokeLoadClassValueMethod(owningType, loadTypeMethods, new AnnotationClassValue<>(annotationName)) + loadClassValueExpressionFn.apply(annotationName) ) ); } writtenAnnotations.add(annotationName); } - private static ExpressionDef instantiateInternal( - ClassTypeDef owningType, - MutableAnnotationMetadata annotationMetadata, - boolean isNew, - Map loadTypeMethods) { - if (!isNew) { - throw new IllegalStateException(); - } + @NonNull + private static ExpressionDef instantiateInternal(MutableAnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { Map> annotationsByStereotype = annotationMetadata.annotationsByStereotype; if (annotationMetadata.getSourceRetentionAnnotations() != null && annotationsByStereotype != null) { annotationsByStereotype = new LinkedHashMap<>(annotationsByStereotype); @@ -315,15 +445,15 @@ private static ExpressionDef instantiateInternal( CONSTRUCTOR_ANNOTATION_METADATA, // 1st argument: the declared annotations - pushCreateAnnotationData(owningType, annotationMetadata.declaredAnnotations, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + pushCreateAnnotationData(annotationMetadata.declaredAnnotations, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn), // 2nd argument: the declared stereotypes - pushCreateAnnotationData(owningType, annotationMetadata.declaredStereotypes, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + pushCreateAnnotationData(annotationMetadata.declaredStereotypes, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn), // 3rd argument: all stereotypes - pushCreateAnnotationData(owningType, annotationMetadata.allStereotypes, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + pushCreateAnnotationData(annotationMetadata.allStereotypes, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn), // 4th argument: all annotations - pushCreateAnnotationData(owningType, annotationMetadata.allAnnotations, loadTypeMethods, annotationMetadata.getSourceRetentionAnnotations()), + pushCreateAnnotationData(annotationMetadata.allAnnotations, annotationMetadata.getSourceRetentionAnnotations(), loadClassValueExpressionFn), // 5th argument: annotations by stereotype, - ExpressionsUtils.stringMapOf(annotationsByStereotype, false, Collections.emptyList(), ExpressionsUtils::listOfString), + GenUtils.stringMapOf(annotationsByStereotype, false, Collections.emptyList(), GenUtils::listOfString), // 6th argument: has property expressions, ExpressionDef.constant(annotationMetadata.hasPropertyExpressions()), // 7th argument: has evaluated expressions @@ -331,11 +461,10 @@ private static ExpressionDef instantiateInternal( ); } - private static ExpressionDef pushCreateAnnotationData( - ClassTypeDef declaringType, - Map> annotationData, - Map loadTypeMethods, - Set sourceRetentionAnnotations) { + @NonNull + private static ExpressionDef pushCreateAnnotationData(Map> annotationData, + Set sourceRetentionAnnotations, + Function loadClassValueExpressionFn) { if (annotationData != null) { annotationData = new LinkedHashMap<>(annotationData); for (String sourceRetentionAnnotation : sourceRetentionAnnotations) { @@ -343,16 +472,16 @@ private static ExpressionDef pushCreateAnnotationData( } } - return ExpressionsUtils.stringMapOf(annotationData, false, Collections.emptyMap(), - attributes -> ExpressionsUtils.stringMapOf(attributes, true, null, - value -> asValueExpression(declaringType, value, loadTypeMethods))); + return GenUtils.stringMapOf(annotationData, false, Collections.emptyMap(), + attributes -> GenUtils.stringMapOf(attributes, true, null, + value -> asValueExpression(value, loadClassValueExpressionFn))); } - private static ExpressionDef asValueExpression(ClassTypeDef declaringType, - Object value, - Map loadTypeMethods) { + @NonNull + private static ExpressionDef asValueExpression(Object value, + Function loadClassValueExpressionFn) { if (value == null) { - throw new IllegalStateException("Cannot map null value in: " + declaringType.getName()); + throw new IllegalStateException("Cannot map null value"); } if (value instanceof Enum anEnum) { return ExpressionDef.constant(anEnum.name()); @@ -367,7 +496,7 @@ private static ExpressionDef asValueExpression(ClassTypeDef declaringType, ClassTypeDef.of(acv.getName()).instantiate() ); } else { - return invokeLoadClassValueMethod(declaringType, loadTypeMethods, acv); + return loadClassValueExpressionFn.apply(acv.getName()); } } if (value.getClass().isArray()) { @@ -377,7 +506,7 @@ private static ExpressionDef asValueExpression(ClassTypeDef declaringType, arrayComponentType = String.class; } return TypeDef.of(arrayComponentType).array().instantiate(Arrays.stream(getArray(value)) - .map(v -> asValueExpression(declaringType, v, loadTypeMethods)) + .map(v -> asValueExpression(v, loadClassValueExpressionFn)) .toList()); } if (value instanceof Collection collection) { @@ -398,14 +527,14 @@ private static ExpressionDef asValueExpression(ClassTypeDef declaringType, componentType = String.class; } return TypeDef.of(componentType).array() - .instantiate(collection.stream().map(i -> asValueExpression(declaringType, i, loadTypeMethods)).toList()); + .instantiate(collection.stream().map(i -> asValueExpression(i, loadClassValueExpressionFn)).toList()); } if (value instanceof AnnotationValue data) { return ClassTypeDef.of(AnnotationValue.class) .instantiate( CONSTRUCTOR_ANNOTATION_VALUE_AND_MAP, ExpressionDef.constant(data.getAnnotationName()), - stringMapOf(declaringType, data.getValues(), loadTypeMethods), + stringMapOf(data.getValues(), loadClassValueExpressionFn), ClassTypeDef.of(AnnotationMetadataSupport.class).getStaticField(ANNOTATION_DEFAULT_VALUES_PROVIDER) ); } @@ -425,6 +554,37 @@ private static ExpressionDef asValueExpression(ClassTypeDef declaringType, throw new IllegalStateException("Unsupported Map value: " + value + " " + value.getClass().getName()); } + @NonNull + private static ExpressionDef stringMapOf(Map annotationData, + Function loadClassValueExpressionFn) { + return GenUtils.stringMapOf( + annotationData, + true, + null, + AnnotationMetadataGenUtils::isSupportedMapValue, + o -> asValueExpression(o, loadClassValueExpressionFn) + ); + } + + @NonNull + private static ExpressionDef pushNewAnnotationMetadataOrReference(AnnotationMetadata annotationMetadata, + Function loadClassValueExpressionFn) { + annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); + if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + // Synthetic property getters / setters can consist of field + (setter / getter) annotation hierarchy + annotationMetadata = MutableAnnotationMetadata.of(annotationMetadataHierarchy); + } + if (annotationMetadata.isEmpty()) { + return emptyMetadata(); + } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + return instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn); + } else if (annotationMetadata instanceof AnnotationMetadataReference reference) { + return annotationMetadataReference(reference); + } else { + throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); + } + } + private static Object[] getArray(Object val) { if (val instanceof Object[]) { return (Object[]) val; @@ -436,7 +596,7 @@ private static Object[] getArray(Object val) { return outputArray; } - public static boolean isSupportedMapValue(Object value) { + private static boolean isSupportedMapValue(Object value) { if (value == null) { return false; } else if (value instanceof Boolean) { @@ -478,123 +638,4 @@ public static boolean isSupportedMapValue(Object value) { return false; } - @NonNull - public static ExpressionDef.InvokeStaticMethod invokeLoadClassValueMethod(ClassTypeDef declaringType, - Map loadTypeMethods, - AnnotationClassValue acv) { - final String typeName = acv.getName(); - - final MethodDef loadTypeGeneratorMethod = loadTypeMethods.computeIfAbsent(typeName, type -> { - - final String methodName = LOAD_CLASS_PREFIX + loadTypeMethods.size(); - - // This logic will generate a method such as the following, allowing non-dynamic classloading: - // - // AnnotationClassValue $micronaut_load_class_value_0() { - // try { - // return new AnnotationClassValue(test.MyClass.class); - // } catch(Throwable e) { - // return new AnnotationClassValue("test.MyClass"); - // } - // } - - return MethodDef.builder(methodName) - .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) - .returns(TYPE_ANNOTATION_CLASS_VALUE) - .buildStatic(methodParameters -> StatementDef.doTry( - TYPE_ANNOTATION_CLASS_VALUE.instantiate( - CONSTRUCTOR_CLASS_VALUE_WITH_CLASS, - ExpressionDef.constant(TypeDef.of(typeName)) - ).returning() - ).doCatch(Throwable.class, exceptionVar -> TYPE_ANNOTATION_CLASS_VALUE.instantiate( - CONSTRUCTOR_CLASS_VALUE, - ExpressionDef.constant(typeName) - ).returning())); - }); - - return declaringType.invokeStatic(loadTypeGeneratorMethod); - } - - public static MethodDef getAnnotationMetadataMethodDef(ClassTypeDef owningType, AnnotationMetadata am) { - return MethodDef.builder("getAnnotationMetadata").returns(TYPE_ANNOTATION_METADATA) - .addModifiers(Modifier.PUBLIC) - .build((aThis, methodParameters) -> { - // in order to save memory of a method doesn't have any annotations of its own but merely references class metadata - // then we set up an annotation metadata reference from the method to the class (or inherited method) metadata - AnnotationMetadata annotationMetadata = am.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - return AnnotationMetadataGenUtils.EMPTY_METADATA.returning(); - } - if (annotationMetadata instanceof AnnotationMetadataReference reference) { - return annotationMetadataReference(reference).returning(); - } - return owningType.getStaticField(FIELD_ANNOTATION_METADATA).returning(); - }); - } - - @Nullable - public static FieldDef createAnnotationMetadataField(ClassTypeDef targetType, - AnnotationMetadata annotationMetadata, - Map loadTypeMethods) { - if (annotationMetadata instanceof AnnotationMetadataReference) { - return null; - } - FieldDef.FieldDefBuilder fieldDefBuilder = FieldDef.builder(FIELD_ANNOTATION_METADATA_NAME, TYPE_ANNOTATION_METADATA) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); - - ExpressionDef initializer; - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - initializer = AnnotationMetadataGenUtils.EMPTY_METADATA; - } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - initializer = AnnotationMetadataGenUtils.instantiateNewMetadata( - targetType, - mutableAnnotationMetadata, - loadTypeMethods - ); - } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - initializer = AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(targetType, annotationMetadataHierarchy, loadTypeMethods); - } else { - throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); - } - fieldDefBuilder.initializer(initializer); - - return fieldDefBuilder.build(); - } - - public static void writeAnnotationDefault(List statements, - ClassTypeDef targetClassType, - AnnotationMetadata annotationMetadata, - Map loadTypeMethods) { - annotationMetadata = annotationMetadata.getTargetAnnotationMetadata(); - if (annotationMetadata.isEmpty()) { - return; - } - if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - annotationMetadata = annotationMetadataHierarchy.merge(); - } - if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - AnnotationMetadataGenUtils.writeAnnotationDefaults( - statements, - targetClassType, - mutableAnnotationMetadata, - loadTypeMethods - ); - } else { - throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); - } - } - - private static ExpressionDef stringMapOf(ClassTypeDef declaringType, - Map annotationData, - Map loadTypeMethods) { - return ExpressionsUtils.stringMapOf( - annotationData, - true, - null, - AnnotationMetadataGenUtils::isSupportedMapValue, - o -> asValueExpression(declaringType, o, loadTypeMethods) - ); - } - } diff --git a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java index 063da111a27..71b6ac6e825 100644 --- a/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/annotation/AnnotationMetadataWriter.java @@ -22,6 +22,7 @@ import io.micronaut.sourcegen.bytecode.ByteCodeWriter; import io.micronaut.sourcegen.model.ClassDef; import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; import io.micronaut.sourcegen.model.FieldDef; import io.micronaut.sourcegen.model.MethodDef; import io.micronaut.sourcegen.model.StatementDef; @@ -32,6 +33,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * Responsible for writing class files that are instances of {@link AnnotationMetadataProvider}. @@ -55,10 +57,11 @@ public class AnnotationMetadataWriter { public static byte[] write(String className, AnnotationMetadata annotationMetadata) { Map loadTypeMethods = new LinkedHashMap<>(); ClassTypeDef type = ClassTypeDef.of(className + AnnotationMetadata.CLASS_NAME_SUFFIX); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(type, annotationMetadata, loadTypeMethods); + Function loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(type, loadTypeMethods); + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataFieldAndInitialize(annotationMetadata, loadClassValueExpressionFn); List statements = new ArrayList<>(); - AnnotationMetadataGenUtils.writeAnnotationDefault(statements, type, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.addAnnotationDefaults(statements, annotationMetadata, loadClassValueExpressionFn); ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(type.getName()) .addSuperinterface(TypeDef.of(AnnotationMetadataProvider.class)) diff --git a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java index c5221a6ffaf..9b04d4241ad 100644 --- a/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/beans/visitor/BeanIntrospectionWriter.java @@ -79,6 +79,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -411,7 +412,9 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE } } - private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyData, List staticStatements, Map loadTypeMethods) { + private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyData, + List staticStatements, + Function loadClassValueExpressionFn) { ClassTypeDef beanPropertyRefDef = ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class); boolean mutable = !beanPropertyData.isReadOnly || hasAssociatedConstructorArgument(beanPropertyData.name, beanPropertyData.type); @@ -422,7 +425,7 @@ private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyDat introspectionTypeDef, beanPropertyData.name, beanPropertyData.type, - loadTypeMethods + loadClassValueExpressionFn ).newLocal(beanPropertyData.name + "Arg"); staticStatements.add(defineAndAssign); @@ -445,7 +448,7 @@ private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyDat introspectionTypeDef, beanPropertyData.name, beanPropertyData.readType, - loadTypeMethods + loadClassValueExpressionFn ); writeArgument = beanPropertyData.writeType == null ? null : ArgumentExpUtils.pushCreateArgument( annotationMetadata, @@ -453,7 +456,7 @@ private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyDat introspectionTypeDef, beanPropertyData.name, beanPropertyData.writeType, - loadTypeMethods + loadClassValueExpressionFn ); } return beanPropertyRefDef.instantiate( @@ -470,7 +473,7 @@ private ExpressionDef pushBeanPropertyReference(BeanPropertyData beanPropertyDat ); } - private ExpressionDef newBeanMethodRef(BeanMethodData beanMethodData, Map loadTypeMethods) { + private ExpressionDef newBeanMethodRef(BeanMethodData beanMethodData, Function loadClassValueExpressionFn) { return ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanMethodRef.class) .instantiate( BEAN_METHOD_REF_CONSTRUCTOR, @@ -481,38 +484,38 @@ private ExpressionDef newBeanMethodRef(BeanMethodData beanMethodData, Map loadTypeMethods) { + private ExpressionDef newEnumConstantRef(EnumConstantElement enumConstantElement, Function loadClassValueExpressionFn) { return ClassTypeDef.of( AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class ).instantiate( ENUM_CONSTANT_DYNAMIC_REF_CONSTRUCTOR, // 1: push annotation class value - AnnotationMetadataGenUtils.invokeLoadClassValueMethod(introspectionTypeDef, loadTypeMethods, new AnnotationClassValue<>(enumConstantElement.getOwningType().getName())), + loadClassValueExpressionFn.apply(enumConstantElement.getOwningType().getName()), // 2: push enum name ExpressionDef.constant(enumConstantElement.getName()), // 3: annotation metadata enumConstantElement.getAnnotationMetadata() == null ? ( ClassTypeDef.of(AnnotationMetadata.class).getStaticField("EMPTY_METADATA", TypeDef.of(AnnotationMetadata.class)) - ) : getAnnotationMetadataExpression(enumConstantElement.getAnnotationMetadata(), loadTypeMethods) + ) : getAnnotationMetadataExpression(enumConstantElement.getAnnotationMetadata(), loadClassValueExpressionFn) ); } @@ -534,6 +537,9 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV Map loadTypeMethods = new LinkedHashMap<>(); ClassTypeDef thisType = ClassTypeDef.of(introspectionName); + + Function loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(introspectionTypeDef, loadTypeMethods); + ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(introspectionName).addModifiers(Modifier.FINAL, Modifier.PUBLIC); classDefBuilder.superclass(isEnum ? ClassTypeDef.of(AbstractEnumBeanIntrospectionAndReference.class) : ClassTypeDef.of(AbstractInitializableBeanIntrospectionAndReference.class)); @@ -555,7 +561,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV if (!constructor.getAnnotationMetadata().isEmpty()) { constructorAnnotationMetadataField = FieldDef.builder(FIELD_CONSTRUCTOR_ANNOTATION_METADATA, AnnotationMetadata.class) .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) - .initializer(getAnnotationMetadataExpression(constructor.getAnnotationMetadata(), loadTypeMethods)) + .initializer(getAnnotationMetadataExpression(constructor.getAnnotationMetadata(), loadClassValueExpressionFn)) .build(); classDefBuilder.addField( constructorAnnotationMetadataField @@ -572,7 +578,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV constructor.getOwningType(), introspectionTypeDef, Arrays.asList(constructor.getParameters()), - loadTypeMethods + loadClassValueExpressionFn ) ) .build(); @@ -596,7 +602,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanPropertyRef.class).array() .instantiate( beanProperties.stream() - .map(e -> pushBeanPropertyReference(e, staticStatements, loadTypeMethods)) + .map(e -> pushBeanPropertyReference(e, staticStatements, loadClassValueExpressionFn)) .toList() ) ) @@ -611,7 +617,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV ClassTypeDef.of(AbstractInitializableBeanIntrospection.BeanMethodRef.class).array() .instantiate( beanMethods.stream() - .map(e -> newBeanMethodRef(e, loadTypeMethods)) + .map(e -> newBeanMethodRef(e, loadClassValueExpressionFn)) .toList() ) ) @@ -627,7 +633,7 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV ClassTypeDef.of(AbstractEnumBeanIntrospectionAndReference.EnumConstantDynamicRef.class).array() .instantiate( ((EnumElement) beanClassElement).elements().stream() - .map(e -> newEnumConstantRef(e, loadTypeMethods)) + .map(e -> newEnumConstantRef(e, loadClassValueExpressionFn)) .toList() ) ) @@ -660,9 +666,9 @@ private void writeIntrospectionClass(ClassWriterOutputVisitor classWriterOutputV annotationIndexFields.put(annotationName, field); } - AnnotationMetadataGenUtils.writeAnnotationDefault(staticStatements, introspectionTypeDef, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.addAnnotationDefaults(staticStatements, annotationMetadata, loadClassValueExpressionFn); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(introspectionTypeDef, annotationMetadata, loadTypeMethods); + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataFieldAndInitialize(annotationMetadata, loadClassValueExpressionFn); if (annotationMetadataField != null) { classDefBuilder.addField( annotationMetadataField @@ -889,7 +895,7 @@ private MethodDef getInstantiateMethod(MethodElement constructor, Method method) }); } - private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotationMetadata, Map loadTypeMethods) { + private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotationMetadata, Function loadClassValueExpressionFn) { MutableAnnotationMetadata.contributeDefaults( this.annotationMetadata, annotationMetadata @@ -901,15 +907,9 @@ private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotat } else if (annotationMetadata instanceof AnnotationMetadataReference annotationMetadataReference) { return AnnotationMetadataGenUtils.annotationMetadataReference(annotationMetadataReference); } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy( - introspectionTypeDef, - annotationMetadataHierarchy, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(annotationMetadataHierarchy, loadClassValueExpressionFn); } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - return AnnotationMetadataGenUtils.instantiateNewMetadata( - introspectionTypeDef, - mutableAnnotationMetadata, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn); } else { throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata); } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java index be65420bbc4..106135fa3f2 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/ArgumentExpUtils.java @@ -35,7 +35,6 @@ import io.micronaut.inject.ast.WildcardElement; import io.micronaut.sourcegen.model.ClassTypeDef; import io.micronaut.sourcegen.model.ExpressionDef; -import io.micronaut.sourcegen.model.MethodDef; import io.micronaut.sourcegen.model.TypeDef; import java.lang.reflect.Method; @@ -47,6 +46,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * The argument expression utils. @@ -126,14 +126,14 @@ public final class ArgumentExpUtils { * @param owningType The owning type * @param declaringType The declaring type name * @param argument The argument - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load type method fn * @return The expression */ public static ExpressionDef pushReturnTypeArgument(AnnotationMetadata annotationMetadataWithDefaults, ClassTypeDef owningType, ClassElement declaringType, ClassElement argument, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { // Persist only type annotations added AnnotationMetadata annotationMetadata = argument.getTypeAnnotationMetadata(); @@ -162,7 +162,7 @@ public static ExpressionDef pushReturnTypeArgument(AnnotationMetadata annotation argument, annotationMetadata, argument.getTypeArguments(), - loadTypeMethods + loadClassValueExpressionFn ); } @@ -174,7 +174,7 @@ public static ExpressionDef pushReturnTypeArgument(AnnotationMetadata annotation * @param owningType The owning type * @param argumentName The argument name * @param argument The argument - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load type methods fn * @return The expression */ public static ExpressionDef pushCreateArgument( @@ -183,7 +183,7 @@ public static ExpressionDef pushCreateArgument( ClassTypeDef owningType, String argumentName, ClassElement argument, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { return pushCreateArgument( annotationMetadataWithDefaults, @@ -193,7 +193,7 @@ public static ExpressionDef pushCreateArgument( argument, argument.getAnnotationMetadata(), argument.getTypeArguments(), - loadTypeMethods + loadClassValueExpressionFn ); } @@ -207,7 +207,7 @@ public static ExpressionDef pushCreateArgument( * @param argumentType The argument type * @param annotationMetadata The annotation metadata * @param typeArguments The type arguments - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load class value expression fn * @return The expression */ static ExpressionDef pushCreateArgument( @@ -218,7 +218,7 @@ static ExpressionDef pushCreateArgument( TypedElement argumentType, AnnotationMetadata annotationMetadata, Map typeArguments, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { annotationMetadata = MutableAnnotationMetadata.of(annotationMetadata); ExpressionDef.Constant argumentTypeConstant = ExpressionDef.constant(TypeDef.erasure(resolveArgument(argumentType))); @@ -262,9 +262,8 @@ static ExpressionDef pushCreateArgument( ); values.add(AnnotationMetadataGenUtils.instantiateNewMetadata( - owningType, (MutableAnnotationMetadata) annotationMetadata, - loadTypeMethods + loadClassValueExpressionFn )); } else { values.add(ExpressionDef.nullValue()); @@ -277,7 +276,7 @@ static ExpressionDef pushCreateArgument( owningType, declaringType, typeArguments, - loadTypeMethods + loadClassValueExpressionFn )); } else { values.add(ExpressionDef.nullValue()); @@ -338,7 +337,7 @@ private static TypedElement resolveArgument(TypedElement argumentType) { * @param owningType The owning type * @param declaringType The declaring class element of the generics * @param types The type references - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load type expression fn * @return The expression */ static ExpressionDef pushTypeArgumentElements( @@ -346,7 +345,7 @@ static ExpressionDef pushTypeArgumentElements( ClassTypeDef owningType, ClassElement declaringType, Map types, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { if (types == null || types.isEmpty()) { return TYPE_ARGUMENT_ARRAY.instantiate(); } @@ -357,7 +356,7 @@ static ExpressionDef pushTypeArgumentElements( null, types, new HashSet<>(5), - loadTypeMethods); + loadClassValueExpressionFn); } @SuppressWarnings("java:S1872") @@ -369,7 +368,7 @@ private static ExpressionDef pushTypeArgumentElements( ClassElement element, Map types, Set visitedTypes, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { if (element == null) { if (visitedTypes.contains(declaringType.getName())) { return TYPE_ARGUMENT.getStaticField(ZERO_ARGUMENTS_CONSTANT, TYPE_ARGUMENT_ARRAY); @@ -390,7 +389,7 @@ private static ExpressionDef pushTypeArgumentElements( classElement, typeArguments, visitedTypes, - loadTypeMethods + loadClassValueExpressionFn ); } return buildArgument(argumentName, classElement); @@ -406,7 +405,7 @@ private static ExpressionDef pushTypeArgumentElements( * @param argumentType The argument type * @param typeArguments The nested type arguments * @param visitedTypes The visited types - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load type method fn * @return The expression */ static ExpressionDef buildArgumentWithGenerics( @@ -416,7 +415,7 @@ static ExpressionDef buildArgumentWithGenerics( ClassElement argumentType, Map typeArguments, Set visitedTypes, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { ExpressionDef.Constant argumentTypeConstant = ExpressionDef.constant(TypeDef.erasure(resolveArgument(argumentType))); List values = new ArrayList<>(); @@ -466,9 +465,8 @@ static ExpressionDef buildArgumentWithGenerics( values.add( AnnotationMetadataGenUtils.instantiateNewMetadata( - owningType, (MutableAnnotationMetadata) annotationMetadata, - loadTypeMethods + loadClassValueExpressionFn ) ); } else { @@ -484,7 +482,7 @@ static ExpressionDef buildArgumentWithGenerics( argumentType, typeArguments, visitedTypes, - loadTypeMethods + loadClassValueExpressionFn ) ); @@ -579,15 +577,14 @@ public static ExpressionDef buildArgumentWithGenerics(TypeDef type, * @param declaringElement The declaring element name * @param owningType The owning type * @param argumentTypes The argument types - * @param loadTypeMethods The load type methods + * @param loadClassValueExpressionFn The load type method expression fn * @return The expression */ - public static ExpressionDef pushBuildArgumentsForMethod( - AnnotationMetadata annotationMetadataWithDefaults, - ClassElement declaringElement, - ClassTypeDef owningType, - Collection argumentTypes, - Map loadTypeMethods) { + public static ExpressionDef pushBuildArgumentsForMethod(AnnotationMetadata annotationMetadataWithDefaults, + ClassElement declaringElement, + ClassTypeDef owningType, + Collection argumentTypes, + Function loadClassValueExpressionFn) { return TYPE_ARGUMENT_ARRAY.instantiate(argumentTypes.stream().map(parameterElement -> { ClassElement genericType = parameterElement.getGenericType(); @@ -622,7 +619,7 @@ public static ExpressionDef pushBuildArgumentsForMethod( genericType, annotationMetadata, typeArguments, - loadTypeMethods + loadClassValueExpressionFn ); }).toList()); diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java index fb063009093..8bf97ca1ed6 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanConfigurationWriter.java @@ -38,8 +38,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; -import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.getAnnotationMetadataMethodDef; +import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.createGetAnnotationMetadataMethodDef; /** * Writes configuration classes for configuration packages using ASM. @@ -99,19 +100,22 @@ private byte[] generateClassBytes() { ClassDef configurationClass = configurationClassBuilder .addMethod(MethodDef.constructor().addModifiers(Modifier.PUBLIC).build((aThis, methodParameters) -> aThis.superRef().invokeConstructor(ExpressionDef.constant(packageName)))) - .addMethod(getAnnotationMetadataMethodDef(targetType, annotationMetadata)) + .addMethod(createGetAnnotationMetadataMethodDef(targetType, annotationMetadata)) .build(); Map loadTypeMethods = new LinkedHashMap<>(); + + + Function loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(targetType, loadTypeMethods); + // write the static initializers for the annotation metadata List staticInit = new ArrayList<>(); - AnnotationMetadataGenUtils.writeAnnotationDefault(staticInit, targetType, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.addAnnotationDefaults(staticInit, annotationMetadata, loadClassValueExpressionFn); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField( - targetType, + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataFieldAndInitialize( annotationMetadata, - loadTypeMethods + loadClassValueExpressionFn ); loadTypeMethods.values().forEach(configurationClassBuilder::addMethod); diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java index 5cf809175c4..0d720c92a6f 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/BeanDefinitionWriter.java @@ -655,6 +655,8 @@ public final class BeanDefinitionWriter implements ClassOutputWriter, BeanDefini private ConfigBuilderInjectCommand configBuilderInjectCommand; private boolean validated; + private final Function loadClassValueExpressionFn; + /** * Creates a bean definition writer. * @@ -803,6 +805,8 @@ public BeanDefinitionWriter(Element beanProducingElement, .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", BeanDefinitionReference.class.getName()).build()) .superclass(TypeDef.parameterized(superType, argumentType)); + + loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(beanDefinitionTypeDef, loadTypeMethods); } /** @@ -1592,7 +1596,7 @@ private StatementDef buildInstance(VariableDef.This aThis, BuildMethodDefinition buildMethodDefinition, Function onBeanInstance, boolean isParametrized) { - StatementDef.DefineAndAssign[] constructorDef= new StatementDef.DefineAndAssign[] { null }; + StatementDef.DefineAndAssign[] constructorDef = new StatementDef.DefineAndAssign[] { null }; Supplier constructorDefSupplier = new Supplier() { @Override @@ -1889,7 +1893,7 @@ private ClassTypeDef createFactoryInterceptor(FactoryBuildMethodDefinition facto private StaticBlock getStaticInitializer() { List statements = new ArrayList<>(); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField(beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataFieldAndInitialize(annotationMetadata, loadClassValueExpressionFn); classDefBuilder.addField(annotationMetadataField); @@ -1969,13 +1973,13 @@ private StaticBlock getStaticInitializer() { classDefBuilder.addField(typeArgumentsField); initStatements.add(beanDefinitionTypeDef.getStaticField(typeArgumentsField) - .put(ExpressionsUtils.stringMapOf( + .put(GenUtils.stringMapOf( typeArguments, true, null, el -> ArgumentExpUtils.pushTypeArgumentElements( annotationMetadata, beanDefinitionTypeDef, ClassElement.of(beanDefinitionName), el, - loadTypeMethods + loadClassValueExpressionFn )) )); failStatements.add(beanDefinitionTypeDef.getStaticField(typeArgumentsField).put(ExpressionDef.nullValue())); @@ -2066,7 +2070,7 @@ private StaticBlock getStaticInitializer() { } // Defaults can be contributed by other static initializers, it should be at the end - AnnotationMetadataGenUtils.writeAnnotationDefault(statements, beanDefinitionTypeDef, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.addAnnotationDefaults(statements, annotationMetadata, loadClassValueExpressionFn); return new StaticBlock( StatementDef.multi(statements), @@ -2239,7 +2243,7 @@ private ExpressionDef getAnnotationClassValues(AnnotationClassValue[] classVa } private ExpressionDef getAnnotationClassValue(AnnotationClassValue annotationClassValue) { - return AnnotationMetadataGenUtils.invokeLoadClassValueMethod(beanDefinitionTypeDef, loadTypeMethods, annotationClassValue); + return loadClassValueExpressionFn.apply(annotationClassValue.getName()); } private ExpressionDef newRecord(Class classType, ExpressionDef... values) { @@ -2973,7 +2977,7 @@ private ExpressionDef getGetValueForPathCall(InjectMethodSignature injectMethodS propertyType, generics, new HashSet<>(), - loadTypeMethods + loadClassValueExpressionFn ), ExpressionDef.constant(propertyPath) ); @@ -3484,7 +3488,7 @@ private ExpressionDef getMethodArgument(ParameterElement entry, AnnotationMetada entry.getGenericType(), annotationMetadata, entry.getGenericType().getTypeArguments(), - loadTypeMethods + loadClassValueExpressionFn ); } @@ -3498,7 +3502,7 @@ private ExpressionDef getFieldArgument(FieldElement fieldElement, AnnotationMeta fieldElement.getGenericType(), annotationMetadata, fieldElement.getGenericType().getTypeArguments(), - loadTypeMethods + loadClassValueExpressionFn ); } return resolveFieldArgument(fieldIndex); @@ -4356,7 +4360,7 @@ private ExpressionDef getNewMethodReference(TypedElement beanType, ClassElement.of(beanFullClassName), beanDefinitionTypeDef, Arrays.asList(methodElement.getParameters()), - loadTypeMethods + loadClassValueExpressionFn ), // 4: annotationMetadata getAnnotationMetadataExpression(annotationMetadata) @@ -4396,7 +4400,7 @@ private ExpressionDef getNewFieldReference(TypedElement declaringType, FieldElem fieldElement.getGenericType(), annotationMetadata, fieldElement.getGenericType().getTypeArguments(), - loadTypeMethods + loadClassValueExpressionFn ) ); } @@ -4426,15 +4430,9 @@ private ExpressionDef getAnnotationMetadataExpression(AnnotationMetadata annotat if (annotationMetadata == AnnotationMetadata.EMPTY_METADATA || annotationMetadata.isEmpty()) { return ExpressionDef.nullValue(); } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { - return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy( - beanDefinitionTypeDef, - annotationMetadataHierarchy, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(annotationMetadataHierarchy, loadClassValueExpressionFn); } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { - return AnnotationMetadataGenUtils.instantiateNewMetadata( - beanDefinitionTypeDef, - mutableAnnotationMetadata, - loadTypeMethods); + return AnnotationMetadataGenUtils.instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn); } else { throw new IllegalStateException("Unknown annotation metadata: " + annotationMetadata.getClass().getName()); } diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java b/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java index dddc259f7aa..69bfe6d72bd 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/ExecutableMethodsDefinitionWriter.java @@ -53,6 +53,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -238,6 +239,8 @@ public final void visitDefinitionEnd() { ClassTypeDef thisType = ClassTypeDef.of(className); + Function loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(thisType, loadTypeMethods); + classDefBuilder = ClassDef.builder(className) .addAnnotation(Generated.class) .superclass(ClassTypeDef.of(AbstractExecutableMethodsDefinition.class)); @@ -261,7 +264,7 @@ public final void visitDefinitionEnd() { metadataMethods.add(MethodDef.builder(methodName) .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) .returns(methodReferenceType) - .build((aThis, methodParameters) -> newNewMethodReference((ClassElement) declaringType, methodElement, loadTypeMethods).returning())); + .build((aThis, methodParameters) -> newNewMethodReference((ClassElement) declaringType, methodElement, loadClassValueExpressionFn).returning())); } metadataMethods.forEach(classDefBuilder::addMethod); @@ -370,7 +373,7 @@ private MethodDef buildGetMethod() { private ExpressionDef newNewMethodReference(ClassElement declaringType, MethodElement methodElement, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { ClassTypeDef methodReferenceType = ClassTypeDef.of(AbstractExecutableMethodsDefinition.MethodReference.class); @@ -394,18 +397,18 @@ private ExpressionDef newNewMethodReference(ClassElement declaringType, // 1: declaringType ExpressionDef.constant(ClassTypeDef.of(declaringType)), // 2: annotationMetadata - annotationMetadata(annotationMetadataWithDefaults, annotationMetadata, loadTypeMethods), + annotationMetadata(annotationMetadataWithDefaults, annotationMetadata, loadClassValueExpressionFn), // 3: methodName ExpressionDef.constant(methodElement.getName()), // 4: return argument - ArgumentExpUtils.pushReturnTypeArgument(annotationMetadataWithDefaults, thisType, declaringType, methodElement.getGenericReturnType(), loadTypeMethods), + ArgumentExpUtils.pushReturnTypeArgument(annotationMetadataWithDefaults, thisType, declaringType, methodElement.getGenericReturnType(), loadClassValueExpressionFn), // 5: arguments (methodElement.getSuspendParameters().length == 0 ? ExpressionDef.nullValue() : ArgumentExpUtils.pushBuildArgumentsForMethod( annotationMetadataWithDefaults, declaringType.getType(), thisType, Arrays.asList(methodElement.getSuspendParameters()), - loadTypeMethods + loadClassValueExpressionFn )), // 6: isAbstract ExpressionDef.constant(methodElement.isAbstract()), @@ -416,27 +419,29 @@ private ExpressionDef newNewMethodReference(ClassElement declaringType, private ExpressionDef annotationMetadata(AnnotationMetadata annotationMetadataWithDefaults, AnnotationMetadata annotationMetadata, - Map loadTypeMethods) { + Function loadClassValueExpressionFn) { if (annotationMetadata == AnnotationMetadata.EMPTY_METADATA || annotationMetadata.isEmpty()) { return ExpressionDef.nullValue(); - } else if (annotationMetadata instanceof AnnotationMetadataReference annotationMetadataReference) { + } + if (annotationMetadata instanceof AnnotationMetadataReference annotationMetadataReference) { return AnnotationMetadataGenUtils.annotationMetadataReference(annotationMetadataReference); - } else if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { + } + if (annotationMetadata instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) { MutableAnnotationMetadata.contributeDefaults( annotationMetadataWithDefaults, annotationMetadataHierarchy ); - return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(thisType, annotationMetadataHierarchy, loadTypeMethods); - } else if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { + return AnnotationMetadataGenUtils.instantiateNewMetadataHierarchy(annotationMetadataHierarchy, loadClassValueExpressionFn); + } + if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) { MutableAnnotationMetadata.contributeDefaults( annotationMetadataWithDefaults, annotationMetadata ); - return AnnotationMetadataGenUtils.instantiateNewMetadata(thisType, mutableAnnotationMetadata, loadTypeMethods); - } else { - throw new IllegalStateException("Unknown metadata: " + annotationMetadata); + return AnnotationMetadataGenUtils.instantiateNewMetadata(mutableAnnotationMetadata, loadClassValueExpressionFn); } + throw new IllegalStateException("Unknown metadata: " + annotationMetadata); } /** diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/GenUtils.java similarity index 99% rename from core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java rename to core-processor/src/main/java/io/micronaut/inject/writer/GenUtils.java index 8ce39f8c2f2..54b36b97760 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/ExpressionsUtils.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/GenUtils.java @@ -40,12 +40,15 @@ * @since 4.8 */ @Internal -public final class ExpressionsUtils { +public final class GenUtils { private static final ClassTypeDef MAP_TYPE = ClassTypeDef.of(Map.class); private static final ClassTypeDef MAP_ENTRY_TYPE = ClassTypeDef.of(Map.Entry.class); private static final ClassTypeDef LIST_TYPE = ClassTypeDef.of(List.class); + private GenUtils() { + } + /** * Create a map of a string key expression. * diff --git a/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java b/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java index c45196b6f36..c5baf3a5126 100644 --- a/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java +++ b/core-processor/src/main/java/io/micronaut/inject/writer/MethodGenUtils.java @@ -53,6 +53,9 @@ public final class MethodGenUtils { Object[].class ); + private MethodGenUtils() { + } + /** * The number of Kotlin defaults masks. * @@ -75,24 +78,6 @@ public static boolean hasKotlinDefaultsParameters(List argumen return arguments.stream().anyMatch(p -> p instanceof KotlinParameterElement kp && kp.hasDefault()); } - public static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringType, - MethodElement methodElement, - ExpressionDef target, - List values, - List hasValuesExpressions) { - int numberOfMasks = MethodGenUtils.calculateNumberOfKotlinDefaultsMasks(List.of(methodElement.getSuspendParameters())); - ExpressionDef[] masks = MethodGenUtils.computeKotlinDefaultsMask(numberOfMasks, List.of(methodElement.getSuspendParameters()), hasValuesExpressions); - List newValues = new ArrayList<>(); - newValues.add(target); - newValues.addAll(values); - newValues.addAll(List.of(masks)); // Bit mask of defaults - newValues.add(ExpressionDef.nullValue()); // Last parameter is just a marker and is always null - - MethodDef defaultKotlinMethod = MethodGenUtils.asDefaultKotlinMethod(TypeDef.of(declaringType), methodElement, numberOfMasks); - - return ClassTypeDef.of(declaringType).invokeStatic(defaultKotlinMethod, newValues); - } - public static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringType, MethodElement methodElement, ExpressionDef target, @@ -168,6 +153,24 @@ public static ExpressionDef invokeBeanConstructor(MethodElement constructor, throw new IllegalStateException("Unknown constructor"); } + private static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringType, + MethodElement methodElement, + ExpressionDef target, + List values, + List hasValuesExpressions) { + int numberOfMasks = MethodGenUtils.calculateNumberOfKotlinDefaultsMasks(List.of(methodElement.getSuspendParameters())); + ExpressionDef[] masks = MethodGenUtils.computeKotlinDefaultsMask(numberOfMasks, List.of(methodElement.getSuspendParameters()), hasValuesExpressions); + List newValues = new ArrayList<>(); + newValues.add(target); + newValues.addAll(values); + newValues.addAll(List.of(masks)); // Bit mask of defaults + newValues.add(ExpressionDef.nullValue()); // Last parameter is just a marker and is always null + + MethodDef defaultKotlinMethod = MethodGenUtils.asDefaultKotlinMethod(TypeDef.of(declaringType), methodElement, numberOfMasks); + + return ClassTypeDef.of(declaringType).invokeStatic(defaultKotlinMethod, newValues); + } + private static List constructorValues(ParameterElement[] constructorArguments, @Nullable List values, @@ -220,15 +223,7 @@ private static List getDefaultKotlinConstructorParameters(ParameterElem return parameters; } - /** - * Create a method for Kotlin default invocation. - * - * @param owningType The owing type - * @param method The method - * @param numberOfMasks The number of default masks - * @return A new method - */ - static MethodDef asDefaultKotlinMethod(TypeDef owningType, MethodElement method, int numberOfMasks) { + private static MethodDef asDefaultKotlinMethod(TypeDef owningType, MethodElement method, int numberOfMasks) { ParameterElement[] prevParameters = method.getSuspendParameters(); List parameters = new ArrayList<>(1 + prevParameters.length + numberOfMasks + 1); parameters.add(owningType); @@ -245,7 +240,7 @@ static MethodDef asDefaultKotlinMethod(TypeDef owningType, MethodElement method, .build(); } - public static ExpressionDef[] computeKotlinDefaultsMask(int numberOfMasks, + private static ExpressionDef[] computeKotlinDefaultsMask(int numberOfMasks, List parameters, @Nullable List hasValuesExpressions) { diff --git a/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java b/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java index 1da4b2a4d18..afcdd9807a1 100644 --- a/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java +++ b/core/src/main/java/io/micronaut/core/reflect/ReflectionUtils.java @@ -208,6 +208,7 @@ public static R invokeMethod(T instance, Method method, Object... argumen * @param The return type * @param The instance type * @return The result + * @since 4.8 */ @UsedByGeneratedCode public static R invokeInaccessibleMethod(T instance, Method method, Object... arguments) { diff --git a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java index 29b615e7180..81f2778c186 100644 --- a/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java +++ b/graal/src/main/java/io/micronaut/graal/reflect/GraalReflectionMetadataWriter.java @@ -26,6 +26,7 @@ import io.micronaut.sourcegen.model.AnnotationDef; import io.micronaut.sourcegen.model.ClassDef; import io.micronaut.sourcegen.model.ClassTypeDef; +import io.micronaut.sourcegen.model.ExpressionDef; import io.micronaut.sourcegen.model.FieldDef; import io.micronaut.sourcegen.model.MethodDef; import io.micronaut.sourcegen.model.StatementDef; @@ -37,8 +38,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; -import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.getAnnotationMetadataMethodDef; +import static io.micronaut.inject.annotation.AnnotationMetadataGenUtils.createGetAnnotationMetadataMethodDef; /** * Generates Runtime executed Graal configuration. @@ -71,26 +73,26 @@ public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOE .addModifiers(Modifier.PUBLIC) .addAnnotation(AnnotationDef.builder(Generated.class).addMember("service", GraalReflectionConfigurer.class.getName()).build()) .addSuperinterface(ClassTypeDef.of(GraalReflectionConfigurer.class)) - .addMethod(getAnnotationMetadataMethodDef(thisType, annotationMetadata)); + .addMethod(createGetAnnotationMetadataMethodDef(thisType, annotationMetadata)); Map loadTypeMethods = new LinkedHashMap<>(); + Function loadClassValueExpressionFn = AnnotationMetadataGenUtils.createLoadClassValueExpressionFn(thisType, loadTypeMethods); // write the static initializers for the annotation metadata List staticInit = new ArrayList<>(); - AnnotationMetadataGenUtils.writeAnnotationDefault(staticInit, thisType, annotationMetadata, loadTypeMethods); + AnnotationMetadataGenUtils.addAnnotationDefaults(staticInit, annotationMetadata, loadClassValueExpressionFn); - FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataField( - thisType, + FieldDef annotationMetadataField = AnnotationMetadataGenUtils.createAnnotationMetadataFieldAndInitialize( annotationMetadata, - loadTypeMethods + loadClassValueExpressionFn ); loadTypeMethods.values().forEach(classDefBuilder::addMethod); if (annotationMetadataField != null) { classDefBuilder.addField(annotationMetadataField); - if (!staticInit.isEmpty()) { - classDefBuilder.addStaticInitializer(StatementDef.multi(staticInit)); - } + } + if (!staticInit.isEmpty()) { + classDefBuilder.addStaticInitializer(StatementDef.multi(staticInit)); } outputStream.write(new ByteCodeWriter().write(classDefBuilder.build())); }