DeclaredBeanElementCreator.java
/*
* Copyright 2017-2022 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.processing;
import io.micronaut.aop.Adapter;
import io.micronaut.aop.InterceptorKind;
import io.micronaut.aop.internal.intercepted.InterceptedMethodUtil;
import io.micronaut.aop.writer.AopProxyWriter;
import io.micronaut.context.annotation.Executable;
import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Value;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NextMajorVersion;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.InjectionPoint;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.inject.writer.BeanDefinitionVisitor;
import io.micronaut.inject.writer.BeanDefinitionWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Ordinary declared bean.
*
* @author Denis Stepanov
* @since 4.0.0
*/
@Internal
class DeclaredBeanElementCreator extends AbstractBeanElementCreator {
private static final String MSG_ADAPTER_METHOD_PREFIX = "Cannot adapt method [";
private static final String MSG_TARGET_METHOD_PREFIX = "] to target method [";
protected AopProxyWriter aopProxyVisitor;
protected final boolean isAopProxy;
private final AtomicInteger adaptedMethodIndex = new AtomicInteger(0);
protected DeclaredBeanElementCreator(ClassElement classElement, VisitorContext visitorContext, boolean isAopProxy) {
super(classElement, visitorContext);
this.isAopProxy = isAopProxy;
}
@Override
public final void buildInternal() {
BeanDefinitionVisitor beanDefinitionVisitor = createBeanDefinitionVisitor();
if (isAopProxy) {
// Always create AOP proxy
getAroundAopProxyVisitor(beanDefinitionVisitor, null);
}
build(beanDefinitionVisitor);
}
/**
* Create a bean definition visitor.
*
* @return the visitor
*/
@NonNull
protected BeanDefinitionVisitor createBeanDefinitionVisitor() {
BeanDefinitionVisitor beanDefinitionWriter = new BeanDefinitionWriter(classElement, visitorContext);
beanDefinitionWriters.add(beanDefinitionWriter);
beanDefinitionWriter.visitTypeArguments(classElement.getAllTypeArguments());
visitAnnotationMetadata(beanDefinitionWriter, classElement.getAnnotationMetadata());
MethodElement constructorElement = classElement.getPrimaryConstructor().orElse(null);
if (constructorElement != null) {
applyConfigurationInjectionIfNecessary(beanDefinitionWriter, constructorElement);
beanDefinitionWriter.visitBeanDefinitionConstructor(constructorElement, constructorElement.isPrivate(), visitorContext);
} else {
beanDefinitionWriter.visitDefaultConstructor(AnnotationMetadata.EMPTY_METADATA, visitorContext);
}
return beanDefinitionWriter;
}
/**
* Create an AOP proxy visitor.
*
* @param visitor the parent visitor
* @param methodElement the method that is originating the AOP proxy
* @return The AOP proxy visitor
*/
protected AopProxyWriter getAroundAopProxyVisitor(BeanDefinitionVisitor visitor, @Nullable MethodElement methodElement) {
if (aopProxyVisitor == null) {
if (classElement.isFinal()) {
throw new ProcessingException(classElement, "Cannot apply AOP advice to final class. Class must be made non-final to support proxying: " + classElement.getName());
}
aopProxyVisitor = createAroundAopProxyWriter(
visitor,
isAopProxy || methodElement == null ? classElement.getAnnotationMetadata() : methodElement.getAnnotationMetadata(),
visitorContext,
false
);
beanDefinitionWriters.add(aopProxyVisitor);
MethodElement constructorElement = classElement.getPrimaryConstructor().orElse(null);
if (constructorElement != null) {
aopProxyVisitor.visitBeanDefinitionConstructor(
constructorElement,
constructorElement.isPrivate(),
visitorContext
);
} else {
aopProxyVisitor.visitDefaultConstructor(
AnnotationMetadata.EMPTY_METADATA,
visitorContext
);
}
aopProxyVisitor.visitSuperBeanDefinition(visitor.getBeanDefinitionName());
}
return aopProxyVisitor;
}
/**
* @return true if the class should be processed as a properties bean
*/
protected boolean processAsProperties() {
return false;
}
private void build(BeanDefinitionVisitor visitor) {
Set<FieldElement> processedFields = new HashSet<>();
ElementQuery<MemberElement> memberQuery = ElementQuery.ALL_FIELD_AND_METHODS.includeHiddenElements();
if (processAsProperties()) {
memberQuery = memberQuery.excludePropertyElements();
for (PropertyElement propertyElement : classElement.getBeanProperties()) {
if (visitPropertyInternal(visitor, propertyElement)) {
propertyElement.getField().ifPresent(processedFields::add);
}
}
} else {
for (PropertyElement propertyElement : classElement.getSyntheticBeanProperties()) {
if (visitPropertyInternal(visitor, propertyElement)) {
propertyElement.getField().ifPresent(processedFields::add);
}
}
}
List<MemberElement> memberElements = new ArrayList<>(classElement.getEnclosedElements(memberQuery));
memberElements.removeAll(processedFields);
for (MemberElement memberElement : memberElements) {
if (memberElement instanceof FieldElement fieldElement) {
visitFieldInternal(visitor, fieldElement);
} else if (memberElement instanceof MethodElement methodElement) {
visitMethodInternal(visitor, methodElement);
} else if (!(memberElement instanceof PropertyElement)) {
throw new IllegalStateException("Unknown element");
}
}
}
private void visitFieldInternal(BeanDefinitionVisitor visitor, FieldElement fieldElement) {
boolean claimed = visitField(visitor, fieldElement);
if (claimed) {
addOriginatingElementIfNecessary(visitor, fieldElement);
}
}
private void visitMethodInternal(BeanDefinitionVisitor visitor, MethodElement methodElement) {
makeInterceptedForValidationIfNeeded(methodElement);
boolean claimed = visitMethod(visitor, methodElement);
if (claimed) {
addOriginatingElementIfNecessary(visitor, methodElement);
}
}
private boolean visitPropertyInternal(BeanDefinitionVisitor visitor, PropertyElement propertyElement) {
boolean claimed = visitProperty(visitor, propertyElement);
if (claimed) {
propertyElement.getReadMethod().ifPresent(element -> addOriginatingElementIfNecessary(visitor, element));
propertyElement.getWriteMethod().ifPresent(element -> addOriginatingElementIfNecessary(visitor, element));
propertyElement.getField().ifPresent(element -> addOriginatingElementIfNecessary(visitor, element));
}
return claimed;
}
/**
* Visit a property.
*
* @param visitor The visitor
* @param propertyElement The property
* @return true if processed
*/
protected boolean visitProperty(BeanDefinitionVisitor visitor, PropertyElement propertyElement) {
boolean claimed = false;
Optional<? extends MemberElement> writeMember = propertyElement.getWriteMember();
if (writeMember.isPresent()) {
claimed |= visitPropertyWriteElement(visitor, propertyElement, writeMember.get());
}
Optional<? extends MemberElement> readMember = propertyElement.getReadMember();
if (readMember.isPresent()) {
boolean readElementClaimed = visitPropertyReadElement(visitor, propertyElement, readMember.get());
claimed |= readElementClaimed;
}
// Process property's field if no methods were processed
Optional<FieldElement> field = propertyElement.getField();
if (!claimed && field.isPresent()) {
FieldElement writeElement = field.get();
claimed = visitField(visitor, writeElement);
}
return claimed;
}
/**
* Makes the method intercepted by the validation advice.
* @param element The method element
*/
protected void makeInterceptedForValidationIfNeeded(MethodElement element) {
// The method with constrains should be intercepted with the validation interceptor
if (element.hasDeclaredAnnotation(ANN_REQUIRES_VALIDATION)) {
element.annotate(ANN_VALIDATED);
}
}
/**
* Visit a property read element.
*
* @param visitor The visitor
* @param propertyElement The property
* @param readElement The read element
* @return true if processed
*/
protected boolean visitPropertyReadElement(BeanDefinitionVisitor visitor,
PropertyElement propertyElement,
MemberElement readElement) {
if (readElement instanceof MethodElement methodReadElement) {
return visitPropertyReadElement(visitor, propertyElement, methodReadElement);
}
return false;
}
/**
* Visit a property method read element.
*
* @param visitor The visitor
* @param propertyElement The property
* @param readElement The read element
* @return true if processed
*/
protected boolean visitPropertyReadElement(BeanDefinitionVisitor visitor,
PropertyElement propertyElement,
MethodElement readElement) {
return visitAopAndExecutableMethod(visitor, readElement);
}
/**
* Visit a property write element.
*
* @param visitor The visitor
* @param propertyElement The property
* @param writeElement The write element
* @return true if processed
*/
protected boolean visitPropertyWriteElement(BeanDefinitionVisitor visitor,
PropertyElement propertyElement,
MemberElement writeElement) {
if (writeElement instanceof MethodElement methodWriteElement) {
return visitPropertyWriteElement(visitor, propertyElement, methodWriteElement);
}
return false;
}
/**
* Visit a property write element.
*
* @param visitor The visitor
* @param propertyElement The property
* @param writeElement The write element
* @return true if processed
*/
@NextMajorVersion("Require @ReflectiveAccess for private methods in Micronaut 4")
protected boolean visitPropertyWriteElement(BeanDefinitionVisitor visitor,
PropertyElement propertyElement,
MethodElement writeElement) {
makeInterceptedForValidationIfNeeded(writeElement);
if (visitInjectAndLifecycleMethod(visitor, writeElement)) {
makeInterceptedForValidationIfNeeded(writeElement);
return true;
} else if (!writeElement.isStatic() && writeElement.getMethodAnnotationMetadata().hasStereotype(AnnotationUtil.QUALIFIER)) {
if (propertyElement.getReadMethod().isPresent() && writeElement.hasStereotype(ANN_REQUIRES_VALIDATION)) {
visitor.setValidated(true);
}
staticMethodCheck(writeElement);
// TODO: Require @ReflectiveAccess for private methods in Micronaut 4
visitMethodInjectionPoint(visitor, writeElement);
return true;
}
return visitAopAndExecutableMethod(visitor, writeElement);
}
/**
* Visit a method.
*
* @param visitor The visitor
* @param methodElement The method
* @return true if processed
*/
protected boolean visitMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) {
if (visitInjectAndLifecycleMethod(visitor, methodElement)) {
return true;
}
return visitAopAndExecutableMethod(visitor, methodElement);
}
@NextMajorVersion("Require @ReflectiveAccess for private methods in Micronaut 4")
private boolean visitInjectAndLifecycleMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) {
// All the cases above are using executable methods
boolean claimed = false;
if (methodElement.hasDeclaredAnnotation(AnnotationUtil.POST_CONSTRUCT)) {
staticMethodCheck(methodElement);
// TODO: Require @ReflectiveAccess for private methods in Micronaut 4
visitor.visitPostConstructMethod(
methodElement.getDeclaringType(),
methodElement,
methodElement.isReflectionRequired(classElement),
visitorContext);
claimed = true;
}
if (methodElement.hasDeclaredAnnotation(AnnotationUtil.PRE_DESTROY)) {
staticMethodCheck(methodElement);
// TODO: Require @ReflectiveAccess for private methods in Micronaut 4
visitor.visitPreDestroyMethod(
methodElement.getDeclaringType(),
methodElement,
methodElement.isReflectionRequired(classElement),
visitorContext
);
claimed = true;
}
if (claimed) {
return true;
}
if (!methodElement.isStatic() && isInjectPointMethod(methodElement)) {
staticMethodCheck(methodElement);
// TODO: Require @ReflectiveAccess for private methods in Micronaut 4
visitMethodInjectionPoint(visitor, methodElement);
return true;
}
return false;
}
/**
* Visit a method injection point.
* @param visitor The visitor
* @param methodElement The method element
*/
protected void visitMethodInjectionPoint(BeanDefinitionVisitor visitor, MethodElement methodElement) {
applyConfigurationInjectionIfNecessary(visitor, methodElement);
visitor.visitMethodInjectionPoint(
methodElement.getDeclaringType(),
methodElement,
methodElement.isReflectionRequired(classElement),
visitorContext
);
}
private boolean visitAopAndExecutableMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) {
if (methodElement.isStatic() && isExplicitlyAnnotatedAsExecutable(methodElement)) {
// Only allow static executable methods when it's explicitly annotated with Executable.class
return false;
}
// This method requires pre-processing. See Executable#processOnStartup()
boolean preprocess = methodElement.isTrue(Executable.class, "processOnStartup");
if (preprocess) {
visitor.setRequiresMethodProcessing(true);
}
if (methodElement.hasStereotype(Adapter.class)) {
staticMethodCheck(methodElement);
visitAdaptedMethod(methodElement);
// Adapter is always an executable method but can also be intercepted so continue with visitors below
}
if (visitAopMethod(visitor, methodElement)) {
return true;
}
return visitExecutableMethod(visitor, methodElement);
}
/**
* Visit an AOP method.
*
* @param visitor The visitor
* @param methodElement The method
* @return true if processed
*/
protected boolean visitAopMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) {
boolean aopDefinedOnClassAndPublicMethod = isAopProxy && (methodElement.isPublic() || methodElement.isPackagePrivate());
AnnotationMetadata methodAnnotationMetadata = methodElement.getMethodAnnotationMetadata();
if (aopDefinedOnClassAndPublicMethod ||
!isAopProxy && InterceptedMethodUtil.hasAroundStereotype(methodAnnotationMetadata) ||
InterceptedMethodUtil.hasDeclaredAroundAdvice(methodAnnotationMetadata) && !classElement.isAbstract()) {
if (methodElement.isFinal()) {
if (InterceptedMethodUtil.hasDeclaredAroundAdvice(methodAnnotationMetadata)) {
throw new ProcessingException(methodElement, "Method defines AOP advice but is declared final. Change the method to be non-final in order for AOP advice to be applied.");
} else if (!methodElement.isSynthetic() && aopDefinedOnClassAndPublicMethod && isDeclaredInThisClass(methodElement)) {
throw new ProcessingException(methodElement, "Public method inherits AOP advice but is declared final. Either make the method non-public or apply AOP advice only to public methods declared on the class.");
}
return false;
} else if (methodElement.isPrivate()) {
throw new ProcessingException(methodElement, "Method annotated as executable but is declared private. Change the method to be non-private in order for AOP advice to be applied.");
} else if (methodElement.isStatic()) {
throw new ProcessingException(methodElement, "Method defines AOP advice but is declared static");
}
AopProxyWriter aopProxyVisitor = getAroundAopProxyVisitor(visitor, methodElement);
visitAroundMethod(aopProxyVisitor, classElement, methodElement);
return true;
}
return false;
}
protected void visitAroundMethod(AopProxyWriter aopProxyWriter, TypedElement beanType, MethodElement methodElement) {
aopProxyWriter.visitInterceptorBinding(
InterceptedMethodUtil.resolveInterceptorBinding(methodElement.getAnnotationMetadata(), InterceptorKind.AROUND)
);
aopProxyWriter.visitAroundMethod(beanType, methodElement);
}
/**
* Apply configuration injection for the constructor.
*
* @param visitor The visitor
* @param constructor The constructor
*/
protected void applyConfigurationInjectionIfNecessary(BeanDefinitionVisitor visitor,
MethodElement constructor) {
// default to do nothing
}
/**
* Is inject point method?
*
* @param memberElement The method
* @return true if it is
*/
protected boolean isInjectPointMethod(MemberElement memberElement) {
return memberElement.hasDeclaredStereotype(AnnotationUtil.INJECT);
}
private void staticMethodCheck(MethodElement methodElement) {
if (methodElement.isStatic()) {
if (!isExplicitlyAnnotatedAsExecutable(methodElement)) {
throw new ProcessingException(methodElement, "Static methods only allowed when annotated with @Executable");
}
failIfMethodNotAccessible(methodElement);
}
}
private void failIfMethodNotAccessible(MethodElement methodElement) {
if (!methodElement.isAccessible(classElement)) {
throw new ProcessingException(methodElement, "Method is not accessible for the invocation. To invoke the method using reflection annotate it with @ReflectiveAccess");
}
}
private static boolean isExplicitlyAnnotatedAsExecutable(MethodElement methodElement) {
return !methodElement.getMethodAnnotationMetadata().hasDeclaredAnnotation(Executable.class);
}
/**
* Visit a field.
*
* @param visitor The visitor
* @param fieldElement The field
* @return true if processed
*/
protected boolean visitField(BeanDefinitionVisitor visitor, FieldElement fieldElement) {
if (fieldElement.isStatic() || fieldElement.isFinal()) {
return false;
}
AnnotationMetadata fieldAnnotationMetadata = fieldElement.getAnnotationMetadata();
boolean isRequired = InjectionPoint.isInjectionRequired(fieldElement);
if (fieldAnnotationMetadata.hasStereotype(Value.class) || fieldAnnotationMetadata.hasStereotype(Property.class)) {
visitor.visitFieldValue(
fieldElement.getDeclaringType(),
fieldElement,
fieldElement.isReflectionRequired(classElement),
!isRequired
);
return true;
}
if (fieldAnnotationMetadata.hasStereotype(AnnotationUtil.INJECT)
|| fieldAnnotationMetadata.hasDeclaredStereotype(AnnotationUtil.QUALIFIER)) {
visitor.visitFieldInjectionPoint(
fieldElement.getDeclaringType(),
fieldElement,
fieldElement.isReflectionRequired(classElement),
visitorContext
);
return true;
}
return false;
}
private void addOriginatingElementIfNecessary(BeanDefinitionVisitor writer, MemberElement memberElement) {
if (!memberElement.isSynthetic() && !isDeclaredInThisClass(memberElement)) {
writer.addOriginatingElement(memberElement.getDeclaringType());
}
}
/**
* Visit an executable method.
*
* @param visitor The visitor
* @param methodElement The method
* @return true if processed
*/
protected boolean visitExecutableMethod(BeanDefinitionVisitor visitor, MethodElement methodElement) {
if (!methodElement.hasStereotype(Executable.class)) {
return false;
}
if (methodElement.isSynthetic()) {
// Synthetic methods cannot be executable as @Executable cannot be put on a field
return false;
}
if (methodElement.getMethodAnnotationMetadata().hasStereotype(Executable.class)) {
// @Executable annotated on the method
// Throw error if it cannot be accessed without the reflection
if (!methodElement.isAccessible()) {
throw new ProcessingException(methodElement, "Method annotated as executable but is declared private. To invoke the method using reflection annotate it with @ReflectiveAccess");
}
} else if (!isDeclaredInThisClass(methodElement) && !methodElement.getDeclaringType().hasStereotype(Executable.class)) {
// @Executable not annotated on the declared class or method
// Only include public methods
if (!methodElement.isPublic()) {
return false;
}
}
// else
// @Executable annotated on the class
// only include own accessible methods or the ones annotated with @ReflectiveAccess
if (methodElement.isAccessible()
|| !methodElement.isPrivate() && methodElement.getClass().getSimpleName().contains("Groovy")) {
visitor.visitExecutableMethod(classElement, methodElement, visitorContext);
}
return true;
}
private boolean isDeclaredInThisClass(MemberElement memberElement) {
return classElement.equals(memberElement.getDeclaringType());
}
private void visitAdaptedMethod(MethodElement sourceMethod) {
AnnotationMetadata methodAnnotationMetadata = sourceMethod.getDeclaredMetadata();
Optional<ClassElement> interfaceToAdaptValue = methodAnnotationMetadata.getValue(Adapter.class, String.class)
.flatMap(clazz -> visitorContext.getClassElement(clazz, visitorContext.getElementAnnotationMetadataFactory().readOnly()));
if (interfaceToAdaptValue.isEmpty()) {
return;
}
ClassElement interfaceToAdapt = interfaceToAdaptValue.get();
if (!interfaceToAdapt.isInterface()) {
throw new ProcessingException(sourceMethod, "Class to adapt [" + interfaceToAdapt.getName() + "] is not an interface");
}
String rootName = classElement.getSimpleName() + '$' + interfaceToAdapt.getSimpleName() + '$' + sourceMethod.getSimpleName();
String beanClassName = rootName + adaptedMethodIndex.incrementAndGet();
AopProxyWriter aopProxyWriter = new AopProxyWriter(
classElement.getPackageName(),
beanClassName,
true,
false,
sourceMethod,
new AnnotationMetadataHierarchy(classElement.getAnnotationMetadata(), methodAnnotationMetadata),
new ClassElement[]{interfaceToAdapt},
visitorContext
);
aopProxyWriter.visitDefaultConstructor(methodAnnotationMetadata, visitorContext);
List<MethodElement> methods = interfaceToAdapt.getEnclosedElements(ElementQuery.ALL_METHODS.onlyAbstract());
if (methods.isEmpty()) {
throw new ProcessingException(sourceMethod, "Interface to adapt [" + interfaceToAdapt.getName() + "] is not a SAM type. No methods found.");
}
if (methods.size() > 1) {
throw new ProcessingException(sourceMethod, "Interface to adapt [" + interfaceToAdapt.getName() + "] is not a SAM type. More than one abstract method declared.");
}
MethodElement targetMethod = methods.iterator().next();
ParameterElement[] sourceParams = sourceMethod.getParameters();
ParameterElement[] targetParams = targetMethod.getParameters();
int paramLen = targetParams.length;
if (paramLen != sourceParams.length) {
throw new ProcessingException(sourceMethod, MSG_ADAPTER_METHOD_PREFIX + sourceMethod + MSG_TARGET_METHOD_PREFIX + targetMethod + "]. Argument lengths don't match.");
}
if (sourceMethod.isSuspend()) {
throw new ProcessingException(sourceMethod, MSG_ADAPTER_METHOD_PREFIX + sourceMethod + MSG_TARGET_METHOD_PREFIX + targetMethod + "]. Kotlin suspend method not supported here.");
}
Map<String, ClassElement> typeVariables = interfaceToAdapt.getTypeArguments();
Map<String, ClassElement> genericTypes = CollectionUtils.newLinkedHashMap(paramLen);
for (int i = 0; i < paramLen; i++) {
ParameterElement targetParam = targetParams[i];
ParameterElement sourceParam = sourceParams[i];
ClassElement targetType = targetParam.getType();
ClassElement targetGenericType = targetParam.getGenericType();
ClassElement sourceType = sourceParam.getGenericType();
// ??? Java returns generic placeholder for the generic type and Groovy from the ordinary type
if (targetGenericType instanceof GenericPlaceholderElement genericPlaceholderElement) {
String variableName = genericPlaceholderElement.getVariableName();
if (typeVariables.containsKey(variableName)) {
genericTypes.put(variableName, sourceType);
}
} else if (targetType instanceof GenericPlaceholderElement genericPlaceholderElement) {
String variableName = genericPlaceholderElement.getVariableName();
if (typeVariables.containsKey(variableName)) {
genericTypes.put(variableName, sourceType);
}
}
if (!sourceType.isAssignable(targetGenericType.getName())) {
throw new ProcessingException(sourceMethod, MSG_ADAPTER_METHOD_PREFIX + sourceMethod + MSG_TARGET_METHOD_PREFIX + targetMethod + "]. Type [" + sourceType.getName() + "] is not a subtype of type [" + targetGenericType.getName() + "] for argument at position " + i);
}
}
if (!genericTypes.isEmpty()) {
aopProxyWriter.visitTypeArguments(Collections.singletonMap(interfaceToAdapt.getName(), genericTypes));
}
AnnotationClassValue<?>[] adaptedArgumentTypes = Arrays.stream(sourceParams)
.map(p -> new AnnotationClassValue<>(getClassName(p.getGenericType())))
.toArray(AnnotationClassValue[]::new);
targetMethod = targetMethod.withNewOwningType(classElement);
targetMethod.annotate(Adapter.class, builder -> {
builder.member(Adapter.InternalAttributes.ADAPTED_BEAN, new AnnotationClassValue<>(getClassName(classElement)));
builder.member(Adapter.InternalAttributes.ADAPTED_METHOD, sourceMethod.getName());
builder.member(Adapter.InternalAttributes.ADAPTED_ARGUMENT_TYPES, adaptedArgumentTypes);
String qualifier = classElement.stringValue(AnnotationUtil.NAMED).orElse(null);
if (StringUtils.isNotEmpty(qualifier)) {
builder.member(Adapter.InternalAttributes.ADAPTED_QUALIFIER, qualifier);
}
});
aopProxyWriter.visitAroundMethod(interfaceToAdapt, targetMethod);
beanDefinitionWriters.add(aopProxyWriter);
}
private static String getClassName(ClassElement element) {
if (element.isArray()) {
return getClassName(element.fromArray()) + "[]";
}
return element.getName();
}
}