Personal tools
You are here: Home ブログ iwanaga Rhinoを組み込む為のイントロダクション
« December 2010 »
Su Mo Tu We Th Fr Sa
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Recent comments
Re:javascriptのthisについて inoue 2009-11-13
Re:Rhino Code Reading #0x04 iwanaga 2008-11-26
Re:Rhino Code Reading #0x04 inoue 2008-11-26
 
Document Actions

Rhinoを組み込む為のイントロダクション

Rhino Code Readingの更新が滞ってしまってから久しいですが、
最近Rhinoの組込みを実践してみたのでそれを基にtipsに近いイントロダクションを書きます。

1 組込む

1.1 最も簡単な組み込み

public class EvalString {
    public static void main(String args[])
    {
        // 1. Contextの作成
        // Context cx = Context.enter(); は非推奨?
        ContextFactory contextFactory = new ContextFactory();
        Context cx = contextFactory.enterContext();
        try {
            // 2. グローバルスコープオブジェクトの取得
            Scriptable globalScope = cx.initStandardObjects();
            // 3. 文字列をeval(評価)する。
            Object result = cx.evaluateString(scope, "1 + 1", "<cmd>", 1, null);
            // 4. 実行結果を標準出力に書きこむ
            System.out.println(Context.toString(result));
        } finally {
            // 5. Contextの終了
            Context.exit();
        }
    }
}

Rhinoの組み込みで最も簡単な組み込み例です。

ContextをContextFactoryから作成している点を除けば、
Rhino Code Reading #0x02に載せたコードとだいたい同じですので説明はそちらを参照してください。

1.2 複数回コードを実行する

複数回コードを実行するだけならevaluateStringを複数回実行すれば良いですが、それでは定義された変数などの状態が保持され続てしまうのでShellのようなインタラクティブな用途以外では向きません。

cx.evaluateString(globalScope, "var i = 1;", "<cmd>", 1, null);
for (int i = 0; i < 10; i++) {
    Object result = cx.evaluateString(globalScope, "i += 1; i;", "<cmd>", 1, null);
    System.out.print(Context.toString(result) + " ");
}
// result: 2 3 4 5 6 7 8 9 10 11


JavaScriptの状態はglobaScopeが保持しています。
しかし、globalScopeを作成するのに使用したContext.initStandardObjectsメソッドはコストの高いメソッドです。
そこでContext.newObjectで新たなオブジェクトを作成し、それをローカルスコープとして使用します。
Context.newObjectで作成されたオブジェクトはプロパティを一切持たない空のオブジェクトですが、
プロトタイプを用いることでglobalScopeの環境を共有しつつローカルスコープとして利用することができます。
これはマルチスレッド間でスコープを共有したい時にも同じ方法が有効です。

cx.evaluateString(globalScope, "var i = 1;", "<cmd>", 1, null);
for (int i = 0; i < 10; i++) {
    // ローカルスコープの作成
    Scriptable localScope = cx.newObject(globalScope);
    localScope.setPrototype(globalScope);
    localScope.setParentScope(null);

    Object result = cx.evaluateString(localScope, "i += 1; i;", "<cmd>", 1, null);
    System.out.print(Context.toString(result) + " ");
}
// result: 2 2 2 2 2 2 2 2 2 2


同じコードを何回も実行するのであれば、事前にコンパイルを行ってから実行すると良いでしょう。

// スクリプトをコンパイル
Script script = cx.compileString("i += 1; i;", "<cmd>", 1, null);
for (int i = 0; i < 10; i++) {
    ...

    // コンパイルしたスクリプトの実行
    Object result = script.exec(cx, localScope);
    System.out.print(Context.toString(result) + " ");
}


2 拡張する

2.1 Javaオブジェクトの追加

Javaのオブジェクトを追加するにはContext.javaToJSでラッパーオブジェクトを作成し、
ScriptableObject.putPropertyでJavaScriptオブジェクトをプロパティに追加します。

Object wrappedOut = Context.javaToJS(System.out, globalScope);
ScriptableObject.putProperty(globalScope, "out", wrappedOut);
cx.evaluateString(globalScope, "out.println(42);", "<cmd>", 1, null);


2.2 メソッドの追加

組込みのRhino Shellのように独自のグローバルメソッドを定義したい時があります。
そういった時に利用されるのがScriptableObject.defineFunctionProperties()です。
追加できるメソッドは二種類の形式が許可されています。
一つは、引数を持たない、もしくは1個以上の引数(boolean, int, double, Object, String, Scriptableのいずれかの型)を持ち、引数と同じいずれかの型かvoid型の値を返すメンバメソッド。
もう一つは、(Context cx, Scriptable thisObj, Object[] args, Function funObj)形式で、可変長引数を受けとり、Object型かvoid型の値を返すstaticメソッドです。

public static void main(String args[]) {
    ContextFactory contextFactory = new ContextFactory();
    Context cx = contextFactory.enterContext();
    try {
        Scriptableobject globalScope = cx.initStandardObjects();

        // メソッドの追加
        String[] funcNames = { "sum", "print" };
        globalScope.defineFunctionProperties(funcNames, Global.class, ScriptableObject.DONTENUM);

        cx.evaluateString(globalScope, "print(sum(1,2), \"hello\");", "<cmd>", 1, null);
    } finally {
        Context.exit();
    }
    // result: 3 hello
}

