言語の埋込み

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

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

始める前に、必ずGraalVMを設定してください。

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

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

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

1. hello-polyglotプロジェクト・ディレクトリを作成します。

2. プロジェクト・ディレクトリで、次のコードを含むHelloPolyglot.javaファイルを追加します:

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

public class hello_polyglot_js {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("js", "print('Hello JavaScript!');");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

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

public class hello_polyglot_R {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {
            context.eval("R", "print('Hello R!');");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

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

public class hello_polyglot_ruby {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("ruby", "puts 'Hello Ruby!'");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

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

public class hello_polyglot_python {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("python", "print('Hello Python!')");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

  

 このコードでは:

3. javac HelloPolyglot.javaを実行してGraalVMでHelloPolyglot.javaをコンパイルします。

4. java HelloPolyglotを実行してGraalVMでアプリケーションを実行します。

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

この項の他のコード例を使用するには、単に、次のことを実行する必要があります:

1. コード・スニペットをHelloPolyglot.javaのメイン・メソッドに追加します。

2. ポリグロット・アプリケーションをコンパイルして実行します。

ゲスト言語関数の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オブジェクトをゲスト言語に渡すことができます。

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

ゲスト言語がJavaオブジェクトのpublicメソッドまたはフィールドにアクセスできるようにするには、コンテキストがビルドされるときにallowAllAccess(true)を設定します。このモードでは、ゲスト言語コードは、明示的にエクスポートされていない他の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へのアクセス権を付与すると、これらの権限を使用して他のアクセス制限をバイパスできるため、実質的にすべてのアクセス権が付与されます。

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

ポリグロット埋込みは、ネイティブ・イメージを使用して事前にコンパイルすることもできます。デフォルトでは、ポリグロットAPIを使用している場合、言語は含まれません。ゲスト言語を有効にするには、--language:<languageId> (たとえば、--language:js)オプションを指定する必要があります。現在、ポリグロット・ネイティブ実行可能ファイルをビルドする場合は、--initialize-at-build-timeオプションを設定する必要があります。このページのすべての例は、native-imageビルダーを使用してネイティブ実行可能ファイルに変換できます。

次の例は、native-imageを使用して単純なHelloWorld JavaScriptアプリケーションをビルドする方法を示しています:

javac HelloPolyglot.java
native-image --language:js --initialize-at-build-time -cp . HelloPolyglot
./HelloPolyglot

-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntimeオプションをビルダーに渡して、ゲスト言語をネイティブ実行可能ファイルに含め、JITコンパイラを除外することもできます。フラグ-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntimeは、デフォルト設定をオーバーライドするように、すべてのTruffle言語/ツール・オプションのに配置する必要があります。

前述の例を再度ビルドできますが、今回は次のコマンドを実行して、作成されるイメージにTruffle言語インタプリタのみを含めます(GraalVMコンパイラはイメージに含まれません):

native-image --language:js -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime --initialize-at-build-time -cp . HelloPolyglotInterpreter

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

ゲスト・アプリケーションからホスト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 }
]

これで、ホスト・アクセスをサポートするネイティブの実行可能ファイルを作成できます:

javac AccessJavaFromJS.java
native-image --language:js --initialize-at-build-time -H:ReflectionConfigurationFiles=reflect.json -cp . AccessJavaFromJS
./accessjavafromjs

イメージでアサーションが必要な場合は、-H:+RuntimeAssertionsオプションをnative-imageに渡すことができます。本番デプロイメントでは、このオプションは省略してください。

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

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;
            }
        }
    }
}

このコードでは:

ゲスト言語に埋め込まれたゲスト言語

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();
        }
    }
}

このコードでは:

ポリグロット分離

GraalVM Enterpriseでは、専用のnative-image分離で実行するようにポリグロット・エンジンを構成できます。この試験段階の機能は、--engine.SpawnIsolateオプションによって有効になります。このモードで実行されているエンジンは、独自のガベージ・コレクタおよびJITコンパイラを使用して、VMレベルのフォルト・ドメイン内で実行されます。エンジンが分離内で実行されるということは、ポリグロットAPIと相互運用性に関して完全に透過的です:

import org.graalvm.polyglot.*;

public class PolyglotIsolate {
  public static void main(String[] args) {
    Context context = Context.newBuilder("js")
      .allowHostAccess(HostAccess.SCOPED)
      .allowExperimentalOptions(true)
      .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;
  }
}

ホストのGCと分離のGCは互いに認識していないため、両方のヒープ上のオブジェクト間で循環参照が発生する可能性があります。したがって、循環参照を回避するには、ホスト・コールバックのスコープ・パラメータを使用することを強くお薦めします。

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

public class PolyglotIsolateMultipleContexts {
    public static void main(String[] args) {
        try (Engine engine = Engine.newBuilder()
                .allowExperimentalOptions(true)
                .option("engine.SpawnIsolate", "js").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.SpawnIsolateのパラメータとして指定する必要があることに注意してください。その理由は、分離されたエンジンは、どの言語セットが使用可能であるかを認識する必要があるためです。バックグラウンドで、GraalVMは対応するネイティブ・イメージ言語ライブラリを見つけます。単一の言語のみが選択されている場合、その言語のライブラリがロードされます。複数の言語が選択されている場合、GraalVMに付属しているすべてのTruffle言語を含むライブラリlibpolyglotがロードされます。一致するライブラリが使用できない場合、エンジンの作成は失敗します。

GraalVMの存続期間中にロードできる言語ライブラリは1つのみです。つまり、最初に作成された分離エンジンによって、残りの実行のデフォルトが設定されます: Javascriptのみを使用する分離エンジンが最初に作成された場合、分離言語ではJavascriptのみが使用可能になります。

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

分離して実行されているエンジンは、エンジン・ビルダーに--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)
        .allowExperimentalOptions(true)
        .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が発生します。

依存性の設定

GraalVMの埋込みAPI (つまりorg.graalvm.polyglot.*)を最大限に使用するには、プロジェクトでGraalVMをJAVA_HOMEとして使用する必要があります。それに加えて、graal-sdk.jar (GraalVMに含まれる)を、プロジェクトに提供された依存性として指定する必要があります。これは主に、プロジェクトがこのAPIを使用するという情報をIDEやその他のツールに提供するためのものです。このMavenの例は、pom.xmlファイルに次を追加することを意味します。

<dependency>
    <groupId>org.graalvm.sdk</groupId>
    <artifactId>graal-sdk</artifactId>
    <version>${graalvm.version}</version>
    <scope>provided</scope>
</dependency>

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

module com.mycompany.app {
  requires org.graalvm.sdk;

}

7c86aa8c43a(ネイティブ・イメージ > ネイティブ実行可能ファイル)