Rhino of JavaScript

Takami Torao Java 6
  • このエントリーをはてなブックマークに追加

JavaScript 仕様

Rhino は元々 Mozilla プロジェクトで開発されていた Pure Java 製の JavaScript (ECMAScript) エンジンです。JDK 1.1 からの長い歴史を持ち (そして一時期の暗黒時代を経て)、Java SE 6 の Scripting API 実装として標準バンドルされています。

Java SE 6 の Rhino 1.6 は以下の仕様に準拠しています。ECMA-290 ECMAScript Components Specification ECMA-357 ECMAScript for XML (E4X)

ECMA 仕様では構文や組み込み関数などを定義しているのみであり、一般的なブラウザで使用できる documentalert() などの実行環境に依存する機能は用意されていません。しかし同等の暗黙オブジェクトを Java 側で追加することが可能です。

Rhino お試し実行

Java SE 6 以降がインストールされている環境であれば以下のアプレットでスクリプトの試行が可能です。実際どの程度のことが出来るのか試してみたい場合に使用して下さい。使い方はこちらScriptBox

構文と機能

Rhino 1.6 でサポートされている機能は以下の通りです (実地で調べた結果なので他にもあるかもしれません)。

構文/機能 1.6
定数リテラル null, true, false, undefined, NaN, Infinity
ビルトイン関数 eval(), parseInt(), parseFloat(), escape(), unescape(), isNaN(), isFinite(), print(), println()
ビルトイン型 Array, Boolean, Number, String, Date, Math, Function, Object, RegExp, void
コメント /* */, //
演算子 +, -, *, /, %, ++, --, <<, <<<, >>, =, +=, -=, *=, /=, %=, &=, |=, ^=, ==, !=, <=, >=, <, >, !, &&, ||, &, |, ^, ~, 条件? A: B, new, delete, typeof
if 構文 if(条件){
  …
} else if(条件){
  …
} else {
  …
}
switch 構文 switch(値){   // 値は文字列も可
case 値1:
  …
  break;

default:
  …
  break;
}
for 構文 for(初期化; 条件; 加減算){
  …
}
for(変数 in 集合){
  …
}
for each(変数 in 集合){
  …
}
while 構文 while(条件){
  …
}
do{
  …
} while(条件);
try 構文 try{
  …
  throw 値;   //呼び出し先の関数でも可
} catch(変数){
  …
} finally {
  …
}
変数定義 var x = 100;
関数定義 function foo(引数1,引数2,…){
  …
  return 値;
}
プロトタイプ function Foo(){…}
Foo.prototype.bar = function(){…}
var x = new Foo();
x.bar();
正規表現 "abcdefg".search(/ABC/i)
ECMA3 class, public, protected, private, final, abstract, static, synchronized, native
(予約のみ)
XML var x = <foo><bar>100</bar></foo>;

Java のクラスを使用する

スクリプト内での Java クラスの使用は至って簡単です。単純に FQN で指定するだけでインスタンスの構築からメソッド呼び出し、フィールドアクセスまであらゆる操作が可能です。

// print("hello, world") と等価
java.lang.System.out.println("hello, world");

// ダイアログを表示
javax.swing.JOptionPane.showMessageDialog(null,"hello, world");

// 初期化したボタンを結果として返す
var button = new javax.swing.JButton();
button.setLabel("ボタン");
button;

プリミティブ型の配列を構築する時だけは少々コツが必要で、Arrayクラスにプリミティブ Wrapper クラスの TYPE を指定します。

var buffer = java.lang.reflect.Array.newInstance(
  java.lang.Byte.TYPE, 1024);

FQN での指定はコードが冗長になりがちですが JavaImporter() を使用することで import 宣言と同様にパッケージ名を省略することが出来ます (ただし with の使用はあまり効率が良くないと Rhino 公式サイトで言及されています)。

var ns = JavaImporter(java.awt, javax.swing);
with(ns){
  var button = new JButton();
  button.setFont(new Font("Dialog", Font.PLAIN, 12));
  button.setLabel("ボタン");
  button;
}

Rhino から Java で可能な機能が全て利用できる事に注意してください。サンドボックスの外で動作している場合、スクリプトから以下の処理が行えてしまいます。

  • ファイルの読み込み、書き込み、一覧取得、削除
  • 任意のサーバとの通信
  • 任意のクラスをロード、実行

スタンドアロンアプリケーションや JEE コンテナなどで信頼できない (誰が作ったか分からない) スクリプトを動作させることは致命的な脆弱性につながりますので注意してください。

暗黙オブジェクトの追加

スクリプト実行前に ScriptContextBindingsにオブジェクトを指定することでスクリプトの暗黙オブジェクトを追加することが出来ます。

ScriptContext context = engine.getContext();
Bindings bind = context.getBindings(ScriptContext.ENGINE_SCOPE);
bind.put("foo", new Object(){
  public String getBar(){
    return "bar";
  }
  });

ENGINE_SCOPEは同一 ScriptEngine インスタンス上で共有されるバインディングをを表します。この他に同一 ScriptEngineFactory インスタンスで共有される GLOBAL_SCOPEを使用することが出来ます。

スクリプトからはバインドしたオブジェクトの public フィールドやメソッドにアクセスすることが出来ます。特にオブジェクトが getter/setter を持つ場合、プロパティ名でもアクセスすることが出来ます (上記の例では手を抜いて無名内部クラスで行っています)。

// foo.bar でも良い
foo.getFoo();

また後述のビルトイン関数のように暗黙オブジェクトを定義するスクリプトを同一エンジン上で実行しておくことでも目的を果たすことが出来ます。

これにより documentwindow などに相当するオブジェクトをゴリゴリ作成してブラウザ並みに仕立て上げることも可能になっています。

ビルトイン関数の追加

オブジェクトを指定しなくても使用できるトップレベルのビルトイン関数は、あらかじめ同一のエンジン上でその関数の定義を評価しておく事で使用することができます。例えば alert() (本来は window のメソッドですが…) を定義するには以下のように関数定義スクリプトを同一エンジン上で実行しておきます。

engine.eval(
  "function alert(msg){" +
  "javax.swing.JOptionPane.showMessageDialog(null,msg);}"
);
engine.eval(yourScript);

プロパティ等

ScriptEngine#put() で設定できる値。

名前 説明
javax.script.filename スクリプトのファイル名。例外メッセージなどに使用される。