// 一般的な形式
public int sum(int x, int y) {
    return x + y;
}

// 可変長引数形式
public static Object print(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
    for (int i = 0; i < args.length; i++) {
        if (i > 0) {
            System.out.print(" ");
        }
        System.out.print(Context.toString(args[i]));
    }
    System.out.println();
    return Context.getUndefinedValue();
}


2.3 ホストオブジェクト

Nativeオブジェクト以外の組込みオブジェクトのことを指します。
Rhinoでは特定の規約にそったJavaクラスを作成することで、独自のホストオブジェクトを組込むことが出来ます。

public class CustomHostObject extends ScriptableObject {
    private int count;
    public static void main(String args[]) throws Exception {
        ContextFactory contextFactory = new ContextFactory();
        Context cx = contextFactory.enterContext();
        try {
            Scriptable globalScope = cx.initStandardObjects();
            // ホストオブジェクトの登録
            ScriptableObject.defineClass(globalScope, CustomHostObject.class);

            Object result = cx.evaluateString(globalScope, "var c = new Custom(3); c.count; c.count;", "<cmd>", 1, null);
            System.out.println(Context.toString(result));
        } finally {
            Context.exit();
        }
        // result: 4
    }

    // 引数のないコンストラクタは: Rhinoランタイムによって、インスタンスの作成に使用される。
    public CustomHostObject() {}

    // JavaScriptコンストラクタ: jsConstructorメソッドによって定義。
    public void jsConstructor(int a) { count = a; }

    // クラス名: getClassNameメソッドによって定義。コンストラクター名を決定するのに使用。
    public String getClassName() { return "Custom"; }

    // 動的プロパティ: 「jsGet_ / jsSet_ プロパティ」ではじまるメソッド定義。
    public int jsGet_count() { return count++; }

    // JavaScriptメソッド: 「jsFunction_メソッド名」ではじまるメソッド定義。
    public void jsFunction_resetCount() { count = 0; }
}


3 更に組込む

3.1 デバッガ

Debuggerインターフェイスを実装したクラスを用意しても構いませんが、
Rhino組込みのデバッガを利用することで簡単且つ高機能なRhinoデバッガを利用することが出来ます。

ContextFactory contextFactory = new ContextFactory();
Main debugger = new org.mozilla.javascript.tools.debugger.Main("debug window");
debugger.attachTo(contextFactory);
debugger.pack();
debugger.setSize(600, 460);
debugger.setVisible(true);

org.mozilla.javascript.tools.debugger.Mainが含まれていない場合は
ant compile-debuggerで作成したjarを使用すると良いようです。

3.2 セキュリティ

スクリプトから呼び出されたくないクラスはClassShutterを使用すればクラス名からクラスの呼び出しを遮断することが出来ます。
不特定多数のユーザーがスクリプトを実行できる様なアプリケーションの場合などで有効です。

// ClassShutterの登録
cx.setClassShutter(new ClassShutter() {
    // 可視判定メソッド
    public boolean visibleToScripts(String fullClassName) {
        if(fullClassName.indexOf("java.lang.System") != -1) {
            System.err.println("unvisible class : " + fullClassName);
            return false;
        }
        return true;
    }
});

ScriptableObject globalScope = cx.initStandardObjects();
cx.evaluateString(globalScope, "java.lang.System.out.println(3);", "<cmd>", 1, null);

// result :
// unvisible class : java.lang.System
// Exception in thread "main" org.mozilla.javascript.EcmaError:
// TypeError: Cannot call property println in object [JavaPackage java.lang.System.out].
// It is not a function, it is "object". (<cmd>#1)
// ...

3.3 書けなかったこと

SecurityController / PolicySecurityController - 電子署名ベースのセキュリティポリシー。難しい・・・。


3.4 最近知ったこと

JavaScript1.7でlet 文/式/宣言がサポートされていた事。


3.5 参考サイト

組込みはエントリを書くのに参考にさせて頂いたサイトです。

Rhino - MDC
https://developer.mozilla.org/ja/Rhino

ディスカッション - mozilla.dev.tech.js-engine.rhino | Google グループ
http://groups.google.com/group/mozilla.dev.tech.js-engine.rhino/topics

Rhinoに関する覚え書き
http://homepage.mac.com/takaho_e/computer/rhino/rhino.html

Helma Javascript Web Application Framework (Rhinoを利用したオープンソースのプロジェクト)
http://dev.helma.org/

a geek (Rhinoに関する複数のエントリー有り)
http://d.hatena.ne.jp/hiratara/


3.6 一番大事なこと

えんじょい。

Category(s)
rhino
The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/iwanaga/rhino-intro/tbping
Add comment

You can add a comment by filling out the form below. Plain text formatting.

(Required)
(Required)
(Required)
(Required)
(Required)
This helps us prevent automated spamming.
Captcha Image


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.