Releases: eobermuhlner/java-scriptengine
Release 2.0.0
API changes
Backport to Java 8
The java-scriptengine was backported to Java 8.
This has some impact on the Java Module (Jigsaw) support.
- The
module-info.classwas removed - The
Automatic-Module-Name: ch.obermuhlner.scriptengine.javaheader in theMANFEST.MFwas added again
In Java 8 the special permission policy codeBase protocol jrt: is not supported.
Therefore special codeBase for the permission policy
jrt:/ch.obermuhlner.scriptengine.java/memory-class was removed
and the http://ch.obermuhlner/ch.obermuhlner.scriptengine.java/memory-class was used instead.
Enhancements
No enhancements.
Bugfixes
No Bugfix changes.
Examples
Note: The example code is available on github, but not part of the
java-scriptengine library.
No changes in the examples.
Release 1.1.0
API changes
No breaking API changes.
Enhancements
Control visible classes of script during execution (Isolation)
In previous releases the classloader of the caller was not visible
to the script during execution.
It was therefore not possible to use classes of the application
from inside the script.
It is now possible to control the isolation level of the script.
Isolation.CallerClassLoader: the script can see the classes of the
calling applicationIsolation.IsolatedClassLoader: the script can only see JDK classes
and classes declared inside the script
The default behaviour is Isolation.CallerClassLoader.
Renamed to getCompiledClass() and getCompiledInstance()
Renamed the following methods:
JavaCompiledScript.getInstanceClass()togetCompiledClass()JavaCompiledScript.getInstance()togetCompiledInstance()
For backwards compatibility the old methods are still available
but marked as @Deprecated.
They old methods will be removed with the next major release.
Preparation for Java Module (Jigsaw)
In preparation for future support of Java Modules (Jigsaw) the java-scriptengine is now a Java module.
Important: If the application is a Java Module then the java.scriptengine cannot see classes in the application
(in other words Isolation.CallerClassLoader does not work correctly).
module ch.obermuhlner.scriptengine.java {
exports ch.obermuhlner.scriptengine.java;
exports ch.obermuhlner.scriptengine.java.constructor;
exports ch.obermuhlner.scriptengine.java.execution;
exports ch.obermuhlner.scriptengine.java.name;
exports ch.obermuhlner.scriptengine.java.util;
requires transitive java.scripting;
requires java.compiler;
}
The OSGi Export-Package declaration in the MANIFEST.MF exports the
same packages.
Special codeBase for permission policy
The script classes are executed using a special codeBase:
jrt:/ch.obermuhlner.scriptengine.java/memory-class
This allows to grant specific permissions to the script classes.
Here an example policy file:
// global permissions (for the application and on-the-fly compiled script classes)
grant {
permission java.io.FilePermission "<<ALL FILES>>", "read";
};
// permissions for the example application
grant codeBase "file:/C:/Users/obe/git/java-scriptengine/ch.obermuhlner.scriptengine.example/out/production/classes/" {
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "accessSystemModules";
permission java.lang.RuntimePermission "closeClassLoader";
permission java.lang.RuntimePermission "createClassLoader";
permission java.util.PropertyPermission "application.home", "read";
permission java.util.PropertyPermission "env.class.path", "read";
permission java.util.PropertyPermission "java.class.path", "read";
permission java.util.PropertyPermission "java.home", "read";
};
// permissions for the java-scriptengine
grant codeBase "file:/C:/Users/obe/git/java-scriptengine/ch.obermuhlner.scriptengine.java/out/production/classes/" {
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "accessSystemModules";
permission java.lang.RuntimePermission "closeClassLoader";
permission java.lang.RuntimePermission "createClassLoader";
permission java.util.PropertyPermission "application.home", "read";
permission java.util.PropertyPermission "env.class.path", "read";
permission java.util.PropertyPermission "java.class.path", "read";
permission java.util.PropertyPermission "java.home", "read";
permission java.lang.RuntimePermission "exitVM";
};
// permissions for on-the-fly compiled script classes (notice the special URL)
grant codeBase "jrt:/ch.obermuhlner.scriptengine.java/memory-class" {
permission java.lang.RuntimePermission "exitVM";
permission java.util.PropertyPermission "java.home", "read";
};
// permissions for the jdk.compiler module
grant codeBase "jrt:/jdk.compiler" {
permission java.lang.RuntimePermission "closeClassLoader";
permission java.lang.RuntimePermission "createClassLoader";
permission java.util.PropertyPermission "application.home", "read";
permission java.util.PropertyPermission "env.class.path", "read";
permission java.util.PropertyPermission "java.class.path", "read";
permission java.util.PropertyPermission "java.home", "read";
};
Added MethodExecutionStrategy.byMainMethod()
Added MethodExecutionStrategy.byMainMethod() that will call the public static void main(String[] args)
with the specified arguments.
Bugfixes
Fix javadoc in MethodExecutionStrategy byArgumentTypes() and byMatchingArguments()
The javadoc for the methods
MethodExecutionStrategy.byArgumentTypes()MethodExecutionStrategy.byMatchingArguments()
described the return value wrong.
Examples
Note: The example code is available on github, but not part of the
java-scriptengine library.
All of the example code in this documentation is runnable
in the class ScriptEngineExample.
Added ScriptEnginePerformance
An example class ScriptEnginePerformance was added to measure the
performance of the JavaScriptEngine for compilation and evaluation
of scripts.
Release 1.0.1
API changes
ConstructorStrategy may return null to run static methods
The ConstructorStrategy is now allowed to return a null instance.
This indicates that a static method should be called in the
ExecutionStrategy.
The DefaultExecutionStrategy and MethodExecutionStrategy support this behaviour.
Using a NullConstructorStrategy is the most convenient way to do this.
Bugfixes
MethodExecutionStrategy.byMatchingArguments() did not check methodName
MethodExecutionStrategy.byMatchingArguments() is supposed to search for
a method with the specified methodName, but the name was actually ignored.
Engine version
The JavaScriptEngineFactory.getEngineVersion() of release 1.0.0
reported a wrong version "0.0.1".
The new release reports the now correct version "1.0.1".
Enhancements
Improved error messages for ambiguous constructors and methods
The error messages for ambiguous constructors and methods in the
ScriptExceptions thrown by the DefaultConstructorStrategy
and the MethodExecutionStrategy have been improved to list the
ambiguous constructors and methods.
Ambiguous constructors with matching arguments found:
public ch.obermuhlner.scriptengine.java.constructor.DefaultConstructorStrategyTest$TestConstructor(java.lang.String,java.lang.Long,int)
public ch.obermuhlner.scriptengine.java.constructor.DefaultConstructorStrategyTest$TestConstructor(java.lang.String,java.lang.String,int)Ambiguous methods 'doSomething' with matching arguments found:
public java.lang.String ch.obermuhlner.scriptengine.java.execution.MethodExecutionStrategyTest$TestMethod.doSomething(java.lang.String,java.lang.Long,int)
public java.lang.String ch.obermuhlner.scriptengine.java.execution.MethodExecutionStrategyTest$TestMethod.doSomething(java.lang.String,java.lang.String,int)Javadoc
Javadoc was written for all public classes.
Examples
Note: The example code is available on github, but not part of the
java-scriptengine library.
All of the example code in this documentation is runnable
in the class ScriptEngineExample.
Release 1.0.0
Release 1.0.0
The java-scriptengine (not to be confused with a javascript script engine)
compiles and executes Java files at runtime.
The script source is a standard Java class that must follow these rules:
- public class
- constructor with no arguments (default constructor)
- Callable entry point. One of the following:
- class implements
Supplier: theget()method is called - class implements
Runnable: therun()method is called - class has exactly one
publicmethod with no arguments: call it
- class implements
The script class can be arbitrarily named and may be in a named package or the default package.
Note: The scanner that parses the script for package and class names is very simple.
Avoid confusing it with comments that contain the keywords package or public class
or comments between the keywords and the package/class names.
Simple usage
The following code snippet shows a simple usage of the Java script engine:
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
Object result = engine.eval("" +
"public class Script {" +
" public String getMessage() {" +
" return \"Hello World\";" +
" } " +
"}");
System.out.println("Result: " + result);
} catch (ScriptException e) {
e.printStackTrace();
}The console output shows the result of the only method in the Script class.
Result: Hello WorldCompiling
Calling ScriptEngine.eval() multiple times is very efficient because
the same script has to be compiled every time.
The JavaScriptEngine implements the Compilable interface which
allows to compile the script once and run it multiple times.
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
Compilable compiler = (Compilable) engine;
CompiledScript compiledScript = compiler.compile("" +
"public class Script {" +
" private int counter = 1;" +
" public String getMessage() {" +
" return \"Hello World #\" + counter++;" +
" } " +
"}");
Object result1 = compiledScript.eval();
System.out.println("Result1: " + result1);
Object result2 = compiledScript.eval();
System.out.println("Result2: " + result2);
} catch (ScriptException e) {
e.printStackTrace();
}The console output shows that the same instance was called
multiple times (without recompiling the script).
Result1: Hello World #1
Result2: Hello World #2Bindings instance variables (fields)
You can read and write variables, both instance variables (fields) and static variables,
by using Bindings in the script engine.
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
Compilable compiler = (Compilable) engine;
CompiledScript compiledScript = compiler.compile("" +
"public class Script {" +
" public static String message = \"Counting\";" +
" public int counter = 1;" +
" public String getMessage() {" +
" return message + \" #\" + counter++;" +
" } " +
"}");
{
Bindings bindings = engine.createBindings();
Object result = compiledScript.eval(bindings);
System.out.println("Result1: " + result);
System.out.println("Variable1 message: " + bindings.get("message"));
System.out.println("Variable1 counter: " + bindings.get("counter"));
}
{
Bindings bindings = engine.createBindings();
bindings.put("message", "Hello world");
Object result = compiledScript.eval(bindings);
System.out.println("Result2: " + result);
System.out.println("Variable2 message: " + bindings.get("message"));
System.out.println("Variable2 counter: " + bindings.get("counter"));
}
} catch (ScriptException e) {
e.printStackTrace();
}The console output shows that bindings can read and write values
from both instance and static variables of your class.
Result1: Counting #1
Variable1 message: Counting
Variable1 counter: 2
Result2: Hello world #2
Variable2 message: Hello world
Variable2 counter: 3Advanced features of JavaScriptEngine
The JavaScriptEngine has an additional API to control
the execution of the script class.
Set NameStrategy in JavaScriptEngine
You can specify the strategy to determine the name of the script class
from the script.
public interface NameStrategy {
String getFullName(String script) throws ScriptException;
}The default implementation DefaultNameStrategy uses a simple
(regular expression based) scanner
to find the package name and the class name in the script.
Alternatively the FixNameStrategy allows to set an explicit
fully qualified class name.
Set ConstructorStrategy in JavaScriptEngine
You can specify the strategy to construct an actual instance of
the script class.
public interface ConstructorStrategy {
Object construct(Class<?> clazz) throws ScriptException;
}The default implementation DefaultConstructorStrategy
uses the no-argument default constructor.
Additional static constructor methods DefaultConstructorStrategy
allow to use a constructor with explicit arguments.
The following example uses the
convenience DefaultConstructorStrategy.byMatchingArguments()
to to determine a matching constructor
using the given arguments:
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
JavaScriptEngine javaScriptEngine = (JavaScriptEngine) engine;
javaScriptEngine.setConstructorStrategy(DefaultConstructorStrategy.byMatchingArguments("Hello", 42));
Object result = engine.eval("" +
"public class Script {" +
" private final String message;" +
" private final int value;" +
" public Script(String message, int value) {" +
" this.message = message;" +
" this.value = value;" +
" }" +
" public String getMessage() {" +
" return \"Message: \" + message + value;" +
" }" +
"}");
System.out.println("Result: " + result);
} catch (ScriptException e) {
e.printStackTrace();
}Set ExecutionStrategyFactory in JavaScriptEngine
You can specify the strategy to execute the script class instance
by providing a factory that creates an ExecutionStrategy
from a Class<?>.
public interface ExecutionStrategyFactory {
public ExecutionStrategy create(Class<?> clazz) throws ScriptException;
}public interface ExecutionStrategy {
Object execute(Object instance) throws ScriptException;
}The default implementation DefaultExecutionStrategy supports the following:
- class implements
Supplier: theget()method is called - class implements
Runnable: therun()method is called - class has exactly one
publicmethod with no arguments: call it
Alternatively the MethodExecutionStrategy
can be used to call a specific method with its arguments.
Use one of the following static constructor methods:
MethodExecutionStrategy.byMethod(Method method, Object... arguments)MethodExecutionStrategy.byMatchingArguments(Class<?> clazz, String methodName, Object... arguments) throws ScriptExceptionMethodExecutionStrategy.byArgumentTypes(Class<?> clazz, String methodName, Class<?>[] argumentTypes, Object... arguments) throws ScriptException
The MethodExecutionStrategy.byMatchingArguments() is probably
the most convenient way. It determines a matching function
by name and the given arguments (ignoring null arguments).
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
JavaScriptEngine javaScriptEngine = (JavaScriptEngine) engine;
javaScriptEngine.setExecutionStrategyFactory((clazz) -> {
return MethodExecutionStrategy.byMatchingArguments(
clazz,
"getMessage",
"Hello", 42);
});
Object result = engine.eval("" +
"public class Script {" +
" public String getMessage(Object message, int value) {" +
" return \"Message: \" + message + value;" +
" } " +
"}");
System.out.println("Result: " + result);
} catch (ScriptException e) {
e.printStackTrace();
}The console output shows that getMessage("Hello", 42) was called.
Result: Message: Hello42Set ExecutionStrategy in JavaCompiledScript
If you compile the script with Compilable you can
specify the ExecutionStrategy directly on the compiled script
instead of using the ExecutionStrategyFactory on the engine.
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("java");
JavaScriptEngine javaScriptEngine = (JavaScriptEngine) engine;
javaScriptEngine.setExecutionStrategyFactory((clazz) -> {
return MethodExecutionStrategy.byMatchingArguments(
clazz,
"getMessage",
"Hello", 42);
});
JavaCompiledScript compiledScript = javaScriptEngine.compile("" +
"public class Script {" +
" public String getMessage(Object...scriptengine-jshell 0.1.0
First release of the JShell script engine.