Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.instancify.scriptify.api.script;

/**
* Defines the structure of a compiled script that can be executed.
*
* @param <T> type of value returned by the script after evaluation
*/
public interface CompiledScript<T> extends AutoCloseable {

/**
* Retrieves the value produced by the script after compilation.
*
* @return the compiled script value
*/
T get();

/**
* Executes the compiled script with the provided arguments.
*
* @param args arguments passed to the script during execution
* @return the result of script execution
*/
T eval(Object... args);

/**
* Closes the compiled script and releases any resources associated with it.
*/
@Override
void close();
}

31 changes: 28 additions & 3 deletions api/src/main/java/com/instancify/scriptify/api/script/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,34 @@ public interface Script<T> {
void addExtraScript(String script);

/**
* Evaluates and executes this script.
* Compiles the script.
*
* @throws ScriptFunctionException If there's an error during script evaluation
* @param script the script to compile
* @return compiled script object
* @throws ScriptException if there's an error during script compiling
*/
T eval(String script) throws ScriptException;
CompiledScript<T> compile(String script) throws ScriptException;

/**
* One-time evaluation of a script without worrying about closing the context.
*
* @param script the script to evaluate
* @return the evaluation result
* @throws ScriptException if there's an error during script evaluation
*/
T evalOneShot(String script) throws ScriptException;

/**
* Evaluates and executes the script.
*
* @param script the script to evaluate
* @return the evaluation result
* @throws ScriptFunctionException if there's an error during script evaluation
* @deprecated For a one-time evaluation of a script, use the Script#evalOneShot method;
* for pre-compiling a script, use Script#compile.
*/
@Deprecated
default T eval(String script) throws ScriptException {
return this.evalOneShot(script);
}
}
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ java {

allprojects {
group = "com.instancify.scriptify"
version = "1.4.4-SNAPSHOT"
version = "1.5.0-SNAPSHOT"
}

subprojects {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.instancify.scriptify.js.graalvm.script;

import com.instancify.scriptify.api.script.CompiledScript;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

public class JsCompiledScript implements CompiledScript<Value> {
private final Context context;
private final Value value;

public JsCompiledScript(Context context, Value value) {
this.context = context;
this.value = value;
}

@Override
public Value get() {
return value;
}

@Override
public Value eval(Object... args) {
if (!value.canExecute()) {
throw new IllegalStateException("Script is not executable");
}
return value.execute(args);
}

@Override
public void close() {
context.close();
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.instancify.scriptify.js.graalvm.script;

import com.instancify.scriptify.api.exception.ScriptException;
import com.instancify.scriptify.api.script.CompiledScript;
import com.instancify.scriptify.api.script.Script;
import com.instancify.scriptify.api.script.ScriptObject;
import com.instancify.scriptify.api.script.constant.ScriptConstant;
Expand Down Expand Up @@ -57,7 +58,7 @@ public void addExtraScript(String script) {
}

@Override
public Value eval(String script) throws ScriptException {
public CompiledScript<Value> compile(String script) throws ScriptException {
Context.Builder builder = Context.newBuilder("js")
.allowHostAccess(HostAccess.newBuilder(HostAccess.ALL)
// Mapping for the ScriptObject class required
Expand Down Expand Up @@ -98,11 +99,16 @@ public Value eval(String script) throws ScriptException {
fullScript.append(script);

try {
return context.eval("js", fullScript.toString());
return new JsCompiledScript(context, context.eval("js", fullScript.toString()));
} catch (Exception e) {
throw new ScriptException(e);
} finally {
context.close();
}
}

@Override
public Value evalOneShot(String script) throws ScriptException {
try (CompiledScript<Value> compiled = compile(script)) {
return compiled.get();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.instancify.scriptify.js.rhino.script;

import com.instancify.scriptify.api.script.CompiledScript;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptableObject;

public class JsCompiledScript implements CompiledScript<Object> {

private final Context context;
private final ScriptableObject scope;
private final Script compiled;

public JsCompiledScript(Context context, ScriptableObject scope, Script compiled) {
this.context = context;
this.scope = scope;
this.compiled = compiled;
}

@Override
public Object get() {
return compiled.exec(context, scope);
}

@Override
public Object eval(Object... args) {
Object result = compiled.exec(context, scope);
if (result instanceof Function function) {
return function.call(context, scope, scope, args);
}
return result;
}

@Override
public void close() {
context.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.instancify.scriptify.js.rhino.script;

import com.instancify.scriptify.api.exception.ScriptException;
import com.instancify.scriptify.api.script.CompiledScript;
import com.instancify.scriptify.api.script.Script;
import com.instancify.scriptify.api.script.constant.ScriptConstant;
import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
Expand All @@ -19,16 +20,11 @@

public class JsScript implements Script<Object> {

private final Context context = Context.enter();
private final ScriptSecurityManager securityManager = new StandardSecurityManager();
private ScriptFunctionManager functionManager = new StandardFunctionManager();
private ScriptConstantManager constantManager = new StandardConstantManager();
private final List<String> extraScript = new ArrayList<>();

public JsScript() {
context.setWrapFactory(new JsWrapFactory());
}

@Override
public ScriptSecurityManager getSecurityManager() {
return securityManager;
Expand Down Expand Up @@ -60,36 +56,45 @@ public void addExtraScript(String script) {
}

@Override
public Object eval(String script) throws ScriptException {
ScriptableObject scope = context.initStandardObjects();

// If security mode is enabled, search all exclusions
// and add the classes that were excluded to JsSecurityClassAccessor
if (securityManager.getSecurityMode()) {
context.setClassShutter(new JsSecurityClassAccessor(securityManager.getExcludes()));
}

for (ScriptFunctionDefinition definition : functionManager.getFunctions().values()) {
scope.put(definition.getFunction().getName(), scope, new JsFunction(this, definition));
}

for (ScriptConstant constant : constantManager.getConstants().values()) {
ScriptableObject.putConstProperty(scope, constant.getName(), constant.getValue());
}

// Building full script including extra script code
StringBuilder fullScript = new StringBuilder();
for (String extra : extraScript) {
fullScript.append(extra).append("\n");
}
fullScript.append(script);

public CompiledScript<Object> compile(String script) throws ScriptException {
try {
return context.evaluateString(scope, fullScript.toString(), null, 1, null);
Context context = Context.enter();
context.setWrapFactory(new JsWrapFactory());

ScriptableObject scope = context.initStandardObjects();

// If security mode is enabled, search all exclusions
// and add the classes that were excluded to JsSecurityClassAccessor
if (securityManager.getSecurityMode()) {
context.setClassShutter(new JsSecurityClassAccessor(securityManager.getExcludes()));
}

for (ScriptFunctionDefinition definition : functionManager.getFunctions().values()) {
scope.put(definition.getFunction().getName(), scope, new JsFunction(this, definition));
}

for (ScriptConstant constant : constantManager.getConstants().values()) {
ScriptableObject.putConstProperty(scope, constant.getName(), constant.getValue());
}

// Building full script including extra script code
StringBuilder fullScript = new StringBuilder();
for (String extra : extraScript) {
fullScript.append(extra).append("\n");
}
fullScript.append(script);

var compiled = context.compileString(fullScript.toString(), "script", 1, null);
return new JsCompiledScript(context, scope, compiled);
} catch (Exception e) {
throw new ScriptException(e);
} finally {
context.close();
}
}

@Override
public Object evalOneShot(String script) throws ScriptException {
try (CompiledScript<Object> compiled = compile(script)) {
return compiled.get();
}
}
}