Skip navigation links
org.netbeans.libs.graalsdk 1.14

Package org.netbeans.libs.graalsdk

Polyglot scripting tutorial.

See: Description

Package org.netbeans.libs.graalsdk Description

Polyglot scripting tutorial.

Polyglot Scripting Tutorial

This tutorial shows how to embed the various languages in a NetBeans or even plain Java application via Scripting helper methods. This environment lets Java interoperate with standard as well as GraalVM based guest languages via foreign objects and foreign functions. For example Java code can directly access guest language methods, objects, classes, and some complex data structures with Java-typed accessors. In the reverse direction, guest language code can access Java objects, classes, and constructors.

This tutorial helps you get started, starting with setup instructions, followed by descriptions of different interoperation scenarios with (working) code examples.

Contents

Setup

The most advanced features that this API provides work in cooperation with GraalVM. Downloading GraalVM and running your (NetBeans) application on GraalVM will give you access to the polyglot features highlighted in this tutorial.

NetBeans modules are uploaded to Maven central. You can use them from your pom.xml file as:

<dependency>
    <groupId>org.netbeans.api</groupId>
    <artifactId>scripting</artifactId>
    <version>11</version> <!-- or any later version -->
</dependency>
 

Get started

Guest language "Hello World!"

Integrating scripting into your Java application starts with building an instance of ScriptEngineManager via Scripting helper method. You can then use the engine to evaluate guest language source code.

The following example evaluates a Python source, let it print a message, returns it, and then "casts" the result to a Java string.

ScriptEngine python = Scripting.createManager().getEngineByMimeType("text/x-python");
assert python != null : "Install Graal Python via `gu install python`";

String x = (String) python.eval("\n"
    + "x = 'Hello World!'\n"
    + "print(x)\n"
    + "x\n"
);

assert x.equals("Hello World!") : x;

It's a polyglot world

How to list all available languages? To obtain all registered engine factories in the system use:

ScriptEngine found = null;
final ScriptEngineManager manager = Scripting.createManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
    final String name = factory.getEngineName();
    System.err.println("Found " + name);
    if (factory.getMimeTypes().contains("text/javascript")) {
        if (name.equals("GraalVM:js")) {
            found = factory.getScriptEngine();
        }
    }
}

When the above code snippet is executed on GraalVM it may print:

Found Oracle Nashorn
Found Graal.js
Found GraalVM:js
Found GraalVM:llvm
Found GraalVM:python
 
e.g. a mixture of standard script engines in the JDK with additional ones provided as GraalVM languages located via dedicated implementation of EngineProvider interface

Add a language

GraalVM download comes with highly efficient implementations of JavaScript. Additional languages like Ruby, the R and Python can be installed via the bin/gu Graal Updater tool:

 $ /graalvm/bin/gu available
 Downloading: Component catalog
 ComponentId              Version             Component name
 ----------------------------------------------------------------
 python                   1.0.0-rc9           Graal.Python
 R                        1.0.0-rc9           FastR
 ruby                     1.0.0-rc9           TruffleRuby
 $ /graalvm/bin/gu install python
 Downloading: Component catalog
 Processing component archive: Component python
 
After invoking this command and downloading the bits, the JVM will be ready to execute Python scripts.

Hello World in Python and JavaScript

The Scripting.createManager() method is your gateway to polyglot world! Just create a manager and it can serve as a hub where various language engines connect together. Following example shows JavaScript and Python interacting with each other:

// creates a single shared manager for two languages
final ScriptEngineManager manager = Scripting.createManager();

// creates two engines connected to each other
ScriptEngine js = manager.getEngineByMimeType("text/javascript");
assert js != null : "Run on GraalVM!";
ScriptEngine python = manager.getEngineByMimeType("text/x-python");
assert python != null : "Install Graal Python via `gu install python`";

// JavaScript function that takes another function as argument
Object sayHelloRaw = js.eval("(function(subject) {\n"
        + "  return 'Hello ' + subject() + '!';\n"
        + "})\n");
