by

Awesome GraalVM: Create a Java API on top of a JavaScript library

The Graal Polyglot API lets you embed and run code from guest languages in JVM-based host applications. In this article, we will cover how we create a Java API on top of a JavaScript library.

With more than 700 000 packages npmjs is the world’s largest repository of code! You can find literally everything you need (or not).

For our example we will use the excellent chroma.js library. chroma.js is a tiny JavaScript library (but really useful) for dealing with colors!
Here’s an example for a simple read / manipulate / output chain:

chroma('pink').darken().saturate(2).hex() // "#ff6d93"

Pretty cool right ?
Now let’s see how we can use this library in a Java application that runs on GraalVM.

  1. First download and copy chroma.min.js to your project resources.

  2. Add a dependency on the GraalVM SDK (that contains the Polyglot API):

    maven
    <dependency>
      <groupId>org.graalvm</groupId>
      <artifactId>graal-sdk</artifactId>
      <version>1.0.0-rc7</version>
    </dependency>
    gradle
    compile 'org.graalvm:graal-sdk:1.0.0-rc7'

    If you are using another package manager, follow the instructions on Maven Central.

  3. Use the GraalVM Polyglot API to evaluate the chroma library.
    Now that we can use the org.graalvm.polyglot API in our code, we are going to create a singleton class named GraalVMChroma in the org.chroma package:

    package org.chroma;
    
    import org.graalvm.polyglot.Context;
    import org.graalvm.polyglot.Value;
    
    import java.io.IOException;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    public class GraalVMChroma {
    
      private final Value chroma;
      private static GraalVMChroma instance = null;
    
      private GraalVMChroma() {
        Context context = Context.create(); 
        URL chromaResource = Thread.currentThread().getContextClassLoader().getResource("chroma.min.js");
        String content = new String(Files.readAllBytes(Paths.get(chromaResource.toURI()))); 
        context.eval("js", content); 
        chroma = context.eval("js", "chroma"); 
      }
    
      public static GraalVMChroma create() {
        if (instance == null) {
          instance = new GraalVMChroma();
        }
        return instance;
      }
    
      public Value getChroma() {
        return chroma;
      }
    }
    1 Create a GraalVM Context
    2 Read the file chroma.min.js into a String
    3 Evaluate the library code
    4 Get the chroma object as a GraalVM Value

    In the code above, the file chroma.min.js will be read from the classpath and turned into a String. Then we evaluate the chroma library code, using the eval method on the context. And finally, we use eval("js", "chroma") to get the chroma object.

    The GraalVM Value represents a polyglot value that can be accessed using a set of language agnostic operations. Polyglot values have one of the following types:

    • Null

    • Number

    • Boolean

    • String

    • Host Object

    • Proxy Object

    • Native Pointer

    In addition any value may have one or more of the following traits:

    • Array Elements

    • Members

    • Executable

    • Instantiable

    If you want to learn more about GraalVM Value, please read the  official documentation.

    In our case, the chroma object is an Instantiable as we can instantiate a new chroma object with chroma('hotpink').

  4. Build a Java API.
    We create a Chroma interface in the org.chroma package:

    package org.chroma;
    
    import org.graalvm.polyglot.Value;
    
    import java.io.IOException;
    import java.net.URISyntaxException;
    import java.util.List;
    
    public interface Chroma {
    
      Chroma darken();
    
      Chroma saturate(int value);
    
      String hex();
    
      List<Integer> rgba();
    
      static Chroma create(String color) {
        try {
          Value chroma = GraalVMChroma.create().getChroma(); 
          Value instance = chroma.newInstance(color); 
          return new ChromaInstance(instance); 
        } catch (IOException | URISyntaxException e) {
          throw new RuntimeException("Unable to instantiate Chroma GraalVM");
        }
      }
    }
    1 Get the chroma object on the singleton
    2 Instantiate the chroma object with a color
    3 Instantiate a Java class named ChromaInstance with the chroma instance
  5. Implement the API.
    The ChromaInstance implements the Chroma interface:

    package org.chroma;
    
    import org.graalvm.polyglot.Value;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class ChromaInstance implements Chroma {
    
      private Value chromaInstance;
    
      public ChromaInstance(Value chromaInstance) {
        this.chromaInstance = chromaInstance; 
      }
    
      @Override
      public Chroma darken() {
        return new ChromaInstance(chromaInstance.getMember("darken").execute()); 
      }
    
      @Override
      public Chroma saturate(int value) {
        return new ChromaInstance(chromaInstance.getMember("saturate").execute(value)); 
      }
    
      @Override
      public String hex() {
        return chromaInstance.getMember("hex").execute().asString(); 
      }
    
      @Override
      public List<Integer> rgba() {
        List<Integer> result = new ArrayList<>();
        Value value = chromaInstance.getMember("rgba").execute(); 
        if (value.hasArrayElements()) {
          for (int i = 0; i < value.getArraySize(); i++) {
            result.add(value.getArrayElement(i).asInt());
          }
        }
        return result;
      }
    }
    1 Save the reference to the chroma instance
    2 Get the member called darken on the chroma instance and execute the function
    3 Get the member called saturate on the chroma instance and execute the function with a value
    4 Get the member called hex on the chroma instance, execute the function and return a String
    5 Get the member called rgba on the chroma instance, execute the function and build a Java List from the Array Elements Value

And now we can use the chroma library in our Java application as follow:

Chroma.create("pink").darken().saturate(2).hex() // "#ff6d93"

Chroma.create("orange").rgba() // List(255, 165, 0, 1)

Written with GraalVM 1.0.0-rc7 and Java 1.8.
The full source code is available on GitHub: github.com/yuzutech/chroma-graalvm