Class JavaBox
- All Implemented Interfaces:
Closeable,AutoCloseable
Overview
Each JavaBox instance relies on an underlying JShell instance configured for to parse and execute
scripts, and to hold its state, i.e., the variables, methods, and classes declared by those scripts.
The JavaBox instance is configured for local execution, so scripts
run within the same virtual machine.
Bevcause JavaBox instances always run in local execution mode, they support the direct transfer of Java objects
between the container and the outside world:
- Return values from script execution are returned to the caller
JShellvariables can be read and written directly (viagetVariable()andsetVariable()).
Lifecycle
Usage follows this pattern:
- Instances are created by providing a
Config, an immutable configuration object created viaConfig.Builder. - Instances must be
initialize()'d before use. This builds the underlyingJShellinstance and creates an internal service thread. - Instances must be
close()'d to release resources when no longer needed.
Script Execution
Scripts are executed via execute(). A single script may contain multiple individual expressions,
statements, or declarations; these are called "snippets". The snippets are analyzed and executed one at a time.
The return value from execute() contains a distinct SnippetOutcome for each of the snippets.
Often the last snippet's return value (if any) is considered to be the overall script's "return value".
Suspend and Resume
If a script invokes suspend(), then execute() returns to the caller with the
last snippet outcome being an instance of SnippetOutcome.Suspended. The script can be restarted later by invoking
resume(), which behaves just like execute(), except that it continues the previous
script instead of starting a new one. On the next return, the previously suspended snippet's earlier SnippetOutcome.Suspended
outcome will be overwritten with its new, updated outcome.
If there is a suspended script associated with an instance, any new invocation of execute() will fail.
Instead, suspeneded scripts must be resumed via resume() and allowed to terminate.
Interruption
Both execute() and resume() block the calling thread until the script terminates
or suspends itself. Another thread can interrupt that execution by interrupting the calling thread, or equivalenntly
by invoking interrupt(). If a snippet's execution is interrrupted, its outcome is SnippetOutcome.Interrupted.
A suspended script can also be interrupted, but the script does not awaken immediately. Instead, upon the next call to
resume(), it will terminate immediately and have outcome SnippetOutcome.Interrupted.
Controls
Scripts may be restricted or otherwise transformed using Controls which are specified as part of the initial
Config. Controls can do the following things:
- Inspect and modify all of the bytecode generated from scripts
- Keep state associated with each
JavaBoxinstance - Keep state associated with each
JavaBoxsnippet execution
Every control is given a per-container Control.ContainerContext and a per-execution Control.ExecutionContext.
Controls can modify script bytecode to insert method calls into the control itself, where it can then utilize its
state to decide what to do, etc.
Examples
Here is a simple "Hello, World" example:
Config config = Config.builder().build();
try (JavaBox box = new JavaBox(config)) {
box.initialize();
box.setVariable("target", "World");
ScriptResult result = box.execute("""
String.format("Hello, %s!", target);
""");
System.out.println(result.returnValue()); // prints "Hello, World!"
}
Here is an example that shows how to avoid infinite loops:
// Set up control
Config config = Config.builder()
.withControl(new TimeLimitControl(Duration.ofSeconds(5)))
.build();
// Execute script
ScriptResult result;
try (JavaBox box = new JavaBox(config)) {
box.initialize();
result = box.execute("""
while (true) {
Thread.yield();
}
""");
}
// Check result
switch (result.snippetOutcomes().get(0)) {
case SnippetOutcome.ExceptionThrown e when e.exception() instanceof TimeLimitExceededException
-> System.out.println("infinite loop detected");
}
Thread Safety
Instances are thread safe but single threaded: when simultaneous operations are attempted from multiple threads, only one operation executes at a time. The one-at-a-time operations are:
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()Close this instance.Execute the given script in this container.static Control.ExecutionContextexecutionContextFor(Class<? extends Control> controlType) Obtain the execution context associated with the specifiedControlclass and the script execution occurring in the current thread.protected voidfinishingExecution(Object result, Throwable error) Subclass hook invoked when finishing script execution.Get theConfigassociated with this instance.static JavaBoxGet theJavaBoxinstance associated with the current thread.Get theJShellinstanced associated with this container.getVariable(String varName) Get the value of a variable in this container.voidInitialize this instance.booleanInterrupt the current script execution, if any.booleanisClosed()Determine if this instance is closed.booleanDetermine if this instance is currently executing a script.booleanDetermine if this instance has been initialized.booleanDetermine if this instance has a suspended script.Resume this instance's suspended script.voidsetVariable(String varName, Object varValue) Declare and assign a variable in this container.voidsetVariable(String varName, String varType, Object varValue) Declare and assign a variable in this container.protected voidSubclass hook invoked when starting script execution.static ObjectSuspend the script executing in the current thread.static ObjectObtain the value of a variable being set bysetVariable().
-
Constructor Details
-
JavaBox
Constructor.Instances must be
initialize()d before use.- Parameters:
config- configuration- Throws:
IllegalArgumentException- ifconfigis null
-
-
Method Details
-
getConfig
-
isInitialized
public boolean isInitialized()Determine if this instance has been initialized.- Returns:
- true if this instance is initialized, otherwise false
-
isClosed
public boolean isClosed()Determine if this instance is closed.- Returns:
- true if this instance is closed, otherwise false
-
isExecuting
public boolean isExecuting()Determine if this instance is currently executing a script.A suspended script counts as "currently executing"; use
isSuspended()to detect that situation.- Returns:
- true if this instance has a currently executing script, otherwise false
-
isSuspended
public boolean isSuspended()Determine if this instance has a suspended script.- Returns:
- true if this instance has a currently suspended script, otherwise false
-
getJShell
Get theJShellinstanced associated with this container.- Returns:
- this container's
JShell - Throws:
IllegalStateException- if this instance is not yet initialized
-
getCurrent
Get theJavaBoxinstance associated with the current thread.This method works during
JShellinitialization and script execution.- Throws:
IllegalStateException- if there is no such instance
-
initialize
public void initialize()Initialize this instance.- Throws:
IllegalStateException- if this instance is already initialized or closed
-
close
public void close()Close this instance.If this instance is already closed, or was never initialized, this method does nothing.
If there is a currently executing or suspended script, it will be interrupted and allowed to terminate.
- Specified by:
closein interfaceAutoCloseable- Specified by:
closein interfaceCloseable
-
getVariable
Get the value of a variable in this container.- Parameters:
varName- variable name- Returns:
- variable value
- Throws:
InterruptedException- if the current thread is interruptedIllegalStateException- if this instance is not initialized or closedIllegalArgumentException- ifvarNameis not foundIllegalArgumentException- ifvarNameis not a valid Java identifierIllegalArgumentException- ifvarNameis null- See Also:
-
setVariable
Declare and assign a variable in this container.Equivalent to:
setVariable(varName, null, varValue).- Parameters:
varName- variable namevarValue- variable value- Throws:
InterruptedException- if the current thread is interruptedIllegalStateException- if this instance is not initialized or closedIllegalArgumentException- ifvarNameis not a valid Java identifierIllegalArgumentException- ifvarNameis null- See Also:
-
setVariable
public void setVariable(String varName, String varType, Object varValue) throws InterruptedException Declare and assign a variable in this container.This is basically equivalent to executing the script
"<varType> <varName> = <varValue>;".If
vartypeis null:- The actual type of
varValue(expressed as a string) will be used; this type name must be accessible in the generated script - If
varValueis a non-null primitive wrapper type, the corresponding primitive type is used - If
varValueis null,varwill be used
Using the narrowest possible type for
varTypeis advantageous because it eliminates the need for casting when referring tovarNamein subsequent scripts. However, it's possible thatvarTypeis not accessible in the script environment, e.g., not on the classpath, or a private class. In that case, this method will throw aJavaBoxException. To avoid that, setvarTypeto any accessible supertype (e.g.,"Object"), or use"var"to infer it.- Parameters:
varName- variable namevarType- variable's declared type, or null to infer from actual type; must be accessible in the generated scriptvarValue- variable value- Throws:
InterruptedException- if the current thread is interruptedIllegalStateException- if this instance is not initialized or closedIllegalArgumentException- ifvarNameis not a valid Java identifierIllegalArgumentException- ifvarNameis nullJavaBoxException- if variable assignment fails- See Also:
- The actual type of
-
variableValue
Obtain the value of a variable being set bysetVariable().This method is only used internally; it's
publicso that it can be accessed from JShell scripts.- Returns:
- value of variable being set if any, otherwise null
- Throws:
IllegalStateException- if the current thread is not asetVariable()script thread
-
execute
Execute the given script in this container.The script is broken into individual snippets, which are executed one at a time. Processing stops after the last snippet, or when any snippet has an outcome implementing
SnippetOutcome.HaltsScript. The results from the execution of all snippets attempted up to that point are then returned as aScriptResult.If the current thread is interrupted, then
interrupt()is implicitly invoked.If the script
suspend()s itself, this method returns as described above, the last outcome will be aSnippetOutcome.Suspended, and the script becomes this instance's suspended script. The script must beresume()ed before a new script can be executed. A suspended script can beinterrupt()ed, in which case it will throwThreadDeathas soon as it is resumed.If this method is invoked when this instance has a suspended script, an
IllegalStateExceptionis thrown.- Parameters:
source- the script to execute- Returns:
- result from successful script execution
- Throws:
InterruptedException- if the current thread is interruptedIllegalStateException- if this instance has a suspended scriptIllegalStateException- if this instance is not initialized or closedIllegalArgumentException- ifsourceis null
-
suspend
Suspend the script executing in the current thread.If a script invokes this method, the script will pause and the associated
execute()orresume()invocation that (re)started this script's execution will return to the caller, with the corresponding snippet outcome being aSnippetOutcome.Suspendedcontainingparameter.This instance will then have a suspended script. The suspended script must be
resume()ed before a new script can beexecute()ed. A suspended script can beinterrupt()ed, in which case it will throwThreadDeathas soon as it resumes.- Parameters:
parameter- value to be made available viaSnippetOutcome.Suspended.parameter()- Returns:
- the return value provided to
resume() - Throws:
ThreadDeath- ifinterrupt()orclose()was invoked while suspendedIllegalStateException- if the current thread is not a script execution thread
-
resume
Resume this instance's suspended script.The script's earlier invocation of
suspend()will returnreturnValue, or throwThreadDeathifinterrupt()has been invoked since it was suspended. This method will then block just likeexecute(), i.e., until the script terminates or suspends itself again.The returned
ScriptResultwill include the outcomes from all snippets in the original script, including those that executed before the snippet that invokedsuspend(), followed by the updated outcome of the suspended snippet, followed by any additional outcomes from the script.- Parameters:
returnValue- the value to be returned to the script fromsuspend()- Returns:
- the result from the script's execution
- Throws:
ThreadDeath- ifinterrupt()orclose()was invoked on the associated containerIllegalStateException- if this instance has no currently suspended scriptIllegalStateException- if this instance is not initialized or closed
-
interrupt
public boolean interrupt()Interrupt the current script execution, if any.If there is no current execution, then nothing happens and false is returned. Otherwise, an attempt is made to stop the execution via
JShell.stop(). If successful, the final snippet outcome will beSnippetOutcome.Interrupted. Even in this case, because this operation is asynchronous, the snippet may have actually never started, it may have only partially completed, or it may have fully completed.If this instance has a currently suspeneded script, that script will awaken and throw an immediate
ThreadDeathexception. Note thatSnippetOutcome.Interruptedis the outcome assigned to any snippet that terminates by throwingThreadDeath.If this instance is closed or not initialized, false is returned.
- Returns:
- true if execution was interrupted, false if no execution was occurring
-
executionContextFor
Obtain the execution context associated with the specifiedControlclass and the script execution occurring in the current thread.This method can be used by
Controls that need access to the per-execution or per-container context from within the execution thread, for example, from bytecode woven into script classes.The
Controlclass is used instead of theControlinstance itself to allow invoking this method from woven bytecode. ThecontrolTypemust exactly equal theControlinstance class (not just be assignable from it). If multiple instances of the sameControlclass are configured on a container, then the context associated with the first instance is returned.If a return value from a script's execution is an invokable object, then any subsequent invocations into that object's methods will not be able to obtain any per-execution context using this method, because they will executing outside of the container. Instead, an
IllegalStateExceptionis thrown.- Parameters:
controlType- the control's Java class- Returns:
- the execution context for the control of type
controlType - Throws:
JavaBoxException- if no control having typecontrolTypeis configuredIllegalStateException- if the current thread is not aJavaBoxscript execution thread
-
startingExecution
protected void startingExecution()Subclass hook invoked when starting script execution.This method must not lock this instance or deadlock will result.
The implementation in
JavaBoxdoes nothing. -
finishingExecution
-