@SuppressWarnings("unchecked")
Function<Object, ?> sayHelloFn = ((Invocable)js).getInterface(sayHelloRaw, Function.class);

// Python function that returns a value
Object worldFn = python.eval("\n"
    + "def world():\n"
    + "  return 'World'\n"
    + "\n"
    + "world\n"
    + ""
);

// pass Python function as an argument to JavaScript function
String helloWorld = (String) sayHelloFn.apply(worldFn);

assert "Hello World!".equals(helloWorld) : helloWorld;

Languages provided by GraalVM use the ScriptEngineManager created by org.netbeans.libs.graalsdk.Scripting#createManager() factory method as a connection point to talk to each other and mutually share and use its objects, functions and other services.

Cast Array to List

Dynamic languages may represent array in their own special ways. However the org.netbeans.libs.graalsdk.Scripting interoperation let's you view each array-like object as List. Just "cast it" like in following example:

ScriptEngine js = Scripting.createManager().getEngineByName("GraalVM:js");
assert js != null : "Run on GraalVM!";

Object raw = js.eval("['Hello', 'World']\n");

// convert the raw array into List:
List<?> list = ((Invocable)js).getInterface(raw, List.class);

assert list.size() == 2;
assert list.get(0).equals("Hello");
assert list.get(1).equals("World");

Call guest language functions from Java

org.netbeans.libs.graalsdk.Scripting interoperation lets Java call foreign functions that guest language code exports (details vary across languages). This section presents a few examples.

Define and call a JavaScript function

A function exported from a dynamic language becomes a callable foreign function by giving it a Java type, for example the Java interface Multiplier in the following code.

@FunctionalInterface
interface Multiplier {
    int multiply(int a, int b);
}

