言語の埋込み

GraalVMポリグロットAPIを使用すると、JVMベースのホスト・アプリケーションにゲスト言語のコードを埋め込んで実行できます。

この項では、GraalVMで実行され、ゲスト言語を直接コールするホスト・アプリケーションをJavaで作成する方法を学習します。各コード例の下にあるタブを使用して、JavaScript、R、RubyおよびPythonのいずれかを選択できます。

ノート: ポリグロット埋込みの使用方法の説明は、GraalVM for JDK 21 (23.1.1)リリースで改訂されました。古いバージョンのGraalVMをまだ使用している場合は、正しいバージョンのドキュメントが表示されていることを確認してください。変更の詳細は、リリース・ノートを参照してください。

依存性の設定

GraalVMポリグロットAPIバージョン23.1.0以降、必要なアーティファクトはすべてMaven Centralから直接ダウンロードできます。埋込みに関連するすべてのアーティファクトは、Maven依存関係グループorg.graalvm.polyglotにあります。実行可能な完全な例は、GitHubのポリグロット埋込みのデモンストレーションを参照してください。

プロジェクトに配置できるMaven依存関係設定の例を次に示します。

<dependency> 
	<groupId>org.graalvm.polyglot</groupId> 
	<artifactId>polyglot</artifactId> 
	<version>23.1.1</version> 
</dependency>
<dependency> 
	<groupId>org.graalvm.polyglot</groupId> 
	<!-- Select language: js, ruby, python, java, llvm, wasm, languages-->
	<artifactId>js</artifactId> 
	<version>23.1.1</version> 
	<type>pom</type>
</dependency>
<!-- add additional languages if needed -->
<dependency> 
	<groupId>org.graalvm.polyglot</groupId> 
	<!-- Select tools: profiler, inspect, coverage, dap, tools -->
	<artifactId>tools</artifactId> 
	<version>23.1.1</version> 
	<type>pom</type>
</dependency>
<!-- add specific tools if needed -->

言語およびツールの依存関係では、GraalVM Free Terms and Conditions (GFTC)ライセンスを使用します。かわりにコミュニティ・ライセンス・バージョンを使用するには、各アーティファクト(js-communityなど)に-community接尾辞を追加します。ポリグロット分離アーティファクトにアクセスするには、かわりに-isolate接尾辞(js-isolateなど)を使用します。

アーティファクトpolyglotおよびtoolsには、使用可能なすべての言語とツールが依存関係として含まれています。このアーティファクトは、メジャー・リリース間で増減する場合があります。本番デプロイメントに必要な言語のみを選択することをお薦めします。

pomタイプは、言語またはツールの依存関係に必須です。

また、Javaモジュールを使用する場合、module-info.javaファイルにはorg.graalvm.polyglotが必要です。

module com.mycompany.app {
  requires org.graalvm.polyglot;
}

Truffleランタイム最適化を使用して構成を実行できるかどうかは、使用するGraalVM JDKによって異なります。詳細は、「ランタイム・コンパイル」の項を参照してください。

可能な場合には必ず、モジュールとモジュールパスを使用して、ポリグロット埋込みを構成することをお薦めします。かわりにクラスパスからポリグロットを使用すると、そのクラスパス上のすべてのライブラリで、安全でないAPIへのアクセスが可能になることに注意してください。まだアプリケーションをモジュール化していない場合は、クラスとモジュールパスをハイブリッドで使用できます。たとえば:

$JAVA_HOME/bin/java -classpath=lib --module-path=lib/polyglot --add-modules=org.graalvm.polyglot ...

この例では、lib/polyglotフォルダに、ポリグロットと言語のjarがすべて含まれている必要があります。クラスパスからポリグロット・クラスにアクセスするには、--add-modules=org.graalvm.polyglot JVMオプションも指定する必要があります。クラスパス上のnative-imageポリグロット・モジュールを使用している場合は、自動的にそのモジュールパスにアップグレードされます。

Maven Assemblyプラグインを使用するなどして、ポリグロット・ライブラリから単一のuber Jarを作成することは可能ではありますが、お薦めはしません。また、Uber jarは、ネイティブイメージの作成との組合せではサポートされません。

ポリグロット・アプリケーションのコンパイルおよび実行

GraalVMでは、Truffle言語実装フレームワークで実装された任意の言語で記述されたポリグロット・アプリケーションを実行できます。これらの言語は、以降、ゲスト言語と呼びます。

この項のステップを実行して、GraalVMで実行され、プログラミング言語の相互運用性を示すサンプルのポリグロット・アプリケーションを作成します。

  1. Mavenを使用して新しいJavaプロジェクトを作成します。

  2. polyglot-embedding-demoリポジトリをクローニングします。
     git clone https://github.com/graalvm/polyglot-embedding-demo.git
    
  3. コード例を「メイン・クラス」に挿入します。

  4. Mavenのpom.xml依存関係構成を更新して、前の項の説明に従って実行する言語を含めます。

  5. GraalVM JDKを指すようにJAVA_HOME環境変数を設定して、GraalVMをダウンロードして設定します

  6. mvn package exec:execを実行して、サンプル・コードをビルドおよび実行します。

これで、GraalVMで実行されるJavaホスト・アプリケーションとゲスト言語コードで構成されたポリグロット・アプリケーションが作成されました。このアプリケーションを他のコード例で使用して、ポリグロットAPIのさらに高度な機能をデモンストレーションできます。

ゲスト言語関数のJava値としての定義

ポリグロット・アプリケーションを使用すると、あるプログラミング言語から値を取得し、それを他の言語で使用できます。

