| Class | Description |
|---|---|
| GraalSDK |
Integration of NetBeans Scripting
API and GraalVM; see the
tutorial for more details. |
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.
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>
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.
ScriptEnginepython =Scripting.createManager().getEngineByMimeType("text/x-python"); assert python != null : "Install Graal Python via `gu install python`";Stringx = (String) python.eval("\n" + "x = 'Hello World!'\n" + "print(x)\n" + "x\n" ); assert x.equals("Hello World!") : x;
ScriptEnginefound = null; finalScriptEngineManagermanager =Scripting.createManager(); for (ScriptEngineFactoryfactory : manager.getEngineFactories()) { finalStringname = 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:pythone.g. a mixture of standard script engines in the JDK with additional ones provided as GraalVM languages located via dedicated implementation of
EngineProvider
interface
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 pythonAfter invoking this command and downloading the bits, the JVM will be ready to execute Python scripts.
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 finalScriptEngineManagermanager =Scripting.createManager(); // creates two engines connected to each otherScriptEnginejs = manager.getEngineByMimeType("text/javascript"); assert js != null : "Run on GraalVM!";ScriptEnginepython = manager.getEngineByMimeType("text/x-python"); assert python != null : "Install Graal Python via `gu install python`"; // JavaScript function that takes another function as argumentObjectsayHelloRaw = js.eval("(function(subject) {\n" + " return 'Hello ' + subject() + '!';\n" + "})\n"); @SuppressWarnings("unchecked")function.Function<Object, ?> sayHelloFn = ((Invocable)js).getInterface(sayHelloRaw,function.Function.class); // Python function that returns a valueObjectworldFn = python.eval("\n" + "def world():\n" + " return 'World'\n" + "\n" + "world\n" + "" ); // pass Python function as an argument to JavaScript functionStringhelloWorld = (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.
org.netbeans.libs.graalsdk.Scripting interoperation let's you
view each array-like object as List. Just
"cast it"
like in following example:
ScriptEnginejs =Scripting.createManager().getEngineByName("GraalVM:js"); assert js != null : "Run on GraalVM!";Objectraw = 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");
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.
Multiplier in the following code.
@Notes:FunctionalInterfaceinterface Multiplier { int multiply(int a, int b); } public void callJavaScriptFunctionFromJava() throwsException{Invocableinvocable = (Invocable) engine;Stringsrc = "(" + "function (a, b) {\n" + " return a * b;\n" + "})"; // Evaluate JavaScript function definitionObjectjsFunction = 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)); }
Object that can be
"cast"
to a foreign function with a Java type.
Notes:ScriptEnginepython = manager.getEngineByName("GraalVM:python");Invocableinvocable = (Invocable) python;Stringsrc = "" + "def mul(a, b):\n" + " return a * b\n" + "\n" + "mul\n"; // Evaluate Python function definitionObjectpythonFunction = 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));
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.qbinom from the built-in stats package.
@Don't forget to install support for the R language into your GraalVM instance:FunctionalInterfaceinterface BinomQuantile { int qbinom(double q, int count, double prob); } public void callRFunctionFromJava() throwsException{ // FastR currently needs access to native libraries: finalScriptEngineManagermanager =Scripting.newBuilder().allowAllAccess(true).build();ScriptEnginerEngine = manager.getEngineByMimeType("application/x-r"); finalObjectfuncRaw = rEngine.eval("qbinom"); BinomQuantile func = ((Invocable) rEngine).getInterface(funcRaw, BinomQuantile.class); assertEquals(4, func.qbinom(0.37, 10, 0.5)); }
$ /graalvm/bin/gu install
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:
jsFunction) that can be
executed directly,
without giving it a Java type.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.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:
jsFunction that can be
executed directly,
without giving it a Java type.jsFunction returns a JS factory method for class
JSIncrementor that can also be executed directly.Incrementor.invocable.getInterface(Class)
plays an essential role supporting interoperation between Java and guest language data
structures.
This section presents a few examples.
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:
jsFunction) that can be
"cast"
to a foreign function with Java type PointProvider.pointProvider) creates
a JS array, which is returned as a foreign object
with Java type List<Point>.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:
jsFunction that can be
executed directly,
without giving it a Java type.jsFunction returns a JS mock JSON parser function
(assigned to jsMockParser), that can be
"cast"
to a foreign function with Java type ParseJSON.List<Repository>.
Map. Here is an example:
While not type-safe, it is a generic approach able to inspect objects of unknown structure.ScriptEnginepython =Scripting.createManager().getEngineByName("GraalVM:python"); assert python != null : "Install Graal Python via `gu install python`";Objectraw = 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;
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:
jsFunction that can be executed directly with one argument.javaMoment is seen by the JS function as a
foreign object whose public fields are visible.
jsFunction returns a JS number
that can be
"cast"
to a Java Number and then to a Java int.
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.
@FunctionalInterfaceinterface MomentConverter { int toSeconds(Moment moment); } public void accessFieldsOfJavaObjectWithConverter() throwsException{Stringsrc = "\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 finalObjectjsFunction = 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); }
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:
jsFunction that can be executed directly with one argument.Moment.class is seen by the JS function as a
foreign class whose public constructor is visible.
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).