Nashornでホストオブジェクトを追加するには
2014年9月14日 コメントを残す
RhinoではScriptableObjectを継承することで簡単にプロパティの拡張が可能なホストオブジェクトを実装できた。Nashornではそのような公開インターフェイスは用意されていないが、jdk.nashorn.internal.objects.ScriptObject
を継承し、nasgen
というツールを使うことで内部的には実現できている。
Nashornのホストオブジェクト実装方法はJavaScriptで擬似的にクラスを作るのに近い。JavaScriptでクラスを実現するには一般には以下のように行う。
function Greeting() {} Greeting.prototype.sayHello = function (name) { return 'Hello, ' + name; }; var greeting = new Greeting();
JavaScriptでクラスのインスタンスを生成するには、インスタンスであるオブジェクト自身のほかにコンストラクターとなる関数オブジェクトとプロトタイプオブジェクトの2つのオブジェクトがかかわってくる。
コンストラクターとなる関数オブジェクトはただの関数オブジェクトで、利用者がどう使うかの違いでしかない。関数オブジェクトをコンストラクターとして使うにはnew
演算子を使う。関数オブジェクトはprototype
プロパティを持っていて、初期値は空のオブジェクトが設定されている。new
演算子を使ってオブジェクトを生成すると、生成したオブジェクトのプロトタイプ内部プロパティがコンストラクターのprototype
プロパティの値に設定される。プロトタイプ内部プロパティとprototype
プロパティは違うものなので、greeting.prototype
はundefined
だ。プロトタイプ内部プロパティを取得するにはObject.getPrototypeOf(greeting)
を使う。
greeting.prototype // -> undefined Object.getPrototypeOf(greeting) // -> { sayHello: function () { } }
プロトタイプ内部プロパティはオブジェクトのひな形として機能する。greeting
自身にはsayHello
プロパティを設定してはいないが、greeting.sayHello
はプロトタイプ内部プロパティはオブジェクトのsayHello
プロパティの値を返してくれる。
Nashornでホストオブジェクトを実装するには、コンストラクター、プロトタイプ、オブジェクトの実装の3つを用意してやればよい。NashornでもRhinoと同様にアノテーションを使って実装する。ホストオブジェクトのタイプごとに1つのJavaクラスを作ってやればよいのだが、コンストラクターやプロトタイプを自分で扱うため面倒になる。
試しに実装してみたホストオブジェクトの実装はこんな感じだ、Nashornではホストオブジェクトを生成するための公開インターフェイスは用意されておらず、実装はNashorn自身のパッケージであるjdk.nashorn.internal.objects
を使わなければならない。
package jdk.nashorn.internal.objects; import jdk.nashorn.internal.objects.annotations.Attribute; import jdk.nashorn.internal.objects.annotations.Constructor; import jdk.nashorn.internal.objects.annotations.Function; import jdk.nashorn.internal.objects.annotations.ScriptClass; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.ScriptObject; @ScriptClass("Greeting") public class NativeGreeting extends ScriptObject { // initialized by nasgen private static PropertyMap $nasgenmap$; static PropertyMap getInitialMap() { return $nasgenmap$; } @Constructor public static Object constructor(final boolean isNew, final Object self, final Object... args) { return new NativeGreeting(Global.instance()); } NativeGreeting(final Global global) { super(global.getGreetingPrototype(), global.getGreetingMap()); } public String sayHello(String name) { return "Hello, " + name; } @Function(attributes = Attribute.NOT_ENUMERABLE) public static Object sayHello(final Object self, final Object name) { return ((NativeGreeting) self).sayHello((String) name); } }
Nashornのビルド時には、Nativeの実装がコンパイルされた後でnasgen
ツールがjdk/nashorn/internal/objects/NativeGreeting.class
を読み込み、
jdk/nashorn/internal/objects/NativeGreeting$Constructor.class
とjdk/nashorn/internal/objects/NativeGreeting$Prototype.class
の2つのクラスファイルを自動生成する。
jdk.nashorn.internal.objects.Global
クラスはJavaScriptのグローバルオブジェクトを実装するJavaクラスで、グローバルオブジェクトの初期時に、initConstructor()
メソッドを使って、jdk.nashorn.internal.objects.NativeGreeting$Constructor
クラスからコンストラクターの関数オブジェクトを生成する。
this.builtinGreeting = (ScriptFunction)initConstructor("Greeting");
このコンストラクターはjdk.nashorn.internal.objects.NativeGreeting$Prototype
で実装されるプロトタイプを持っていて、以下のように取得できる。
ScriptObject getGreetingPrototype() { return ScriptFunction.getPrototype(builtinGreeting); }
あとはthis.builtinGreeting
をJavaScriptから見えるようにすればJavaScriptでGreetingオブジェクトを生成できる。
print(Greeting); var greeting = new Greeting(); print(greeting); print(greeting.sayHello('Alice'));
以下、今回使ったパッチ
diff -r 18edd7a1b166 src/jdk/nashorn/internal/objects/Global.java --- a/src/jdk/nashorn/internal/objects/Global.java Wed Dec 11 18:09:34 2013 +0100 +++ b/src/jdk/nashorn/internal/objects/Global.java Sun Sep 14 08:07:01 2014 +0900 @@ -313,6 +313,9 @@ @Property(name = "__LINE__", attributes = Attribute.NON_ENUMERABLE_CONSTANT) public Object __LINE__; + @Property(name = "Greeting", attributes = Attribute.NOT_ENUMERABLE) + public volatile Object greeting; + /** Used as Date.prototype's default value */ public NativeDate DEFAULT_DATE; @@ -364,6 +367,7 @@ private ScriptObject builtinUint32Array; private ScriptObject builtinFloat32Array; private ScriptObject builtinFloat64Array; + private ScriptObject builtinGreeting; /* * ECMA section 13.2.3 The [[ThrowTypeError]] Function Object @@ -399,6 +403,7 @@ private PropertyMap anonymousFunctionMap; private PropertyMap strictFunctionMap; private PropertyMap boundFunctionMap; + private PropertyMap nativeGreetingMap; // Flag to indicate that a split method issued a return statement private int splitState = -1; @@ -999,6 +1004,10 @@ return ScriptFunction.getPrototype(builtinFloat64Array); } + ScriptObject getGreetingPrototype() { + return ScriptFunction.getPrototype(builtinGreeting); + } + // Builtin PropertyMap accessors PropertyMap getAccessorPropertyDescriptorMap() { return accessorPropertyDescriptorMap; @@ -1124,6 +1133,10 @@ return typeErrorThrower; } + PropertyMap getGreetingMap() { + return nativeGreetingMap; + } + /** * Called from compiled script code to test if builtin has been overridden * @@ -1669,6 +1682,8 @@ this.builtinNumber = (ScriptFunction)initConstructor("Number"); this.builtinRegExp = (ScriptFunction)initConstructor("RegExp"); this.builtinString = (ScriptFunction)initConstructor("String"); + this.builtinGreeting = (ScriptFunction)initConstructor("Greeting"); + System.out.println(this.builtinGreeting); // initialize String.prototype.length to 0 // add String.prototype.length @@ -1883,6 +1898,7 @@ this.uint32Array = this.builtinUint32Array; this.float32Array = this.builtinFloat32Array; this.float64Array = this.builtinFloat64Array; + this.greeting = this.builtinGreeting; } private void initDebug() { @@ -1975,6 +1991,7 @@ this.anonymousFunctionMap = ScriptFunctionImpl.getInitialAnonymousMap().duplicate(); this.strictFunctionMap = ScriptFunctionImpl.getInitialStrictMap().duplicate(); this.boundFunctionMap = ScriptFunctionImpl.getInitialBoundMap().duplicate(); + this.nativeGreetingMap = NativeGreeting.getInitialMap().duplicate(); // java if (! env._no_java) {
参考資料