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.
-
First download and copy chroma.min.js to your project resources.
-
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>
gradlecompile 'org.graalvm:graal-sdk:1.0.0-rc7'
If you are using another package manager, follow the instructions on Maven Central.
-
Use the GraalVM Polyglot API to evaluate the chroma library.
Now that we can use theorg.graalvm.polyglot
API in our code, we are going to create a singleton class namedGraalVMChroma
in theorg.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 GraalVMValue
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 theeval
method on thecontext
. And finally, we useeval("js", "chroma")
to get thechroma
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 withchroma('hotpink')
. -
-
Build a Java API.
We create aChroma
interface in theorg.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 singleton2 Instantiate the chroma
object with a color3 Instantiate a Java class named ChromaInstance
with the chroma instance -
Implement the API.
TheChromaInstance
implements theChroma
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 function3 Get the member called saturate
on the chroma instance and execute the function with a value4 Get the member called hex
on the chroma instance, execute the function and return aString
5 Get the member called rgba
on the chroma instance, execute the function and build a JavaList
from the Array ElementsValue
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