Skip to content

Releases: eobermuhlner/java-scriptengine

Release 2.0.0

06 Nov 08:53
Compare
Choose a tag to compare

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.class was removed
  • The Automatic-Module-Name: ch.obermuhlner.scriptengine.java header in the MANFEST.MF was 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

06 Sep 17:37
Compare
Choose a tag to compare

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 application
  • Isolation.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() to getCompiledClass()
  • JavaCompiledScript.getInstance() to getCompiledInstance()

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

21 Aug 17:41
Compare
Choose a tag to compare

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

19 Aug 18:27
Compare
Choose a tag to compare

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: the get() method is called
    • class implements Runnable: the run() method is called
    • class has exactly one public method with no arguments: call it

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 World

Compiling

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 #2

Bindings 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: 3

Advanced 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: the get() method is called
  • class implements Runnable: the run() method is called
  • class has exactly one public method 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 ScriptException
  • MethodExecutionStrategy.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: Hello42

Set 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...
Read more

scriptengine-jshell 0.1.0

08 Aug 14:44
Compare
Choose a tag to compare

First release of the JShell script engine.