public void callJavaScriptFunctionFromJava() throws Exception {
    Invocable invocable = (Invocable) engine;

    String src = "(" +
        "function (a, b) {\n" +
        "  return a * b;\n" +
        "})";

    // Evaluate JavaScript function definition
    Object jsFunction = engine.eval(src);

    // Create Java access to JavaScript function
    Multiplier mul = invocable.getInterface(jsFunction, Multiplier.class);

    assertEquals(42, mul.multiply(6, 7));
    assertEquals(144, mul.multiply(12, 12));
    assertEquals(256, mul.multiply(32, 8));
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of two arguments represented as Object that can be "cast" to a foreign function with a Java type.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

Define and call a Python function

The same example can be rewritten to Python:

ScriptEngine python = manager.getEngineByName("GraalVM:python");
Invocable invocable = (Invocable) python;
String src = "" +
    "def mul(a, b):\n" +
    "  return a * b\n" +
    "\n" +
    "mul\n";

// Evaluate Python function definition
Object pythonFunction = python.eval(src);

// Create Java access to Python function
Multiplier mul = invocable.getInterface(pythonFunction, Multiplier.class);

assertEquals(42, mul.multiply(6, 7));
assertEquals(144, mul.multiply(12, 12));
assertEquals(256, mul.multiply(32, 8));
Notes:
  • Evaluating the Python source defines a mul function and also returns it as a value to the Java code that can "cast" it to a foreign function with a given Java type.

Call an existing R function

In this sample we use a reference to existing R function qbinom from the built-in stats package.

@FunctionalInterface
interface BinomQuantile {
    int qbinom(double q, int count, double prob);
}

public void callRFunctionFromJava() throws Exception {
    // FastR currently needs access to native libraries:
    final ScriptEngineManager manager = Scripting.newBuilder().allowAllAccess(true).build();
    ScriptEngine rEngine = manager.getEngineByMimeType("application/x-r");

    final Object funcRaw = rEngine.eval("qbinom");
    BinomQuantile func = ((Invocable) rEngine).getInterface(funcRaw, BinomQuantile.class);
    assertEquals(4, func.qbinom(0.37, 10, 0.5));
}
Don't forget to install support for the R language into your GraalVM instance:

 $ /graalvm/bin/gu install
 

Call multiple guest language functions with shared state from Java

Often it is necessary to export multiple dynamic language functions that work together, for example by sharing variables. This can be done by giving an exported group of functions a Java type with more than a single method, for example the Java interface Counter in the following code.

interface Counter {
    void addTime(int hours, int minutes, int seconds);
    int timeInSeconds();
}

public void callJavaScriptFunctionsWithSharedStateFromJava() throws Exception {
    String src = "\n"
        + "(function() {\n"
        + "  var seconds = 0;\n"
        + "  function addTime(h, m, s) {\n"
        + "    seconds += 3600 * h;\n"
        + "    seconds += 60 * m;\n"
        + "    seconds += s;\n"
        + "  }\n"
        + "  function time() {\n"
        + "    return seconds;\n"
        + "  }\n"
        + "  return {\n"
        + "    'addTime': addTime,\n"
        + "    'timeInSeconds': time\n"
        + "  }\n"
        + "})\n";

    // Evaluate JavaScript function definition
    Object jsFunction = engine.eval(src);

    // Execute the JavaScript function via its call method
    Object jsObject = ((Invocable)engine).invokeMethod(jsFunction, "call");

    // Create Java access to the JavaScript object
    Counter counter = ((Invocable)engine).getInterface(jsObject, Counter.class);

    counter.addTime(6, 30, 0);
    counter.addTime(9, 0, 0);
    counter.addTime(12, 5, 30);

    assertEquals(99330, counter.timeInSeconds());
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of no arguments assigned to jsFunction) that can be executed directly, without giving it a Java type.
  • Executing jsFunction returns a JS dynamic object (containing two methods and a shared variable) that can be "cast" to a foreign object with a Java type.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

Access guest language classes from Java

Access a JavaScript class

The ECMAScript 6 specification adds the concept of typeless classes to JavaScript. NetBeans org.netbeans.libs.graalsdk.Scripting interoperation allows Java to access fields and functions of a JavaScript class, for example the foreign function factory and class given the Java type Incrementor in the following code.

interface Incrementor {
    int inc();
    int dec();
    int value();
}

public void callJavaScriptClassFactoryFromJava() throws Exception {
    String src = "\n"
        + "(function() {\n"
        + "  class JSIncrementor {\n"
        + "     constructor(init) {\n"
        + "       this.value = init;\n"
        + "     }\n"
        + "     inc() {\n"
        + "       return ++this.value;\n"
        + "     }\n"
        + "     dec() {\n"
        + "       return --this.value;\n"
        + "     }\n"
        + "  }\n"
        + "  return function(init) {\n"
        + "    return new JSIncrementor(init);\n"
        + "  }\n"
        + "})\n";

    // Evaluate JavaScript function definition
    Object jsFunction = engine.eval(src);

    final Invocable inv = (Invocable) engine;

    // Execute the JavaScript function
    Object jsFactory = inv.invokeMethod(jsFunction, "call");

    // Execute the JavaScript factory to create Java objects
    Incrementor initFive = inv.getInterface(
        inv.invokeMethod(jsFactory, "call", null, 5),
        Incrementor.class
    );
    Incrementor initTen = inv.getInterface(
        inv.invokeMethod(jsFactory, "call", null, 10),
        Incrementor.class
    );

    initFive.inc();
    assertEquals("Now at seven", 7, initFive.inc());

    initTen.dec();
    assertEquals("Now at eight", 8, initTen.dec());
    initTen.dec();

    assertEquals("Values are the same", initFive.value(), initTen.value());
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of no arguments assigned to jsFunction that can be executed directly, without giving it a Java type.
  • Executing jsFunction returns a JS factory method for class JSIncrementor that can also be executed directly.
  • Executing the JS factory returns a JS object that can be "cast" to a foreign object with the Java type Incrementor.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

Access guest language data structures from Java

The method invocable.getInterface(Class) plays an essential role supporting interoperation between Java and guest language data structures. This section presents a few examples.

Access a JavaScript Array

The following example demonstrates type-safe Java foreign access to members of a JavaScript array with members of a known type, accessed as a Java List of objects with type given by interface Point.

interface Point {
    int x();
    int y();
}

@FunctionalInterface
interface PointProvider {
    List<Point> createPoints();
}

public void accessJavaScriptArrayWithTypedElementsFromJava() throws Exception {
    String src = "\n"
        + "(function() {\n"
        + "  class Point {\n"
        + "     constructor(x, y) {\n"
        + "       this.x = x;\n"
        + "       this.y = y;\n"
        + "     }\n"
        + "  }\n"
        + "  return [ new Point(30, 15), new Point(5, 7) ];\n"
        + "})\n";

    // Evaluate the JavaScript function definition
    Object jsFunction = engine.eval(src);

    // Create Java-typed access to the JavaScript function
    PointProvider pointProvider = ((Invocable) engine).getInterface(jsFunction, PointProvider.class);

    // Invoke the JavaScript function to generate points
    List<Point> points = pointProvider.createPoints();

    assertEquals("Two points", 2, points.size());

    Point first = points.get(0);
    assertEquals(30, first.x());
    assertEquals(15, first.y());

    Point second = points.get(1);
    assertEquals(5, second.x());
    assertEquals(7, second.y());
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of no arguments assigned to jsFunction) that can be "cast" to a foreign function with Java type PointProvider.
  • Invoking the foreign function (assigned to pointProvider) creates a JS array, which is returned as a foreign object with Java type List<Point>.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

Access a JavaScript JSON structure

This example demonstrates type-safe Java foreign access to a JavaScript JSON-like structure, based on JSON data returned by a GitHub API. The GitHub response contains a list of repository objects. Each repository has an id, name, list of URLs, and a nested structure describing its owner. Java interfaces Repository and Owner define the structure as Java types.

The following Java code is able to inspect a JavaScript JSON data structure generated by "mock parser" in a type-safe way.

interface Repository {
    int id();
    String name();
    Owner owner();
    boolean has_wiki();
    List<String> urls();
}

interface Owner {
    int id();
    String login();
    boolean site_admin();
}

@FunctionalInterface
interface ParseJSON {
    List<Repository> parse();
}

public void accessJavaScriptJSONObjectFromJava() throws Exception {
    String src =
        "(function () { \n" +
        "  return function() {\n" +
        "    return [\n" +
        "      {\n" +
        "        \"id\": 6109440,\n" +
        "        \"name\": \"holssewebsocket\",\n" +
        "        \"owner\": {\n" +
        "          \"login\": \"jersey\",\n" +
        "          \"id\": 399710,\n" +
        "          \"site_admin\": false\n" +
        "        },\n" +
        "        \"urls\": [\n" +
        "          \"https://api.github.com/repos/jersey/hol\",\n" +
        "          \"https://api.github.com/repos/jersey/hol/forks\",\n" +
        "          \"https://api.github.com/repos/jersey/hol/teams\",\n" +
        "        ],\n" +
        "        \"has_wiki\": true\n" +
        "      }\n" +
        "    ]\n" +
        "  };\n" +
        "})\n";

    // Evaluate the JavaScript function definition
    Object jsFunction = engine.eval(src);

    // Execute the JavaScript function to create the "mock parser"
    Object jsMockParser = ((Invocable) engine).invokeMethod(jsFunction, "call");

    // Create Java-typed access to the "mock parser"
    ParseJSON mockParser = ((Invocable) engine).getInterface(jsMockParser, ParseJSON.class);

    List<Repository> repos = mockParser.parse();
    assertEquals("One repo", 1, repos.size());
    assertEquals("holssewebsocket", repos.get(0).name());
    assertTrue("wiki", repos.get(0).has_wiki());
    assertEquals("3 urls", 3, repos.get(0).urls().size());
    final String url1 = repos.get(0).urls().get(0);
    assertEquals("1st", "https://api.github.com/repos/jersey/hol", url1);

    Owner owner = repos.get(0).owner();
    assertNotNull("Owner exists", owner);

    assertEquals("login", "jersey", owner.login());
    assertEquals("id", 399710, owner.id());
    assertFalse(owner.site_admin());
}

Notes:
  • Evaluating the JS source returns an anonymous JS function of no arguments assigned to jsFunction that can be executed directly, without giving it a Java type.
  • Executing jsFunction returns a JS mock JSON parser function (assigned to jsMockParser), that can be "cast" to a foreign function with Java type ParseJSON.
  • Calling the Java-typed mock parser creates a JS data structure, which is returned as a foreign object with Java type List<Repository>.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

View any Object as Map

Each dynamic object coming from a GraalVM language can be "cast" to a Map. Here is an example:

ScriptEngine python = Scripting.createManager().getEngineByName("GraalVM:python");
assert python != null : "Install Graal Python via `gu install python`";

Object raw = python.eval("\n"
    + "class Point:\n"
    + "  x = 1\n"
    + "  y = 2\n"
    + "Point()\n"
);

Map<?,?> point = ((Invocable)python).getInterface(raw, Map.class);
assert ((Number)point.get("x")).intValue() == 1;
assert ((Number)point.get("y")).intValue() == 2;
While not type-safe, it is a generic approach able to inspect objects of unknown structure.

Access Java from guest languages

Just like Java can access guest language objects, the guest languages may access Java objects, their fields and call their methods. Few examples follow.

Access Java fields and methods from JavaScript

Public members of Java objects can be exposed to guest language code as foreign objects, for example Java objects of type Moment in the following example.

public static final class Moment {
    public final int hours;
    public final int minutes;
    public final int seconds;

    public Moment(int hours, int minutes, int seconds) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }
}

public void accessFieldsOfJavaObject() throws Exception {
    String src = "\n"
        + "(function(t) {\n"
        + "  return 3600 * t.hours + 60 * t.minutes + t.seconds;\n"
        + "})\n";

    final Moment javaMoment = new Moment(6, 30, 10);

    // Evaluate the JavaScript function definition
    Object jsFunction = engine.eval(src);

    // Execute the JavaScript function, passing a Java object argument
    Object jsSeconds = ((Invocable)engine).invokeMethod(
        jsFunction, "call", null, javaMoment
    );

    // Convert foreign object result to desired Java type
    int seconds = ((Number) jsSeconds).intValue();

    assertEquals(3600 * 6 + 30 * 60 + 10, seconds);
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of one argument assigned to jsFunction that can be executed directly with one argument.
  • The Java argument javaMoment is seen by the JS function as a foreign object whose public fields are visible.
  • Executing jsFunction returns a JS number that can be "cast" to a Java Number and then to a Java int.
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

The multiple steps needed to convert the result in the above example produces awkward code that can be simplified. Instead of invoking the JS function directly, and "casting" the wrapped JS result, we can instead "cast" the JS function to a Java foreign function (of type MomentConverter) that returns the desired Java type directly, as shown in the following variation.

@FunctionalInterface
interface MomentConverter {
    int toSeconds(Moment moment);
}

public void accessFieldsOfJavaObjectWithConverter() throws Exception {
    String src = "\n"
        + "(function(t) {\n"
        + "  return 3600 * t.hours + 60 * t.minutes + t.seconds;\n"
        + "})\n";

    final Moment javaMoment = new Moment(6, 30, 10);

    // Evaluate the JavaScript function definition
    final Object jsFunction = engine.eval(src);

    // Convert the function to desired Java type
    MomentConverter converter = ((Invocable) engine).getInterface(
        jsFunction, MomentConverter.class
    );

    // Execute the JavaScript function as a Java foreign function
    int seconds = converter.toSeconds(javaMoment);

    assertEquals(3600 * 6 + 30 * 60 + 10, seconds);
}

Access Java constructors and static methods from JavaScript

Dynamic languages can also access the public constructors and public static methods of any Java class for which they are provided a reference. The following example shows JavaScript access to the public constructor of a Java class.

interface MomentFactory {
    Moment create(int h, int m, int s);
}

public void createJavaScriptFactoryForJavaClass() throws Exception {
    String src = "\n"
        + "(function(Moment) {\n"
        + "  return function(h, m, s) {\n"
        + "     return new Moment(h, m, s);\n"
        + "  };\n"
        + "})\n";

    // Evaluate the JavaScript function definition
    final Object jsFunction = engine.eval(src);

    // Create a JavaScript factory for the provided Java class
    final Object jsFactory = ((Invocable) engine).invokeMethod(jsFunction, "call", null, Moment.class);

    // Convert the JavaScript factory to a Java foreign function
    MomentFactory momentFactory = ((Invocable) engine).getInterface(jsFactory, MomentFactory.class);

    final Moment javaMoment = momentFactory.create(6, 30, 10);
    assertEquals("Hours", 6, javaMoment.hours);
    assertEquals("Minutes", 30, javaMoment.minutes);
    assertEquals("Seconds", 10, javaMoment.seconds);
}
Notes:
  • Evaluating the JS source returns an anonymous JS function of one argument assigned to jsFunction that can be executed directly with one argument.
  • The Java class argument Moment.class is seen by the JS function as a foreign class whose public constructor is visible.
  • Executing jsFunction with the Java class argument returns a JS "factory" function (for the Java class) that can be "cast" to the desired Java function type (MomentFactory).
  • Parentheses around the JS function definition keep it out of JavaScript's global scope, so the Java object holds the only reference to it.

Exception handling

Exceptions are thrown in different way, depending on whether the script (guest language) raised the error, or the error came from the host language, e.g. a java object called from the script.
  • Exceptions from the script code are always reported as ScriptException that provides the appropriate details.
  • Checked exceptions from host (java) code, unhandled by the script are raised as some RuntimeException subclasses; the Throwable#getCause then indicates the original cause for the exception.
  • Unchecked exceptions are directly propagated.
    // this is error in Javascript (null dereference), so ScriptException will be thrown,
    // with scripting engine's implementation exception inside.
    try {
        engine.eval(
             "var a = null;\n"
            + "a.call(null)");
    } catch (ScriptException ex) {
    }
    
    // The callback will throw a checked exception - something that happens
    // "outside" the script in the runtime: will throw RuntimeException subclass
    // with the real exception set as cause.
    Callback cb = new Callback();
    try {
        Object jsFunction = engine.eval(
              "(function(cb) {"
            + " cb.io();"
            + "})"
        );
        ((Invocable) engine).invokeMethod(jsFunction, "call", null, cb);
    } catch (RuntimeException ex) {
        // this is a checked java exception; it's just wrapped into a
        // RuntimeException:
        assertTrue(ex.getCause() instanceof IOException);
    } catch (Exception ex) {
        fail("Runtime expected");
    }
    
    // the last exception is a runtime exception originating from java.
    // it will be reported 'as is' or wrapped, depending on the engine
    try {
        Object jsFunction = engine.eval(
              "(function(cb, l) {"
            + " cb.next(l);"
            + "})"
        );
        ((Invocable) engine).invokeMethod(jsFunction, "call", null, cb, new LinkedList());
    } catch (NoSuchElementException ex) {
        // this is a checked java exception; it's thrown unchanged.
    } catch (RuntimeException ex) {
        // ... or wrapped in a Runtime:
        assertTrue(ex.getCause() instanceof NoSuchElementException);
    } catch (Exception ex) {
        fail("NoSuchElement expected");
    }
    
Skip navigation links
org.netbeans.libs.graalsdk 1.14