DispatchWriter.java
/*
* 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.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.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.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.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.IntStream;
/**
* Switch based dispatch writer.
*
* @author Denis Stepanov
* @since 4.7
*/
@Internal
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 ClassTypeDef TYPE_REFLECTION_UTILS = ClassTypeDef.of(ReflectionUtils.class);
private static final Method METHOD_GET_REQUIRED_METHOD = ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getRequiredMethod", Class.class, String.class, Class[].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 Method METHOD_GET_FIELD_VALUE =
ReflectionUtils.getRequiredInternalMethod(ReflectionUtils.class, "getField", Class.class, String.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<DispatchTarget> dispatchTargets = new ArrayList<>();
private boolean hasInterceptedMethod;
/**
* Adds new set field dispatch target.
*
* @param beanField The field
* @return the target index
*/
public int addSetField(FieldElement beanField) {
return addDispatchTarget(new FieldSetDispatchTarget(beanField));
}
/**
* Adds new get field dispatch target.
*
* @param beanField The field
* @return the target index
*/
public int addGetField(FieldElement beanField) {
return addDispatchTarget(new FieldGetDispatchTarget(beanField));
}
/**
* Adds new method dispatch target.
*
* @param declaringType The declaring type
* @param methodElement The method element
* @return the target index
*/
public int addMethod(TypedElement declaringType, MethodElement methodElement) {
return addMethod(declaringType, methodElement, false);
}
/**
* Adds new method dispatch target.
*
* @param declaringType The declaring type
* @param methodElement The method element
* @param useOneDispatch If method should be dispatched using "dispatchOne"
* @return the target index
*/
public int addMethod(TypedElement declaringType, MethodElement methodElement, boolean useOneDispatch) {
DispatchTarget dispatchTarget = findDispatchTarget(declaringType, methodElement, useOneDispatch);
return addDispatchTarget(dispatchTarget);
}
private DispatchTarget findDispatchTarget(TypedElement declaringType, MethodElement methodElement, boolean useOneDispatch) {
List<ParameterElement> 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);
}
/**
* Adds new interceptable method dispatch target.
*
* @param declaringType The declaring type
* @param methodElement The method element
* @param interceptedProxyClassName The interceptedProxyClassName
* @param interceptedProxyBridgeMethodName The interceptedProxyBridgeMethodName
* @return the target index
*/
public int addInterceptedMethod(TypedElement declaringType,
MethodElement methodElement,
String interceptedProxyClassName,
String interceptedProxyBridgeMethodName) {
hasInterceptedMethod = true;
return addDispatchTarget(new InterceptableMethodDispatchTarget(
findDispatchTarget(declaringType, methodElement, false),
declaringType,
methodElement,
interceptedProxyClassName,
interceptedProxyBridgeMethodName)
);
}
/**
* Adds new custom dispatch target.
*
* @param dispatchTarget The dispatch target implementation
* @return the target index
*/
public int addDispatchTarget(DispatchTarget dispatchTarget) {
dispatchTargets.add(dispatchTarget);
return dispatchTargets.size() - 1;
}
@Nullable
public MethodDef buildDispatchMethod() {
List<Map.Entry<DispatchTarget, Integer>> dispatchers = getDispatchers(DispatchTarget::supportsDispatchMulti);
if (dispatchers.isEmpty()) {
return null;
}
return MethodDef.override(DISPATCH_METHOD)
.build((aThis, methodParameters) -> {
VariableDef.MethodParameter methodIndex = methodParameters.get(0);
VariableDef.MethodParameter target = methodParameters.get(1);
VariableDef.MethodParameter argsArray = methodParameters.get(2);
Map<ExpressionDef.Constant, StatementDef> switchCases = CollectionUtils.newHashMap(dispatchers.size());
for (Map.Entry<DispatchTarget, Integer> e : dispatchers) {
int caseIndex = e.getValue();
DispatchTarget dispatchTarget = e.getKey();
StatementDef statementDef = dispatchTarget.dispatch(caseIndex, methodIndex, target, argsArray);
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()
);
});
}
private List<Map.Entry<DispatchTarget, Integer>> getDispatchers(Predicate<DispatchTarget> predicate) {
List<Map.Entry<DispatchTarget, Integer>> result = new ArrayList<>();
int index = 0;
for (DispatchTarget dispatchTarget : dispatchTargets) {
if (predicate.test(dispatchTarget)) {
result.add(Map.entry(dispatchTarget, index));
}
index++;
}
return result;
}
@Nullable
public MethodDef buildDispatchOneMethod() {
List<Map.Entry<DispatchTarget, Integer>> dispatchers = getDispatchers(DispatchTarget::supportsDispatchOne);
if (dispatchers.isEmpty()) {
return null;
}
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<ExpressionDef.Constant, StatementDef> switchCases = CollectionUtils.newHashMap(dispatchers.size());
for (Map.Entry<DispatchTarget, Integer> 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()
);
});
}
@Nullable
public MethodDef buildGetTargetMethodByIndex() {
// Should we include methods that don't require reflection???
List<Map.Entry<DispatchTarget, Integer>> 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<ExpressionDef.Constant, StatementDef> switchCases = CollectionUtils.newHashMap(dispatchers.size());
for (Map.Entry<DispatchTarget, Integer> 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);
}
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<ExpressionDef> 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 {
values.add(
TYPE_REFLECTION_UTILS.getStaticField("EMPTY_CLASS_ARRAY", TypeDef.of(Class[].class))
);
}
return TYPE_REFLECTION_UTILS.invokeStatic(METHOD_GET_REQUIRED_METHOD, values);
}
@Override
public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {
throw new IllegalStateException();
}
/**
* @return all added dispatch targets
*/
public List<DispatchTarget> getDispatchTargets() {
return dispatchTargets;
}
/**
* @return if intercepted method dispatch have been added
*/
public boolean isHasInterceptedMethod() {
return hasInterceptedMethod;
}
/**
* Dispatch target implementation writer.
*/
@Internal
public interface DispatchTarget {
/**
* @return true if writer supports dispatch one.
*/
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 {
/**
* Implement dispatch.
*
* @param target The target
* @param valuesArray The values array
* @return The dispatch statement
*/
@Override
public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) {
ExpressionDef expression = dispatchMultiExpression(target, valuesArray);
return expressionReturning(expression);
}
/**
* Implement dispatch one.
*
* @param caseValue The case value
* @param caseExpression The case expression
* @param target The target
* @param value The value
* @return The dispatch statement
*/
@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();
}
/**
* Implements multi dispatch.
*
* @param target The target
* @param valuesArray The values
* @return THe expression
*/
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()
);
}
/**
* Implements multi dispatch.
*
* @param target The target
* @param values The values
* @return The dispatch expression
*/
protected ExpressionDef dispatchMultiExpression(ExpressionDef target, List<? extends ExpressionDef> 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);
}
/**
* Implements dispatch.
*
* @param target The target
* @return The dispatch expression
*/
protected ExpressionDef dispatchExpression(ExpressionDef target) {
throw new IllegalStateException("Not supported");
}
}
/**
* Field get dispatch target.
*/
@Internal
public static final class FieldGetDispatchTarget extends AbstractDispatchTarget {
@NonNull
final FieldElement beanField;
public FieldGetDispatchTarget(FieldElement beanField) {
this.beanField = beanField;
}
@Override
public boolean supportsDispatchOne() {
return true;
}
@Override
public boolean supportsDispatchMulti() {
return false;
}
@Override
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()) {
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 {
return bean.cast(targetType).field(beanField).cast(propertyType);
}
}
@NonNull
public FieldElement getField() {
return beanField;
}
}
/**
* Field set dispatch target.
*/
@Internal
public static final class FieldSetDispatchTarget extends AbstractDispatchTarget {
@NonNull
final FieldElement beanField;
public FieldSetDispatchTarget(FieldElement beanField) {
this.beanField = beanField;
}
@Override
public boolean supportsDispatchOne() {
return true;
}
@Override
public boolean supportsDispatchMulti() {
return false;
}
@Override
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()) {
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 {
return target.cast(targetType)
.field(beanField)
.put(value.cast(propertyType))
.after(ExpressionDef.nullValue().returning());
}
}
@NonNull
public FieldElement getField() {
return beanField;
}
}
/**
* Method invocation dispatch target.
*/
@Internal
public static final class MethodDispatchTarget extends AbstractDispatchTarget {
final ClassElement declaringType;
final MethodElement methodElement;
private final boolean useOneDispatch;
private MethodDispatchTarget(ClassElement targetType,
MethodElement methodElement,
boolean useOneDispatch) {
this.declaringType = targetType;
this.methodElement = methodElement;
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<? extends ExpressionDef> 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 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<? extends ExpressionDef> values) {
return MethodGenUtils.invokeKotlinDefaultMethod(declaringType, methodElement, target, values);
}
@Override
public ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef value) {
return MethodGenUtils.invokeKotlinDefaultMethod(declaringType, methodElement, target, List.of(value));
}
}
/**
* 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;
}
@Override
public boolean supportsDispatchOne() {
return useOneDispatch;
}
@Override
public boolean supportsDispatchMulti() {
return !useOneDispatch;
}
@Override
public TypedElement getDeclaringType() {
return declaringType;
}
@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()
);
}
}
/**
* Interceptable method invocation dispatch target.
*/
@Internal
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) {
this.declaringType = declaringType;
this.methodElement = methodElement;
this.dispatchTarget = dispatchTarget;
this.interceptedProxyClassName = interceptedProxyClassName;
this.interceptedProxyBridgeMethodName = interceptedProxyBridgeMethodName;
}
@Override
public boolean supportsDispatchOne() {
return false;
}
@Override
public boolean supportsDispatchMulti() {
return true;
}
@Override
public TypedElement getDeclaringType() {
return declaringType;
}
@Override
public MethodElement getMethodElement() {
return methodElement;
}
@Override
public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) {
VariableDef.Field interceptableField = new VariableDef.This()
.field(FIELD_INTERCEPTABLE, TypeDef.of(boolean.class));
ClassTypeDef proxyType = ClassTypeDef.of(interceptedProxyClassName);
return interceptableField.isTrue().and(target.instanceOf(proxyType))
.doIfElse(
invokeProxyBridge(proxyType, target, valuesArray),
dispatchTarget.dispatch(target, valuesArray)
);
}
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()
);
}
return invoke.returning();
}
}
}