Rhino を使って Java から JavaScript を実行する から、さらにあれこれ試していて多少進捗があったので、 現在までに得た見地をまとめます。
今、関心の対象にあるのは、 いわゆる Embedding Rhino と呼ばれていることで、Javaのオブジェクト (Host Objects) をつくって、それを Rhino の javascript から利用することです。 このあたり Rhino Embedding_tutorial の内容です。
なお、例によってここにあるコードはすべて Groovy です。
本題に入る前にデバッグ用に必要な console.log() を Rhinoでどうするか問題を解決します。
たとえば、以下のような javascript を Embedding Rhino で実行したい。
console.log('hello world!');
これを解決します。
これは、https://github.com/mozilla/rhino/blob/master/examples/RunScript2.java に例がある通りです。 Groovy でポイントだけ書き出すと:
@Grab(group='org.mozilla', module='rhino', version='1.7.12')
import org.mozilla.javascript.Context
import org.mozilla.javascript.ScriptableObject
def cx = Context.enter()
def scope = cx.initStandardObjects()
def jsOut = Context.javaToJS(System.out, scope)
ScriptableObject.putProperty(scope, "out", jsOut)
def script = "out.println('hello world!');"
cx.evaluateString(scope, script, "<cmd>", 1, null)
Context.exit()
out.println ではなく console.log としたければ:
def script = '''\
|var console = {};
|console.log = function(msg){ out.println(msg); };
|
|console.log('hello world!');
|'''.stripMargin('|')
とすればOK。
see also : Rhino で console.log() したい
いわゆるDOMで Document, Pages, Page の3つのオブジェクトがあり以下のように javascript で扱いたい、とする。
こんな javascript コード を書きたい:
var page0 = new Page('page-0');
var page1 = new Page('page-1');
var pages = new Pages();
pages.push(page0);
pages.push(page1);
var document = new Document(pages);
var len = document.pages.length;
for(int i=0; i<len; i++){
console.log( '- ' + document.pages[i].name );
}
ここで出現する Document, Pages, Page を Java 側の Host Objects として実装したい、どうすればいいかという問題。
そのためには いわゆる Host Objects 用意すればいいのだが、以下のようなルールで実装:
それでは、これらのルールに則り、Document, Pages, Page クラスを実装してみる。
class Document extends ScriptableObject {
@Override
String getClassName() { return "Document" }
private Pages pages
Document(){}
@JSConstructor
Document(Pages pages){
this.pages=pages
}
@JSGetter
Pages pages(){
return this.pages
}
}
class Pages extends ScriptableObject {
@Override
String getClassName() { return "Pages" }
Pages(){
}
private def list = new java.util.ArrayList()
@JSGetter
int length(){
return list.size()
}
@Override
Object get(int index, Scriptable start){
if(0<=index && index<list.size()){
return list.get(index)
}
return super.get(index, start)
}
@JSFunction
void push(Page value){
list.add(value)
}
}
see also : Rhino で Java 側でつくった配列クラスを使う
class Page extends ScriptableObject {
@Override
String getClassName() { return "Page" }
Page(){}
private String name
@JSConstructor
Page(String name){
this.name = name
}
@JSGetter
String name(){
return name
}
}
Document, Pages, Page を ScriptableObject.defineClass するだけです。
def script = '''\
| // 省略:ここは最初に書いた Javascritp を定義.
'''.stripMargin('|')
def cx = Context.enter()
def scope = cx.initStandardObjects()
Object jsOut = Context.javaToJS(System.out, scope)
ScriptableObject.putProperty(scope, "out", jsOut)
ScriptableObject.defineClass(scope, Page.class)
ScriptableObject.defineClass(scope, Pages.class)
ScriptableObject.defineClass(scope, Document.class)
cx.evaluateString(scope, script, "<cmd>", 1, null)
Context.exit()
see also : Rhino を使って Java から JavaScript を実行する
javascript 側では、単に 構築ずみの document インスタンスを使いたいだけ場合、つまり javascript はこれだけのコードにしたい:
var len = document.pages.length;
for(int i=0; i<len; i++){
console.log( '- ' + document.pages[i].name );
}
このような場合、Java側で doucment インスタンスを構築して scope に存在させておく必要がある。
ScriptableObject.defineClass(scope, Page.class)
ScriptableObject.defineClass(scope, Pages.class)
ScriptableObject.defineClass(scope, Document.class)
def page0 = cx.newObject(scope, "Page", 'page-0')
def page1 = cx.newObject(scope, "Page", 'page-1')
def pages = cx.newObject(scope, "Pages")
pages.push(page0)
pages.push(page1)
def document = cx.newObject(scope, "Document", pages)
scope.put("document", scope, document)
Rhinoなりのお作法があってそのドキュメントがあちこちに分散していたので、ここまで来るのが大変でした。 こんな簡単な DOM ではおよそ実用的ではないので、ここから先もまた道のりは長そうですが。