この項のコード例をポリグロット・アプリケーションで使用して、ポリグロットAPIがJavaScript、R、RubyまたはPython関数をJava値としてどのように戻すことができるかを示します。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_js {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("js", "x => x+1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_R {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    Value function = context.eval("R", "function(x) x + 1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_ruby {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("ruby", "proc { |x| x + 1 }");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
 // END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_python {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("python", "lambda x: x + 1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
 // END-SNIPPET
    }
}

  

 このコードでは:

Javaからゲスト言語への直接アクセス

ポリグロット・アプリケーションは、ほとんどの言語の型へのアクセスに対応し、関数に限定されません。Javaなどのホスト言語は、ポリグロット・アプリケーションに埋め込まれたゲスト言語の値に直接アクセスできます。

この項のコード例をポリグロット・アプリケーションで使用して、ポリグロットAPIがオブジェクト、数値、文字列および配列にどのようにアクセスできるかを示します。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_js_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("js", 
                    "({ "                   +
                        "id   : 42, "       +
                        "text : '42', "     +
                        "arr  : [1,42,3] "  +
                    "})");
    assert result.hasMembers();

    int id = result.getMember("id").asInt();
    assert id == 42;

    String text = result.getMember("text").asString();
    assert text.equals("42");

    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_R_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    Value result = context.eval("R", 
                    "list("                +
                        "id   = 42, "      +
                        "text = '42', "    +
                        "arr  = c(1,42,3)" +
                    ")");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_ruby_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("ruby", 
                    "o = Struct.new(:id, :text, :arr).new(" +
                        "42, "       +
                        "'42', "     +
                        "[1,42,3] "  +
                    ")");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_python_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("python", 
                    "type('obj', (object,), {" +
                        "'id'  : 42, "         +
                        "'text': '42', "       +
                        "'arr' : [1,42,3]"     +
                    "})()");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  

 このコードでは:

ゲスト言語からJavaへのアクセス

ポリグロット・アプリケーションは、ゲスト言語とホスト言語の間の双方向アクセスを提供します。その結果、Javaオブジェクトをゲスト言語に渡すことができます。

ポリグロットAPIはデフォルトでセキュアであるため、デフォルトの構成ではアクセスが制限されています。Javaオブジェクトのパブリック・メソッドまたはパブリック・フィールドへのアクセスをゲスト言語に許可するには、コンテキストのビルド時にallowAllAccess(true)を明示的に指定する必要があります。このモードでは、ゲスト言語コードは、ホストJavaコードがアクセスできるすべてのリソースにアクセスできます。

この項のコード例をポリグロット・アプリケーションで使用して、ゲスト言語がプリミティブJava値、オブジェクト、配列および関数型インタフェースにどのようにアクセスできるかを示します。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_js {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getBindings("js").putMember("javaObj", new MyClass());
        boolean valid = context.eval("js",
               "    javaObj.id         == 42"          +
               " && javaObj.text       == '42'"        +
               " && javaObj.arr[1]     == 42"          +
               " && javaObj.ret42()    == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_R {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getBindings("R").putMember("javaObj", new MyClass());
        boolean valid = context.eval("R",
               "    javaObj$id         == 42"   +
               " && javaObj$text       == '42'" +
               " && javaObj$arr[[2]]   == 42"   +
               " && javaObj$ret42()    == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_ruby {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getPolyglotBindings().putMember("javaObj", new MyClass());
        boolean valid = context.eval("ruby",
               "javaObj = Polyglot.import('javaObj')\n" +
               "    javaObj[:id]         == 42"         +
               " && javaObj[:text]       == '42'"       +
               " && javaObj[:arr][1]     == 42"         +
               " && javaObj[:ret42].call == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}
 
  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_python {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getPolyglotBindings().putMember("javaObj", new MyClass());
        boolean valid = context.eval("python",
               "import polyglot \n"                            +
               "javaObj =  polyglot.import_value('javaObj')\n" +
               "javaObj.id                   == 42"            +
               " and javaObj.text            == '42'"          +
               " and javaObj.arr[1]          == 42"            +
               " and javaObj.ret42() == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  

 このコードでは:

ゲスト言語からのJava型のルックアップ

ゲスト言語にJavaオブジェクトを渡す以外に、ゲスト言語でのJava型のルックアップを許可できます。

この項のコード例をポリグロット・アプリケーションで使用して、ゲスト言語でどのようにJava型をルックアップしてインスタンス化するかを示します。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_js {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("js",
            "var BigDecimal = Java.type('java.math.BigDecimal');" +
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_R {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("R",
            "BigDecimal = java.type('java.math.BigDecimal');\n" + 
            "BigDecimal$valueOf(10)$pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_ruby {

public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("ruby",
            "BigDecimal = Java.type('java.math.BigDecimal')\n" + 
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
 
  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_python {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("python",
            "import java\n" +
            "BigDecimal = java.type('java.math.BigDecimal')\n" + 
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  

 このコードでは:

ポリグロット・プロキシを使用して計算された配列

ポリグロットAPIには、オブジェクト、配列、ネイティブ・オブジェクト、プリミティブなどのゲスト言語の型を模倣することでJavaの相互運用性をカスタマイズできるポリグロット・プロキシ・インタフェースが含まれています。

この項のコード例をポリグロット・アプリケーションで使用して、値を遅延計算する配列の実装方法を確認します。

ノート: ポリグロットAPIは、JVM上またはネイティブ・イメージのいずれかでポリグロット・プロキシをサポートしています。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;

public class proxy_js {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.create()) {
        ComputedArray arr = new ComputedArray();
        context.getBindings("js").putMember("arr", arr);
        long result = context.eval("js",
                    "arr[1] + arr[1000000000]")
                .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_R {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("R",
               "arr <- import('arr');" +
               "arr[2] + arr[1000000001]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_ruby {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("ruby",
               "arr = Polyglot.import('arr') \n" +
               "arr[1] + arr[1000000000]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_python {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("python",
               "import polyglot\n" +
               "arr = polyglot.import_value('arr') \n" +
               "arr[1] + arr[1000000000]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  

 このコードでは:

ポリグロット・プロキシ・インタフェースの詳細は、ポリグロットAPIのJavaDocを参照してください。

ホスト・アクセス

ポリグロットAPIでは、デフォルトで、ファイルI/Oなどの特定の重要な機能へのアクセスが制限されます。これらの制限は、allowAllAccesstrueに設定することで完全に解除できます。

ノート: 現在、アクセス制限はJavaScriptでのみサポートされています。

ホスト機能へのアクセスの制御

ホストへのゲスト・アプリケーションのアクセスを制限することが望ましい場合があります。たとえば、System.exitをコールするJavaメソッドが公開されている場合、ゲスト・アプリケーションはホスト・プロセスを終了できます。メソッドが誤って公開されることを回避するために、ホスト・アクセスはデフォルトでは許可されておらず、すべてのpublicメソッドまたはフィールドには明示的に@HostAccess.Export注釈を付ける必要があります。

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;

public class explicit_access_java_from_js {

static
// BEGIN-SNIPPET
public class Employee {
    private final String name;
    Employee(String name) {this.name = name;}

    @HostAccess.Export
    public String getName() {
        return name;
    }
}
//END-SNIPPET
static
//BEGIN-SNIPPET
public class Services {
    @HostAccess.Export
    public Employee createEmployee(String name) {
        return new Employee(name);
    }
    
    public void exitVM() {
        System.exit(1);
    }
}

public static void main(String[] args) {
    try (Context context = Context.create()) {
        Services services = new Services();
        context.getBindings("js").putMember("services", services);
        String name = context.eval("js",
                "let emp = services.createEmployee('John Doe');" + 
                "emp.getName()").asString();
        assert name.equals("John Doe");
        
        try {
            context.eval("js", "services.exitVM()");
            assert false;
        } catch (PolyglotException e) {
            assert e.getMessage().endsWith(
                    "Unknown identifier: exitVM");
        }
    }
}
// END-SNIPPET
}

  

 このコードでは:

ホスト・アクセスは、カスタムHostAccessポリシーを作成することで完全にカスタマイズできます。

ホスト・コールバック・パラメータのスコープ指定の制御

デフォルトでは、Valueの有効期間は、対応するContextと同じです。ただし、このデフォルトの動作を変更し、値をスコープにバインドすることが望ましい場合もあります。そうすると、実行がスコープから外れたときに、値が無効になります。このようなスコープの例には、Valueがコールバック・パラメータとして渡される、ゲストからホストへのコールバックがあります。コールバック・パラメータの受渡しがデフォルトのHostAccess.EXPLICITでどのように動作するかは前述のとおりです:

public class Services {
    Value lastResult;

    @HostAccess.Export
    public void callback(Value result) {
        this.lastResult = result;
    }

    String getResult() {
        return this.lastResult.asString();
    }
}

public static void main(String[] args) {
    Services s = new Services()
    try (Context context = Context.newBuilder().allowHostAccess(HostAccess.EXPLICIT).build()) {
        context.getBindings("js").putMember("services", s);
        context.eval("js", "services.callback('Hello from JS');");
        System.out.println(s.getResult());
    }
}

この例では、lastResultがゲストの値(ホスト上に格納される)への参照を保持し、callback()のスコープが終了した後もアクセス可能な状態を保ちます。

ただし、これが常に望ましいとはかぎりません。値を有効にしておくと、リソースを不必要にブロックする可能性または一時的な値の動作を正確に反映しない可能性があるためです。このようなケースでは、HostAccess.SCOPEDを使用できます。これは、すべてのコールバックのデフォルト動作を変更して、コールバック・パラメータとして渡される値がコールバックの期間中のみ有効になるようにします。

前述のコードをHostAccess.SCOPEDを使用して動作させるには、コールバック・パラメータとして渡される個々の値を固定して、それらの有効性をコールバックが戻るまで延長します:

public class Services {
    Value lastResult;

    @HostAccess.Export
    void callback(Value result, Value notneeded) {
        this.lastResult = result;
        this.lastResult.pin();
    }

    String getResult() {
        return this.lastResult.asString();
    }
}

public static void main(String[] args) {
    Services s = new Services()
    try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
        context.getBindings("js").putMember("services", s);
        context.eval("js", "services.callback('Hello from JS', 'foobar');");
        System.out.println(services.getResult());
    }
}

または、@HostAccess.DisableMethodScopeの注釈がある場合に、コールバック・メソッド全体でスコープ指定をオプトアウトして、コールバックのすべてのパラメータの通常のセマンティクスを維持します:

public class Services {
    Value lastResult;
    Value metaInfo;

    @HostAccess.Export
    @HostAccess.DisableMethodScope
    void callback(Value result, Value metaInfo) {
        this.lastResult = result;
        this.metaInfo = metaInfo;
    }

    String getResult() {
        return this.lastResult.asString() + this.metaInfo.asString();
    }
}

public static void main(String[] args) {
    Services s = new Services()
    try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
        context.getBindings("js").putMember("services", s);
        context.eval("js", "services.callback('Hello from JS', 'foobar');");
        System.out.println(services.getResult());
    }
}

アクセス権限の構成

ゲスト・アプリケーションに対して、きめ細かいアクセス権限を構成できます。構成は、新しいコンテキストの構築時にContext.Builderクラスを使用して指定できます。次のアクセス・パラメータを構成できます:

ノート: クラスのロード、ネイティブAPIまたはホストI/Oへのアクセス権を付与すると、これらの権限を使用して他のアクセス制限をバイパスできるため、実質的にすべてのアクセス権が付与されます。

ランタイム最適化のサポート

ポリグロットTruffleランタイムは、ランタイム最適化の様々なサポートを備えた複数のホスト仮想マシンで使用できます。ゲスト・アプリケーション・コードのランタイム最適化は、埋込みゲスト・アプリケーションを効率的に実行するために重要です。この表は、Javaランタイムが現在提供する最適化のレベルを示しています。

Javaランタイム ランタイム最適化レベル
Oracle GraalVM 追加のコンパイラ・パスで最適化
GraalVM Community Edition 最適化
Oracle JDK 試験段階のVMオプションを使用して有効にした場合に最適化
OpenJDK 試験段階のVMオプションを使用して有効にした場合に最適化
JVMCI機能のないJDK ランタイム最適化なし(インタプリタのみ)

説明

Oracle JDKおよびOpenJDKのランタイム最適化をデフォルトで有効にするプロジェクトが作成されました。詳細は、プロジェクトGalahadを参照してください。

OpenJDKおよびOracle JDKでの最適化の有効化

OpenJDKのようにデフォルトで有効になっているJDKランタイム最適化で実行すると、次のような警告が表示される場合があります。

[engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to machine code.
Execution without runtime compilation will negatively impact the guest application performance.

これは、ランタイム最適化を有効にせずにゲスト・アプリケーションが実行されることを示します。この警告は、-engine.WarnInterpreterOnly=falseオプションまたは-Dpolyglot.engine.WarnInterpreterOnly=falseシステム・プロパティを使用して抑止できます。また、compiler.jarとその依存関係をMaven Centralからダウンロードし、オプション--upgrade-module-pathを使用するように参照する必要があります。コンパイラのjarは、モジュールまたはクラス・パスに配置しない必要があります。MavenまたはGradleを使用した構成例は、ポリグロット埋込みデモンストレーションを参照してください。

フォールバック・エンジンへの切替え

たとえば、簡単なスクリプトのみを実行したり、リソースに制約のあるシステムで実行したりする必要が生じた場合、ランタイム最適化を行わずにフォールバック・エンジンに切り替えることができます。ポリグロット・バージョン23.1以降、フォールバック・エンジンは、クラスまたはモジュール・パスからtruffle-runtimeおよびtruffle-enterpriseモジュールを削除することでアクティブ化できます。

これは、次のようにMavenを使用して実現できます。

<dependencies>
  <dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>$graalvm-version</version>
    <exclusions>
      <exclusion>
        <groupId>org.graalvm.truffle</groupId>
        <artifactId>truffle-runtime</artifactId>
      </exclusion>
      <exclusion>
        <groupId>org.graalvm.truffle</groupId>
        <artifactId>truffle-enterprise</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>

-community依存関係のみを使用する場合、truffle-enterpriseの除外ルールは不要です。truffle-enterpriseは除外されるため、フォールバック・エンジンはサンドボックス制限やポリグロット分離などの高度な拡張をサポートしません。2つの依存関係が他の場所に含まれていないことをmvn dependency:treeで再チェックすると便利な場合があります。

ランタイムが正常に除外された場合は、次のログ・メッセージが表示されます。

[engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to native code.
Execution without runtime compilation will negatively impact the guest application performance.
The following cause was found: No optimizing Truffle runtime found on the module or class-path.
For more information see: https://www.graalvm.org/latest/reference-manual/embed-languages/.
To disable this warning use the '--engine.WarnInterpreterOnly=false' option or the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.

このメッセージは、追加ステップとして示されたオプションを使用して無効にできます。

これらの依存関係を削除すると、ネイティブ・イメージ・ビルドのフォールバック・エンジンに自動的に切り替わります。

ポリグロット・アプリケーションからのネイティブ実行ファイルのビルド

GraalVM for JDK 21上のポリグロット・バージョン23.1では、ネイティブ・イメージを使用して埋込みポリグロット言語ランタイムでイメージを構築するために特別な構成は必要ありません。他のJava依存関係と同様に、ネイティブ実行可能ファイルを構築する場合、ポリグロット言語JARファイルはクラスまたはモジュール・パスにある必要があります。MavenまたはGradleネイティブ・イメージ・プラグインを使用して、native-imageビルドを構成することをお薦めします。ネイティブ・イメージのMavenおよびGradle構成の例は、ポリグロット埋込みデモンストレーション・リポジトリにあります。

Mavenプロファイル構成の例は次のとおりです。

<profiles>
    <profile>
        <id>native</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.graalvm.buildtools</groupId>
                    <artifactId>native-maven-plugin</artifactId>
                    <version>0.9.25</version>
                    <extensions>true</extensions>
                    <executions>
                        <execution>
                            <id>build-native</id>
                            <goals>
                                <goal>compile-no-fork</goal>
                            </goals>
                            <phase>package</phase>
                        </execution>
                    </executions>
                    <configuration>
                        <imageName>${project.artifactId}</imageName>
                        <mainClass>org.example.embedding.Main</mainClass>
                        <buildArgs>
                            <buildArg>--no-fallback</buildArg>
                            <buildArg>-J-Xmx20g</buildArg>
                        </buildArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

前述の構成でネイティブ実行可能ファイルを構築するには、次を実行します。

mvn -Pnative package

ポリグロット・アプリケーション(Pythonを埋め込んだJavaホスト・アプリケーションなど)からネイティブ実行可能ファイルをビルドするために、すべての必要なファイルを含む./resourcesフォルダがデフォルトで作成されます。デフォルトでは、言語ランタイムは、作成されたネイティブ実行可能ファイルまたはライブラリ・イメージに関連するリソース・フォルダを検索します。実行時には、-Dpolyglot.engine.resourcePath=path/to/resourcesオプションを使用してルックアップの場所をカスタマイズできます。リソースの作成を無効にするには、-H:-CopyLanguageResourcesビルド時オプションを使用できます。一部の言語では、リソース・フォルダがないと実行がサポートされない場合があります。

ポリグロット・バージョン23.1では、-Dorg.graalvm.homeなどの言語ホーム・オプションは使用されなくなり、リソース・フォルダ・オプションに置き換えられました。言語ホーム・オプションは互換性のために引き続き機能しますが、将来のリリースでは削除される可能性があります。

ネイティブ・ホスト・リフレクションの構成

ゲスト・アプリケーションからホストJavaコードへのアクセスが機能するには、Javaリフレクションが必要です。ネイティブ実行可能ファイル内でリフレクションを使用する場合は、リフレクション構成ファイルが必要です。

この例では、JavaScriptを使用して、ネイティブの実行可能ファイルによるホスト・アクセスを示します。次のコードをAccessJavaFromJS.javaという名前の新しいファイルにコピーします。

import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
import java.util.concurrent.*;

public class AccessJavaFromJS {

    public static class MyClass {
        public int               id    = 42;
        public String            text  = "42";
        public int[]             arr   = new int[]{1, 42, 3};
        public Callable<Integer> ret42 = () -> 42;
    }

    public static void main(String[] args) {
        try (Context context = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {
            context.getBindings("js").putMember("javaObj", new MyClass());
            boolean valid = context.eval("js",
                   "    javaObj.id         == 42"          +
                   " && javaObj.text       == '42'"        +
                   " && javaObj.arr[1]     == 42"          +
                   " && javaObj.ret42()    == 42")
               .asBoolean();
            System.out.println("Valid " + valid);
        }
    }
}

次のコードをreflect.jsonにコピーします:

[
  { "name": "AccessJavaFromJS$MyClass", "allPublicFields": true },
  { "name": "java.util.concurrent.Callable", "allPublicMethods": true }
]

これで、ホスト・アクセスをサポートするネイティブ実行可能ファイルを作成し、追加の-H:ReflectionConfigurationFiles=reflect.jsonビルド時オプションを追加できます。

複数のコンテキストにわたるコード・キャッシング

GraalVMポリグロットAPIでは、複数のコンテキストにわたるコード・キャッシングが可能です。コード・キャッシングを使用すると、コンパイルされたコードを再利用でき、ソースの解析を1回のみにできます。コード・キャッシングにより、多くの場合、アプリケーションのメモリー消費およびウォームアップ時間を削減できます。

デフォルトでは、コードは単一のコンテキスト・インスタンス内でのみキャッシュされます。複数のコンテキスト間のコード・キャッシュを有効にするには、明示的なエンジンを指定する必要があります。エンジンは、コンテキスト・ビルダーを使用してコンテキストを作成するときに指定します。エンジン・インスタンスは、コード共有のスコープを決定します。コードは、1つのエンジン・インスタンスに関連付けられたコンテキスト間でのみ共有されます。

すべてのソースがデフォルトでキャッシュされます。キャッシングは、cached(boolean cached)falseに設定することで明示的に無効にできます。キャッシングの無効化は、ソースが1回のみ評価されることがわかっている場合に役立つことがあります。

例として、次のコード・スニペットを考えてみます:

public class Main {
    public static void main(String[] args) {
        try (Engine engine = Engine.create()) {
            Source source = Source.create("js", "21 + 21");
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
        }
    }
}

このコードでは:

重要: 実行中のコンテキスト間でキャッシュされたソースのコード・キャッシュを存続させるには、アプリケーションでSourceオブジェクトが継続的に参照されていることを確認する必要があります。ポリグロット・ランタイムは、次のGCサイクルで参照されなくなったソースのキャッシュされたコードを収集できます。

コード・キャッシュの管理

コード・キャッシュのデータは、Engineインスタンスの一部として格納されます。2つの別々のエンジン・インスタンス間でコード共有が発生することはありません。したがって、グローバル・コード・キャッシュが必要な場合は、シングルトンEngineインスタンスを使用することをお薦めします。コンテキストとは対照的に、エンジンは常に複数のスレッドで共有できます。コンテキストを複数のスレッド間で共有できるかどうかは、使用される言語によって異なります。

コード・キャッシュをパージする明示的な方法はありません。ガベージ・コレクタは、次の収集でこれを自動的に行います。エンジンのコード・キャッシュは、引き続きエンジンへの強い参照があり、エンジンがクローズされていないかぎり収集されません。また、関連付けられたコードが収集されないように、Sourceインスタンスを存続させる必要があります。ソース・インスタンスが参照されなくなったが、エンジンがまだ参照されている場合、ソース・オブジェクトに関連付けられたコード・キャッシュはGCによって収集される可能性があります。したがって、Sourceをキャッシュされたままにするかぎり、Sourceオブジェクトへの強い参照を維持することをお薦めします。

要約すると、コード・キャッシュは、EngineおよびSourceオブジェクトへの強い参照を維持および保持することで制御できます。

ポリグロット分離

Oracle GraalVMでは、専用のネイティブ・イメージ分離で実行するようにポリグロット・エンジンを構成できます。このモードのポリグロット・エンジンは、専用のガベージ・コレクタおよびJITコンパイラを使用して、VMレベルのフォルト・ドメイン内で実行されます。ポリグロット分離は、ポリグロット・サンドボックス化に役立ちます。分離された言語の実行は、HotSpotおよびネイティブ・イメージ・ホスト仮想マシンで動作します。

ポリグロット分離として使用される言語は、Maven Centralから-isolate接尾辞を使用してダウンロードできます。たとえば、分離されたJavaScriptへの依存関係を構成するには、次のようなMaven依存関係を追加します。

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>polyglot</artifactId>
    <version>23.1.0</version>
    <type>jar</type>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js-isolate</artifactId>
    <version>23.1.0</version>
    <type>pom</type>
</dependency>

ダウンロードされた依存関係はプラットフォームに依存せず、プラットフォームごとにネイティブイメージが含まれています。将来のリリースでは、個々のプラットフォームのポリグロット分離ネイティブ・イメージのダウンロードをサポートする予定です。

ポリグロットAPIでの分離使用を有効にするには、構築時に--engine.SpawnIsolate=trueオプションをEngineまたはContextに渡す必要があります。オプションengine.SpawnIsolateは、Oracle GraalVM以外のJDKで使用されている場合は使用できません。

import org.graalvm.polyglot.*;

public class PolyglotIsolate {
	public static void main(String[] args) {
		try (Context context = Context.newBuilder("js")
			  .allowHostAccess(HostAccess.SCOPED)
			  .option("engine.SpawnIsolate", "true").build()) {
			  
			Value function = context.eval("js", "x => x+1");
			assert function.canExecute();
			int x = function.execute(41).asInt();
			assert x == 42;
		}
	}
}

現在、ポリグロット分離言語として次の言語がサポートされています。

言語。 ポリグロット分離のサポート
JavaScript (js-isolate) バージョン23.1でサポート

今後のバージョンでは、より多くの言語のサポートを追加する予定です。

前の例では、HostAccess.SCOPEDを使用してスコープ参照を有効にします。これは、ホストGCとゲストGCが相互に認識されず、オブジェクト間の循環参照を自動的に解決できないために必要です。したがって、循環参照を完全に回避するには、ホスト・コールバックのスコープ・パラメータを使用することを強くお薦めします。

エンジンの共有により、同じ分離エンジンで複数のコンテキストを生成できます:

public class PolyglotIsolateMultipleContexts {
    public static void main(String[] args) {
        try (Engine engine = Engine.newBuilder("js")
                .option("engine.SpawnIsolate", "true").build()) {
            Source source = Source.create("js", "21 + 21");
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
        }
    }
}

ネイティブ・イメージ・ランタイム・オプションの受渡し

分離して実行されているエンジンは、エンジン・ビルダーに--engine.IsolateOption.<option>を渡すことでネイティブ・イメージ・ランタイム・オプションを使用できます。たとえば、--engine.IsolateOption.MaxHeapSize=128mを介して分離する最大ヒープ・サイズを設定することで、エンジンで使用される最大ヒープ・メモリーを制限するために使用できます。

import org.graalvm.polyglot.*;

public class PolyglotIsolateMaxHeap {
  public static void main(String[] args) {
    try {
      Context context = Context.newBuilder("js")
        .allowHostAccess(HostAccess.SCOPED)
        .option("engine.SpawnIsolate", "true")
        .option("engine.IsolateOption.MaxHeapSize", "64m").build()
      context.eval("js", "var a = [];while (true) {a.push('foobar');}");
    } catch (PolyglotException ex) {
      if (ex.isResourceExhausted()) {
        System.out.println("Resource exhausted");
      }
    }
  }
}

最大ヒープ・サイズを超えると、コンテキストが自動的にクローズされ、PolyglotExceptionが発生します。

ホスト・コールバック・スタックの余裕の確保

ポリグロット分離では、--engine.HostCallStackHeadRoomにより、ホスト・コールバックの実行時に使用可能な最小スタック領域が確保されます。使用可能なスタック・サイズが指定したしきい値を下回ると、ホスト・コールバックが失敗します。

メモリー保護

メモリー保護キーをサポートするLinux環境では、--engine.MemoryProtection=trueオプションを使用して、ハードウェア・レベルでポリグロット分離のヒープを分離できます。このオプションを使用してエンジンを作成すると、分離されたエンジンのヒープに専用の保護キーが割り当てられます。GraalVMによって、エンジンのヒープへのアクセスが有効になるのは、ポリグロット分離のコードの実行時のみです。

Javaでのゲスト言語の埋込み

GraalVM Polyglot APIは、Javaの相互運用性を利用して、ゲスト言語内から使用できます。これは、スクリプトを親コンテキストから分離して実行する必要がある場合に役立ちます。ホスト言語としてのJavaでは、Context.eval(Source)へのコールはValueのインスタンスを返しますが、このコードをゲスト言語の一部として実行しているため、かわりに言語固有の相互運用性APIを使用できます。そのため、言語の標準値など、言語内に作成されたコンテキストによって返される値を使用できます。次の例では、value.getMember("data")ではなくvalue.dataを簡便に記述できます。外部値と相互運用する方法の詳細は、各言語のドキュメントを参照してください。複数のコンテキスト間での値の共有の詳細は、こちらを参照してください。

例として、次のコード・スニペットを考えてみます:

import org.graalvm.polyglot.*;

public class Main {
    public static void main(String[] args) {
        try (Context outer = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {
            outer.eval("js", "inner = Java.type('org.graalvm.polyglot.Context').create()");
            outer.eval("js", "value = inner.eval('js', '({data:42})')");
            int result = outer.eval("js", "value.data").asInt();
            outer.eval("js", "inner.close()");

            System.out.println("Valid " + (result == 42));
        }
    }
}

このコードでは:

多数の言語用のシェルのビルド

GraalVMポリグロットAPIでは、数行のコードを使用して、GraalVMでサポートされている任意のゲスト言語と統合するアプリケーションをビルドできます。

このシェル実装は、特定のゲスト言語に依存しません。

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
PrintStream output = System.out;
Context context = Context.newBuilder().allowAllAccess(true).build();
Set<String> languages = context.getEngine().getLanguages().keySet();
output.println("Shell for " + languages + ":");
String language = languages.iterator().next();
for (;;) {
    try {
        output.print(language + "> ");
        String line = input.readLine();
        if (line == null) {
            break;
        } else if (languages.contains(line)) {
            language = line;
            continue;
        }
        Source source = Source.newBuilder(language, line, "<shell>")
                        .interactive(true).buildLiteral();
        context.eval(source);
    } catch (PolyglotException t) {
        if(t.isExit()) {
            break;
        }
        t.printStackTrace();
    }
}

実行リスナーを使用したステップ実行

GraalVMポリグロットAPIでは、ユーザーはExecutionListenerクラスを使用してゲスト言語の実行をインストゥルメントできます。たとえば、ゲスト言語プログラムのすべての文に対して起動される実行リスナーをアタッチできます。実行リスナーはポリグロットの埋込み機能用の単純なAPIとして設計され、プログラムのシングルステップ実行などに役立つ場合があります。

import org.graalvm.polyglot.*;
import org.graalvm.polyglot.management.*;

public class ExecutionListenerTest {
    public static void main(String[] args) {
        try (Context context = Context.create("js")) {
            ExecutionListener listener = ExecutionListener.newBuilder()
                      .onEnter((e) -> System.out.println(
                              e.getLocation().getCharacters()))
                      .statements(true)
                      .attach(context.getEngine());
            context.eval("js", "for (var i = 0; i < 2; i++);");
            listener.close();
        }
    }
}

このコードでは:

JSR-223 ScriptEngineとの互換性

Truffle言語実装フレームワークでは、JSR-223 ScriptEngine実装は提供されません。ポリグロットAPIは、Truffleの機能をより詳細に制御できるため、設定の多くを直接制御し、GraalVMのより詳細なセキュリティ設定の恩恵を受けるために、org.graalvm.polyglot.Contextインタフェースを使用することを強くお薦めします。

ただし、ScriptEngine APIを使用して統合される他のスクリプト言語の代替としてTruffle言語を簡単に評価するには、次の単一ファイル・スクリプト・エンジンを提供します。このファイルは、ソース・ツリーにドロップして、ScriptEngine APIを介してTruffle言語を評価するために直接使用できます。わずか2行でプロジェクトに適応できます。

public final class CHANGE_NAME_EngineFactory implements ScriptEngineFactory {
    private static final String LANGUAGE_ID = "<<INSERT LANGUAGE ID HERE>>";

必要に応じてクラスの名前を変更し、LANGUAGE_IDを目的のTruffle言語に変更します(たとえば、GraalPyの場合はpython、TruffleRubyの場合はruby)。これを使用するには、選択したクラス名を持つMETA-INF/services/javax.script.ScriptEngineFactoryファイルをリソースに含めます。これにより、デフォルトのjavax.script.ScriptEngineManagerで言語を自動的に検出できます。または、ファクトリをjavax.script.ScriptEngineManager#registerEngineNameで登録するか、インスタンス化して直接使用できます。

ベスト・プラクティスは、ファイナライザに依存するのではなく、使用されなくなった場合にScriptEngineをクローズすることです。クローズするには、ScriptEngineclose()メソッドがないため、((AutoCloseable) scriptEngine).close();を使用します。

Graal.jsは、JDK 11で非推奨となったNashorn JavaScriptエンジンから移行するユーザーに対してScriptEngine実装を提供するため、ここではこのメソッドは不要です。

展開して、1つのファイル内のTruffle言語のScriptEngineFactory実装を確認します。

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;

import org.graalvm.home.Version;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;

public final class CHANGE_NAME_EngineFactory implements ScriptEngineFactory {
    private static final String LANGUAGE_ID = "<>";

    /***********************************************************/
    /* Everything below is generic and does not need to change */
    /***********************************************************/

    private final Engine polyglotEngine = Engine.newBuilder().build();
    private final Language language = polyglotEngine.getLanguages().get(LANGUAGE_ID);

    @Override
    public String getEngineName() {
        return language.getImplementationName();
    }

    @Override
    public String getEngineVersion() {
        return Version.getCurrent().toString();
    }

    @Override
    public List getExtensions() {
        return List.of(LANGUAGE_ID);
    }

    @Override
    public List getMimeTypes() {
        return List.copyOf(language.getMimeTypes());
    }

    @Override
    public List getNames() {
        return List.of(language.getName(), LANGUAGE_ID, language.getImplementationName());
    }

    @Override
    public String getLanguageName() {
        return language.getName();
    }

    @Override
    public String getLanguageVersion() {
        return language.getVersion();
    }

    @Override
    public Object getParameter(final String key) {
        switch (key) {
            case ScriptEngine.ENGINE:
                return getEngineName();
            case ScriptEngine.ENGINE_VERSION:
                return getEngineVersion();
            case ScriptEngine.LANGUAGE:
                return getLanguageName();
            case ScriptEngine.LANGUAGE_VERSION:
                return getLanguageVersion();
            case ScriptEngine.NAME:
                return LANGUAGE_ID;
        }
        return null;
    }

    @Override
    public String getMethodCallSyntax(final String obj, final String m, final String... args) {
        throw new UnsupportedOperationException("Unimplemented method 'getMethodCallSyntax'");
    }

    @Override
    public String getOutputStatement(final String toDisplay) {
        throw new UnsupportedOperationException("Unimplemented method 'getOutputStatement'");
    }

    @Override
    public String getProgram(final String... statements) {
        throw new UnsupportedOperationException("Unimplemented method 'getProgram'");
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return new PolyglotEngine(this);
    }

    private static final class PolyglotEngine implements ScriptEngine, Compilable {
        private final ScriptEngineFactory factory;
        private PolyglotContext defaultContext;

        PolyglotEngine(ScriptEngineFactory factory) {
            this.factory = factory;
            this.defaultContext = new PolyglotContext(factory);
        }

        @Override
        public CompiledScript compile(String script) throws ScriptException {
            Source src = Source.create(LANGUAGE_ID, script);
            try {
                defaultContext.getContext().parse(src); // only for the side-effect of validating the source
            } catch (PolyglotException e) {
                throw new ScriptException(e);
            }
            return new PolyglotCompiledScript(src, this);
        }

        @Override
        public CompiledScript compile(Reader script) throws ScriptException {
            Source src;
            try {
                src = Source.newBuilder(LANGUAGE_ID, script, "sourcefromreader").build();
                defaultContext.getContext().parse(src); // only for the side-effect of validating the source
            } catch (PolyglotException | IOException e) {
                throw new ScriptException(e);
            }
            return new PolyglotCompiledScript(src, this);
        }

        @Override
        public Object eval(String script, ScriptContext context) throws ScriptException {
            if (context instanceof PolyglotContext) {
                PolyglotContext c = (PolyglotContext) context;
                try {
                    return c.getContext().eval(LANGUAGE_ID, script).as(Object.class);
                } catch (PolyglotException e) {
                    throw new ScriptException(e);
                }
            } else {
                throw new ClassCastException("invalid context");
            }
        }

        @Override
        public Object eval(Reader reader, ScriptContext context) throws ScriptException {
            Source src;
            try {
                src = Source.newBuilder(LANGUAGE_ID, reader, "sourcefromreader").build();
            } catch (IOException e) {
                throw new ScriptException(e);
            }
            if (context instanceof PolyglotContext) {
                PolyglotContext c = (PolyglotContext) context;
                try {
                    return c.getContext().eval(src);
                } catch (PolyglotException e) {
                    throw new ScriptException(e);
                }
            } else {
                throw new ScriptException("invalid context");
            }
        }

        @Override
        public Object eval(String script) throws ScriptException {
            return eval(script, defaultContext);
        }

        @Override
        public Object eval(Reader reader) throws ScriptException {
            return eval(reader, defaultContext);
        }

        @Override
        public Object eval(String script, Bindings n) throws ScriptException {
            throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
        }

        @Override
        public Object eval(Reader reader, Bindings n) throws ScriptException {
            throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
        }

        @Override
        public void put(String key, Object value) {
            defaultContext.getBindings(ScriptContext.ENGINE_SCOPE).put(key, value);
        }

        @Override
        public Object get(String key) {
            return defaultContext.getBindings(ScriptContext.ENGINE_SCOPE).get(key);
        }

        @Override
        public Bindings getBindings(int scope) {
            return defaultContext.getBindings(scope);
        }

        @Override
        public void setBindings(Bindings bindings, int scope) {
            defaultContext.setBindings(bindings, scope);
        }

        @Override
        public Bindings createBindings() {
            throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
        }

        @Override
        public ScriptContext getContext() {
            return defaultContext;
        }

        @Override
        public void setContext(ScriptContext context) {
            throw new UnsupportedOperationException("The context of a Polyglot ScriptEngine cannot be modified.");
        }

        @Override
        public ScriptEngineFactory getFactory() {
            return factory;
        }
    }

    private static final class PolyglotContext implements ScriptContext {
        private Context context;
        private final ScriptEngineFactory factory;
        private final PolyglotReader in;
        private final PolyglotWriter out;
        private final PolyglotWriter err;
        private Bindings globalBindings;

        PolyglotContext(ScriptEngineFactory factory) {
            this.factory = factory;
            this.in = new PolyglotReader(new InputStreamReader(System.in));
            this.out = new PolyglotWriter(new OutputStreamWriter(System.out));
            this.err = new PolyglotWriter(new OutputStreamWriter(System.err));
        }

        Context getContext() {
            if (context == null) {
                Context.Builder builder = Context.newBuilder(LANGUAGE_ID)
                    .in(this.in)
                    .out(this.out)
                    .err(this.err)
                    .allowAllAccess(true);
                for (Entry<String, Object> entry : getBindings(ScriptContext.GLOBAL_SCOPE).entrySet()) {
                    Object value = entry.getValue();
                    if (value instanceof String) {
                        builder.option(entry.getKey(), (String) value);
                    }
                }
                context = builder.build();
            }
            return context;
        }

        @Override
        public void setBindings(Bindings bindings, int scope) {
            if (scope == ScriptContext.GLOBAL_SCOPE) {
                if (context == null) {
                    globalBindings = bindings;
                } else {
                    throw new UnsupportedOperationException("Global bindings for Polyglot language can only be set before the context is initialized.");
                }
            } else {
                throw new UnsupportedOperationException("Bindings objects for Polyglot language is final.");
            }
        }

        @Override
        public Bindings getBindings(int scope) {
            if (scope == ScriptContext.ENGINE_SCOPE) {
                return new PolyglotBindings(getContext().getBindings(LANGUAGE_ID));
            } else if (scope == ScriptContext.GLOBAL_SCOPE) {
                return globalBindings;
            } else {
                return null;
            }
        }

        @Override
        public void setAttribute(String name, Object value, int scope) {
            if (scope == ScriptContext.ENGINE_SCOPE) {
                getBindings(scope).put(name, value);
            } else if (scope == ScriptContext.GLOBAL_SCOPE) {
                if (context == null) {
                    globalBindings.put(name, value);
                } else {
                    throw new IllegalStateException("Cannot modify global bindings after context creation.");
                }
            }
        }

        @Override
        public Object getAttribute(String name, int scope) {
            if (scope == ScriptContext.ENGINE_SCOPE) {
                return getBindings(scope).get(name);
            } else if (scope == ScriptContext.GLOBAL_SCOPE) {
                return globalBindings.get(name);
            }
            return null;
        }

        @Override
        public Object removeAttribute(String name, int scope) {
            Object prev = getAttribute(name, scope);
            if (prev != null) {
                if (scope == ScriptContext.ENGINE_SCOPE) {
                    getBindings(scope).remove(name);
                } else if (scope == ScriptContext.GLOBAL_SCOPE) {
                    if (context == null) {
                        globalBindings.remove(name);
                    } else {
                        throw new IllegalStateException("Cannot modify global bindings after context creation.");
                    }
                }
            }
            return prev;
        }

        @Override
        public Object getAttribute(String name) {
            return getAttribute(name, ScriptContext.ENGINE_SCOPE);
        }

        @Override
        public int getAttributesScope(String name) {
            if (getAttribute(name, ScriptContext.ENGINE_SCOPE) != null) {
                return ScriptContext.ENGINE_SCOPE;
            } else if (getAttribute(name, ScriptContext.GLOBAL_SCOPE) != null) {
                return ScriptContext.GLOBAL_SCOPE;
            }
            return -1;
        }

        @Override
        public Writer getWriter() {
            return this.out.writer;
        }

        @Override
        public Writer getErrorWriter() {
            return this.err.writer;
        }

        @Override
        public void setWriter(Writer writer) {
            this.out.writer = writer;
        }

        @Override
        public void setErrorWriter(Writer writer) {
            this.err.writer = writer;
        }

        @Override
        public Reader getReader() {
            return this.in.reader;
        }

        @Override
        public void setReader(Reader reader) {
            this.in.reader = reader;
        }

        @Override
        public List getScopes() {
            return List.of(ScriptContext.ENGINE_SCOPE, ScriptContext.GLOBAL_SCOPE);
        }

        private static final class PolyglotReader extends InputStream {
            private volatile Reader reader;

            public PolyglotReader(InputStreamReader inputStreamReader) {
                this.reader = inputStreamReader;
            }

            @Override
            public int read() throws IOException {
                return reader.read();
            }
        }

        private static final class PolyglotWriter extends OutputStream {
            private volatile Writer writer;

            public PolyglotWriter(OutputStreamWriter outputStreamWriter) {
                this.writer = outputStreamWriter;
            }

            @Override
            public void write(int b) throws IOException {
                writer.write(b);
            }
        }
    }

    private static final class PolyglotCompiledScript extends CompiledScript {
        private final Source source;
        private final ScriptEngine engine;

        public PolyglotCompiledScript(Source src, ScriptEngine engine) {
            this.source = src;
            this.engine = engine;
        }

        @Override
        public Object eval(ScriptContext context) throws ScriptException {
            if (context instanceof PolyglotContext) {
                return ((PolyglotContext) context).getContext().eval(source);
            }
            throw new UnsupportedOperationException("Polyglot CompiledScript instances can only be evaluated in Polyglot.");
        }

        @Override
        public ScriptEngine getEngine() {
            return engine;
        }
    }

    private static final class PolyglotBindings implements Bindings {
        private Value languageBindings;

        PolyglotBindings(Value languageBindings) {
            this.languageBindings = languageBindings;
        }

        @Override
        public int size() {
            return keySet().size();
        }

        @Override
        public boolean isEmpty() {
            return size() == 0;
        }

        @Override
        public boolean containsValue(Object value) {
            for (String s : keySet()) {
                if (get(s) == value) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public void clear() {
            for (String s : keySet()) {
                remove(s);
            }
        }

        @Override
        public Set keySet() {
            return languageBindings.getMemberKeys();
        }

        @Override
        public Collection values() {
            List values = new ArrayList<>();
            for (String s : keySet()) {
                values.add(get(s));
            }
            return values;
        }

        @Override
        public Set<Entry<String, Object>> entrySet() {
            Set<Entry<String, Object>> values = new HashSet<>();
            for (String s : keySet()) {
                values.add(new Entry<String, Object>() {
                    @Override
                    public String getKey() {
                        return s;
                    }

                    @Override
                    public Object getValue() {
                        return get(s);
                    }

                    @Override
                    public Object setValue(Object value) {
                        return put(s, value);
                    }
                });
            }
            return values;
        }

        @Override
        public Object put(String name, Object value) {
            Object previous = get(name);
            languageBindings.putMember(name, value);
            return previous;
        }

        @Override
        public void putAll(Map<? extends String, ? extends Object> toMerge) {
            for (Entry<? extends String, ? extends Object> e : toMerge.entrySet()) {
                put(e.getKey(), e.getValue());
            }
        }

        @Override
        public boolean containsKey(Object key) {
            if (key instanceof String) {
                return languageBindings.hasMember((String) key);
            } else {
                return false;
            }
        }

        @Override
        public Object get(Object key) {
            if (key instanceof String) {
                Value value = languageBindings.getMember((String) key);
                if (value != null) {
                    return value.as(Object.class);
                }
            }
            return null;
        }

        @Override
        public Object remove(Object key) {
            Object prev = get(key);
            if (prev != null) {
                languageBindings.removeMember((String) key);
                return prev;
            } else {
                return null;
            }
        }
    }
}
</code></pre>
</details>