javagraalvmgraaljs

Access Java object from JavaScript in GraalVM Polyglot context


Running on GraalVM CE.

openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment (build 11.0.5+10-jvmci-19.3-b05-LTS)
OpenJDK 64-Bit GraalVM CE 19.3.0 (build 11.0.5+10-jvmci-19.3-b05-LTS, mixed mode, sharing)

Case 1:

import org.graalvm.polyglot.Context;

public class Test {

    static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
    }

    public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());

        context.eval("js", "var x = d.name");

        System.out.println(
                context.getBindings("js").getMember("x").asString()
        );
    }
}

Result:

null

Why?

As I can understand, d passed correctly:

((Data) context.getBindings("js").getMember("d").as(Data.class)).name

returns "HelloWorld".

Case 2:

context.eval("js", "d.getName()");

Exception

Exception in thread "main" TypeError: invokeMember (getName) 
on JavaObject[task.Test$Data@35a3d49f (task.Test$Data)] failed due to: 
Unknown identifier: getName

But getName is public... What's wrong?


Solution

  • When you use a context and add a Java Object to it, behind the scene, the IntropLibrary inside TruffleApi creates a HostObject and associate it with that object. This means that you don't use the object itself but rather a wrapper object.

    When you call the getMember() method, the IntropLibrary can only access fields and methods of the hosted object that are publicly available. Since your inner class has default access (no access modifier), the API cannot find its members even though they are public. (a member of a class cannot have broader access than its class itself).

    To solve this issue, all you have to do is make you inner class public

    import org.graalvm.polyglot.Context;
    
    public class Test {
    
      public static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
      }
    
      public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());
    
        context.eval("js", "var x = d.name;");
    
        System.out.println(
            context.getBindings("js").getMember("x").asString()
        );
      }
    }