package jj.script;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.ContinuationPending;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import jj.event.Publisher;
import jj.util.Closer;
/**
* Coordinates processing a continuable script, returning the
* information necessary to restart the script
* @author jason
*
*/
@Singleton
class ContinuationCoordinator implements ContinuationResumer {
private interface ContinuationExecution {
void run(RhinoContext context);
}
private final Publisher publisher;
private final Provider<RhinoContext> contextProvider;
private final CurrentScriptEnvironment env;
private final Map<Class<? extends Continuation>, ContinuationProcessor> continuationProcessors;
private final ContinuationPendingCache cache;
private final IsThread is;
@Inject
ContinuationCoordinator(
final Provider<RhinoContext> contextProvider,
final CurrentScriptEnvironment env,
final Publisher publisher,
final Map<Class<? extends Continuation>, ContinuationProcessor> continuationProcessors,
final ContinuationPendingCache cache,
final IsThread is
) {
this.contextProvider = contextProvider;
this.env = env;
this.publisher = publisher;
this.continuationProcessors = continuationProcessors;
this.cache = cache;
this.is = is;
}
private void log(final Throwable t, final ScriptEnvironment<?> scriptEnvironment) {
publisher.publish(new ScriptExecutionError(scriptEnvironment, t));
}
private PendingKey execute(final ScriptEnvironment<?> scriptEnvironment, final ContinuationExecution execution) {
try (RhinoContext context = contextProvider.get()) {
execution.run(context);
} catch (ContinuationPending continuation) {
return processContinuationState(extractContinuationState(continuation));
} catch (RhinoException re) {
// this is handled inside the RhinoContext
throw re;
} catch (RuntimeException e) {
log(e, scriptEnvironment);
throw e;
}
return null;
}
/**
* continuable script evaluation within the context of {@link ScriptEnvironment}
* @param scriptEnvironment The containing <code>ScriptEnvironment</code>
* @param script The script to evaluate.
* @return A key representing a pending continuation, or null if the execution completed
*/
PendingKey execute(final ScriptEnvironment<?> scriptEnvironment, final Script script) {
assert (scriptEnvironment != null) : "cannot execute without a script execution environment";
assert is.forScriptEnvironment(scriptEnvironment) : "only execute this in the right script environment!";
return execute(scriptEnvironment, context -> {
try (Closer ignored = env.enterScope(scriptEnvironment)) {
context.executeScriptWithContinuations(script, scriptEnvironment.scope());
}
});
}
/**
* continuable <code>Callable</code> execution within the context of {@link ScriptEnvironment}
* @param scriptEnvironment The containing <code>ScriptEnvironment</code>
* @param callable The <code>Callable</code> to execute
* @param args The arguments to the function, if any
* @return A key representing a pending continuation, or null if the execution completed
*/
PendingKey execute(final ScriptEnvironment<?> scriptEnvironment, final Callable callable, final Object...args) {
assert (scriptEnvironment != null) : "cannot execute without a script execution environment";
assert is.forScriptEnvironment(scriptEnvironment) : "only execute this in the right script environment!";
if (callable != null) {
return execute(scriptEnvironment, context -> {
try (Closer ignored = env.enterScope(scriptEnvironment)) {
context.callFunctionWithContinuations(callable, scriptEnvironment.scope(), args);
}
});
}
publisher.publish(new CannotFindFunction(scriptEnvironment));
return null;
}
/**
* Resumes a continuation that was previously saved from an execution in this class
* @param scriptEnvironment The containing <code>ScriptEnvironment</code>
* @param pendingKey The {@link PendingKey} to resume
* @param result The result of the resumption
* @return A key representing a pending continuation, or null if the execution completed
*/
PendingKey resumeContinuation(ScriptEnvironment<?> scriptEnvironment, final PendingKey pendingKey, final Object result) {
assert (scriptEnvironment != null) : "cannot resume without a script execution environment";
assert scriptEnvironment instanceof AbstractScriptEnvironment : "all script environments must be abstract script environments";
assert is.forScriptEnvironment(scriptEnvironment) : "only execute this in the right script environment!";
assert pendingKey != null : "cannot resume without a pending key";
final AbstractScriptEnvironment<?> environment = (AbstractScriptEnvironment<?>)scriptEnvironment;
//noinspection ThrowableResultOfMethodCallIgnored
final ContinuationPending continuation = ((AbstractScriptEnvironment<?>)scriptEnvironment).continuationPending(pendingKey);
if (continuation != null) {
return execute(scriptEnvironment, context -> {
try (Closer ignored = env.enterScope(environment, pendingKey)) {
context.resumeContinuation(continuation.getContinuation(), environment.scope(), result);
}
});
}
publisher.publish(new CannotFindContinuation(scriptEnvironment, pendingKey));
return null;
}
/**
* Kinda weird that this is in here, but it's a convenience for now
* TODO decide if this is okay here
*/
@Override
public void resume(final PendingKey pendingKey, final Object result) {
cache.resume(pendingKey, result);
}
private ContinuationState extractContinuationState(final ContinuationPending continuation) {
final ContinuationState continuationState = (ContinuationState)continuation.getApplicationState();
assert (continuationState != null) : "continuation captured with no state";
return continuationState;
}
private PendingKey processContinuationState(ContinuationState continuationState) {
if (continuationState != null) {
ContinuationProcessor processor = continuationProcessors.get(continuationState.type());
assert processor != null : "could not find a continuation processor of type " + continuationState.type();
processor.process(continuationState);
return continuationState.continuationAs(Continuation.class).pendingKey();
}
return null;
}
}