Skip navigation links

Package com.oracle.truffle.tutorial.embedding

Truffle Tutorial: Embedding Truffle Languages in Java

See: Description

Package com.oracle.truffle.tutorial.embedding Description

Truffle Tutorial: Embedding Truffle Languages in Java

This tutorial shows how to embed the Truffle language execution environment in a Java application.

The Truffle environment lets Java interoperate with Truffle-implemented 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.

Related information

  • PolyglotEngine: execution environment for Truffle-implemented languages.
  • Other Truffle Tutorials

    Contents

    Setup

    Download GraalVM, which contains all the necessary pre-built components. Truffle bits are uploaded to Maven central. You can use them from your pom.xml file as:
    <dependency>
        <groupId>com.oracle.truffle</groupId>
        <artifactId>truffle-api</artifactId>
        <version>0.23</version> <!-- or any later version -->
    </dependency>
    <dependency>
        <groupId>com.oracle.truffle</groupId>
        <artifactId>truffle-dsl-processor</artifactId>
        <version>0.23</version> <!-- same version as above -->
        <scope>provided</scope>
    </dependency>
     

    Get started

    Guest language "Hello World!"

    Integrating Truffle into your Java application starts with building an instance of PolyglotEngine, the execution environment for Truffle-implemented languages. You can then use the engine to evaluate guest language source code.

    The following example creates the (literal) JavaScript Source named hello.js, evaluates it, and then "casts" the result to a Java string. You can also create a Source that wraps a file name or URL.

    PolyglotEngine engine = PolyglotEngine.newBuilder().build();
    Source helloSource = Source.newBuilder("'Hello World!'").
        mimeType("text/javascript").
        name("hello.js").
        build();
    PolyglotEngine.Value result = engine.eval(helloSource);
    String resultString = result.as(String.class);
    assert "Hello World!".equals(resultString);
    

    It's a polyglot world

    How to list all available languages? (TBD)

    Add a language

    The set of supported languages is determined by putting their JAR files on a classpath of your application. Some languages are available in central Maven repository. To include them, modify your pom.xml file (if using Maven) dependency section:
     <dependency>
       <groupId>com.oracle.truffle</groupId>
       <artifactId>truffle-sl</artifactId>
       <version>0.23</version>
     </dependency>
     
    In addition to languages found on classpath the GraalVM download comes with highly efficient implementations of JavaScript, Ruby and the R language.

    Hello World in Ruby and JavaScript

    Mixing languages (TBD)

    Call guest language functions from Java

    Tuffle 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() {
        Source src = Source.newBuilder(
            "(function (a, b) {\n" +
            "  return a * b;" +
            "})").mimeType("text/javascript").name("mul.js").build();
    
        // Evaluate JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Create Java access to JavaScript function
        Multiplier mul = jsFunction.as(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 wrapped in a Value 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 Ruby function

    @FunctionalInterface
    interface Multiplier {
        int multiply(int a, int b);
    }
    
    public void callRubyFunctionFromJava() {
        Source src = Source.newBuilder(
            "proc { |a, b|\n" +
            "  a * b" +
            "}").mimeType("application/x-ruby").name("mul.rb").build();
    
        // Evaluate Ruby function definition
        PolyglotEngine.Value rbFunction = engine.eval(src);
    
        // Create Java access to Ruby function
        Multiplier mul = rbFunction.as(Multiplier.class);
    
        assertEquals(42, mul.multiply(6, 7));
        assertEquals(144, mul.multiply(12, 12));
        assertEquals(256, mul.multiply(32, 8));
    }
    
    Notes:
    • Evaluating the Ruby source returns a Ruby Proc of two arguments wrapped in a Value that can be "cast" to a foreign function with a 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() {
        Source src = Source.newBuilder("qbinom").
                mimeType("text/x-r").name("qbinom.R").build();
        BinomQuantile func = engine.eval(src).as(BinomQuantile.class);
        assertEquals(4, func.qbinom(0.37, 10, 0.5));
    }
    

    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() {
        Source src = Source.newBuilder("\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"
        ).name("CountSeconds.js").mimeType("text/javascript").build();
    
        // Evaluate JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Execute the JavaScript function
        PolyglotEngine.Value jsObject = jsFunction.execute();
    
        // Create Java access to the JavaScript object
        Counter counter = jsObject.as(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 wrapped in a Value (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) wrapped in a Value 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. Truffle 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() {
        Source src = Source.newBuilder("\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"
        ).name("Incrementor.js").mimeType("text/javascript").build();
    
        // Evaluate JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Execute the JavaScript function
        PolyglotEngine.Value jsFactory = jsFunction.execute();
    
        // Execute the JavaScript factory to create Java objects
        Incrementor initFive = jsFactory.execute(5).as(Incrementor.class);
        Incrementor initTen = jsFactory.execute(10).as(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 wrapped in a Value (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 wrapped in a Value 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 Value.as(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() {
        Source src = Source.newBuilder("\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"
        ).name("ArrayOfPoints.js").mimeType("text/javascript").build();
    
        // Evaluate the JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Create Java-typed access to the JavaScript function
        PointProvider pointProvider = jsFunction.as(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 wrapped in a Value (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() {
        Source src = Source.newBuilder(
            "(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"
        ).name("github-api-value.js").mimeType("text/javascript").build();
    
        // Evaluate the JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Execute the JavaScript function to create the "mock parser"
        PolyglotEngine.Value jsMockParser = jsFunction.execute();
    
        // Create Java-typed access to the "mock parser"
        ParseJSON mockParser = jsMockParser.as(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 wrapped in a Value (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) wrapped in a Value, 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.

    Access Java from guest languages

    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() {
        Source src = Source.newBuilder("\n"
            + "(function(t) {\n"
            + "  return 3600 * t.hours + 60 * t.minutes + t.seconds;\n"
            + "})\n"
        ).name("MomentToSeconds.js").mimeType("text/javascript").build();
    
        final Moment javaMoment = new Moment(6, 30, 10);
    
        // Evaluate the JavaScript function definition
        PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Execute the JavaScript function, passing a Java object argument
        PolyglotEngine.Value jsSeconds = jsFunction.execute(javaMoment);
    
        // Convert foreign object result to desired Java type
        int seconds = jsSeconds.as(Number.class).intValue();
    
        assertEquals(3600 * 6 + 30 * 60 + 10, seconds);
    }
    
    Notes:
    • Evaluating the JS source returns an anonymous JS function of one argument wrapped in a Value (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() {
        Source src = Source.newBuilder("\n"
            + "(function(t) {\n"
            + "  return 3600 * t.hours + 60 * t.minutes + t.seconds;\n"
            + "})\n"
        ).name("MomentToSeconds.js").mimeType("text/javascript").build();
    
        final Moment javaMoment = new Moment(6, 30, 10);
    
        // Evaluate the JavaScript function definition
        final PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Convert the function to desired Java type
        MomentConverter converter = jsFunction.as(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() {
        Source src = Source.newBuilder("\n"
            + "(function(Moment) {\n"
            + "  return function(h, m, s) {\n"
            + "     return new Moment(h, m, s);\n"
            + "  };\n"
            + "})\n"
        ).name("ConstructMoment.js").mimeType("text/javascript").build();
    
        // Evaluate the JavaScript function definition
        final PolyglotEngine.Value jsFunction = engine.eval(src);
    
        // Create a JavaScript factory for the provided Java class
        final PolyglotEngine.Value jsFactory = jsFunction.execute(Moment.class);
    
        // Convert the JavaScript factory to a Java foreign function
        MomentFactory momentFactory = jsFactory.as(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 wrapped in a Value (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.
  • Since:
    0.25
    Skip navigation links