VS
Size: a a a
VS
SM
AN
SM
VS
AN
VS
VS
Ⓢ
Ⓢ
VS
Ⓢ
SM
I
import kotlin.ResultKt;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.jvm.functions.Function2;
import kotlin.reflect.KFunction;
import kotlin.reflect.full.KCallables;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.GlobalScope;
import kotlinx.coroutines.reactor.MonoKt;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class A {
static Object invokeHandlerMethod(Method method, Object bean, Object... args) {
KFunction<?> function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method));
if (function.isSuspend()) {
InvokeHandleMethodContinuation lambda = new InvokeHandleMethodContinuation(function, bean, args);
return MonoKt.mono(GlobalScope.INSTANCE, EmptyCoroutineContext.INSTANCE, lambda)
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
} else {
List<Object> newArgs = new ArrayList<>();
newArgs.add(bean);
newArgs.addAll(Arrays.asList(args));
return function.call(newArgs.toArray());
}
}
}
class InvokeHandleMethodContinuation implements Continuation<Object>, Function2<CoroutineScope, Continuation<?>, Object> {
/* Continuation of caller coroutine */
private Continuation<?> completion;
/* state-machine label */
int label = 0;
/* 'captured' variables */
final KFunction<?> function;
final Object bean;
final Object[] args;
/* locals, which should be preserved between suspensions */
/* as demonstration, I save all parameters and 'newArgs' */
CoroutineScope scope;
List<Object> newArgs;
InvokeHandleMethodContinuation(KFunction<?> function, Object bean, Object[] args) {
this.function = function;
this.bean = bean;
this.args = args;
}
@NotNull
@Override
public CoroutineContext getContext() {
return completion.getContext();
}
I
@Override
public void resumeWith(@NotNull Object result) {
try {
Object outcome = invokeSuspend(result);
// the coroutine is suspended. Since 'resumeWith' is called on suspended coroutines and its parent is
// also suspended, there is no need to suspend it one more time.
if (outcome == IntrinsicsKt.getCOROUTINE_SUSPENDED()) return;
// coroutine has finished, wake up its parent.
completion.resumeWith(outcome);
} catch (Throwable e) {
completion.resumeWith(ResultKt.createFailure(e));
}
}
// It is easier to keep track of everything if the state-machine itself is in separate method of continuation, even for suspend functions.
// The body of suspend function will be just 'new ContinuationObject(captured variables).invoke(parameters, including completion)'
// BTW, Kotlin/Native and Kotlin/JS implement coroutines exactly this way.
private Object invokeSuspend(@NotNull Object result) {
// Check if we resumed with exception
ResultKt.throwOnFailure(result);
// State-machine itself
// For each suspend call there should be exactly one state in the state-machine.
// The state should be ended be the call followed by suspension check.\
switch (label) {
case 0:
// The variables are (un)spilled automatically as long as they are represented as continuation's fields.
// Ideally, there shall be only one local: 'result'. See suspend call's comment below.
this.newArgs = new ArrayList<>();
this.newArgs.add(this.bean);
this.newArgs.addAll(Arrays.asList(this.args).subList(0, this.args.length - 2));
// Every state in state-machine should increment the label, this way coroutine's resume will resume in
// correct spot
this.label++;
// Pass 'this' as continuation parameter. This way it will be resumed upon child coroutine's completion.
// Coroutines reuse the same variable 'result' to represent both results of suspend call and an argument of
// resumeWith function. This way, the code works correctly for all outcomes: and if the child does not
// suspend at all, and if it calls this object's 'resumeWith' upon completion.
result = KCallables.callSuspend(function, newArgs.toArray(), this);
// Coroutine is suspended
if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) return result;
// FALLTHRU
case 1:
if (result == Unit.INSTANCE) return null;
else return result;
// FALLTHRU, but this should not matter
default:
throw new IllegalStateException("Incorrect label of coroutine: " + label + " when 2 is maximum value");
}
}
@Override
public Object invoke(CoroutineScope coroutineScope, Continuation<?> continuation) {
this.scope = coroutineScope;
this.completion = continuation;
// The initial result does not matter as long as it is not Result.Failure. It gets thrown away anyway.
return this.invokeSuspend(Unit.INSTANCE);
}
}
Ⓢ
SM
Ⓢ
Ⓢ
SM