[Java, JavaScript] Fully Transparent Java Objects in Nashorn

原文はこちら。
https://blogs.oracle.com/nashorn/entry/fully_transparent_java_objects_in

NashornのJSAdapterは、JavaオブジェクトをJavaScriptで取り扱うための強力なツールで、完全な透過性を有していますので、これを利用してJavaオブジェクトをラップし、新たなプロパティや挙動を追加することができます。
JSAdapter constructor
https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions#Nashornextensions-JSAdapterconstructor
例:
var Date = Java.type('java.time.LocalDate')

function wrapDate(d) {
  return new JSAdapter() {
    __get__: function(name) {
      if (name === 'yesterday') {
        return d.minusDays(1)
      }
      return d[name]
    }
  }
}

var now = wrapDate(Date.now())
実行すると、このスクリプトは現在時刻を表す Date オブジェクトを JSAdapter でラップし、ラップされたオブジェクトの任意のプロパティに対するアクセスがあると、速やかに __get__ function が呼び出されます。プロパティ名がアダプタが理解しているもの(ここでは yesterday )であれば、適切な値を返します。
jjs> load('yesterday.js')

jjs> print(now.yesterday)

2015-11-04
その他のすべてのプロパティはラップされたオブジェクトから取得されます。
しかし、これらの例を見ると、ラッピングが完全ではないことを示しています。
jjs> print(now)

<shell>:1 TypeError: [object JSAdapter] has no such function "toString"

jjs> print(now.lengthOfMonth())

<shell>:1 TypeError: [object JSAdapter] has no such function "lengthOfMonth"
ラッパーは、転送するように指示されたラップされたオブジェクトにそれらの要求を転送するだけの、独立したオブジェクトです。

ここでラッパーが完全に透過的でなければならないのは、デフォルトの場合です。ラッパーを完全に透過的にするためにここで必要なのは、あるプロパティを追加するような、すべてのメソッド呼び出しをラップされたJavaオブジェクトへ転送するデフォルトの場合です。このケースの場合、レシーバーと引数を正しく取り扱う必要があるため、プロパティの検索に比べていささか複雑です。受信機と引数を正しく処理する必要があるため場合は、プロパティの検索よりも少し複雑です。

以下の例は、透過呼び出しの転送のために利用可能なイディオムです。
function wrapDate(d) {
  return new JSAdapter() {
    __call__: function() {
      ...
      var args = [d].concat(Array.prototype.slice.call(arguments, 1))
      return Function.call.apply(d[arguments[0]], args)
    }
  }
}
非常に思慮深いJavaScript中のこの2行で、所望の機能を果たします。各JavaScript関数には、暗黙のローカル変数とargumentsがあり、これは現在起動中の関数に渡されるすべての引数を保持します。このarguments配列は2箇所で利用されています。

最初の行で、実際の呼び出し引数を配列から抽出しています。argumentsの最初の配列要素は呼び出されたメソッド名であって、これは引数になくてもかまいません。しかしレシーバーにとってはこの引数は必要で、 concat を呼び出すことにより、その第1引数を引数の前に連結しています。

2行目では、イディオムが実際に呼び出しを行う Function.call 関数を使っています。プロパティ検索を使って、呼び出されるメソッドを表すオブジェクトをラップされたオブジェクトから取り出します。ここで、arguments変数の最初の要素が呼び出されるメソッドの名前であることを思い出してください。レシーバーオブジェクトを含む引数は、すでに引数にargsに存在します。

これで。ラッピングが透過的になっています。
jjs> print(now)

2015-11-05

jjs> print(now.lengthOfMonth())

30

jjs> print(now.yesterday)

2015-11-04
transparent forwarding idiomを説明したサンプルはJDK 9のNashornのソース(samples/jsadapter-fallthrough.js)にあります。

[訳注]
JDK 9 Nashornのソースは、以下のURLにアクセスし、
http://hg.openjdk.java.net/jdk9/jdk9/nashorn
左側のメニューの[browse] をクリック > ディレクトリ samples をクリック > jsadapter-fallthrough.jsをクリックすると確認できます。

0 件のコメント:

コメントを投稿