JSR 274: The BeanShell Scripting Language

JSR 274で仕様の標準化が進められているBeanShellは、JavaVM上で動作するスクリプト言語である。文法がJavaのそれとほとんど同じという点が大きな特徴といえる。もちろん、動的な型付けに対応しているなど、スクリプト言語としての利点も兼ね揃えている。

文法でJavaと大きく異なる点は、クラスやインタフェースが定義できない点だ。ただし、Javaで書かれたクラスを呼び出すことができるため、自作のクラスが使えないというわけではない。また、Javaプログラム側からBeanShellスクリプトを実行するための機能も備えている。

BeanShellの実装そのものはすでにバージョン2.0のベータ版が公開されており、十分に実用レベルに達しているといっていい。JSR 274による仕様の標準化は、BeanShellの正式な構文や、最低限必要となるコマンドや実行環境などについて定義することを目的としている。

JavaVM上で動作するスクリプト処理系としては、BeanShell以外にもJSR 241で標準化が進められているGroovy、Rubyの実装であるJRuby、Pythonの実装であるJythonなどが有名。

BeanShellを試す

BeanShellの実装はこのページよりダウンロードすることができる。本稿執筆時点での最新版はバージョン2.0 beta 4(ファイル名bsh-2.0b4.jar)。

起動と基本的な使い方

起動はjavaコマンドで行う。BeanShellにはインタプリタモードとコンソールモードがある。ダウンロードしたファイルを任意のフォルダに配置したら、それをクラスパスに含めてコマンドプロンプトからプロンプト1またはプロンプト2のようにして起動する。

プロンプト1 インタプリタモードでの起動

> java -classpath [PATH_TO_JARFILE] bsh.Interpreter
BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh %

プロンプト2 コンソールモードでの起動

> java -classpath [PATH_TO_JARFILE] bsh.Console

インタプリタモードでは、BeanShellはコマンドプロンプト上で動作する。コンソールモードの場合、図1のように内部にワークスペースを持つウィンドウが起動する。ワークスペースではインタプリタモードと同様にBeanShellスクリプトを実行できるほか、簡易なエディタや標準入出力のキャプチャなどといった機能を備えている。

図1 コンソールモード

BeanShellスクリプトの基本的な構文はJavaのそれを踏襲している。BeanShellそのものがJavaを使って記述されており、スクリプト中ではJavaのクラスが利用可能。たとえば最も基本となる「Hello BeanShell!」と出力したい場合はプロンプト3のように記述する。

プロンプト3 文字列を出力する例

bsh % System.out.println("Hello BeanShell!");
Hello BeanShell!

BeanShell独自の組み込みコマンドも用意されている。プロンプト3の例は、printコマンドを利用してプロンプト4のようにも書ける。

プロンプト4 独自のコマンドも用意されている

bsh % print("Hello BeanShell!");

Swingを利用したGUIプログラムはプロンプト5のような具合だ。この例の場合、図2のようなウィンドウが表示される。

プロンプト5 Swingを利用した例

bsh % import javax.swing.*;
bsh % JFrame f = new JFrame("BeanShell Sample");
bsh % f.getContentPane().setLayout(new FlowLayout());
bsh % JButton button1 = new JButton("Hello");
bsh % JButton button2 = new JButton("BeanShell!");
bsh % f.getContentPane().add(button1);
bsh % f.getContentPane().add(button2);
bsh % f.setSize(300, 200);
bsh % f.setVisible(true);

図2 ウィンドウの表示

スクリプトをファイルから読み込んで実行する場合は、runコマンドまたはsourceコマンドを利用する。プロンプト5の例で1行ずつ実行したスクリプトを「JFrameSample.bsh」というファイルにまとめて記述した場合(リスト1)、runコマンドでプロンプト6のように実行できる。

リスト1 JFrameSample.bsh

import javax.swing.*;

JFrame f = new JFrame("BeanShell Sample");
f.getContentPane().setLayout(new FlowLayout());

JButton button1 = new JButton("Hello");
JButton button2 = new JButton("BeanShell!");
f.getContentPane().add(button1);
f.getContentPane().add(button2);

f.setSize(300, 200);
f.setVisible(true);

プロンプト6 runコマンドでスクリプトを実行

bsh % run("JFrameSample.bsh");

sourceコマンドを利用した場合、スクリプト実行後のプロンプトで変数などの実行結果が有効になる。したがってプロンプト7のように、JFrameSample.bshを実行したままの状態で、内部で利用している変数にアクセスすることが可能。

プロンプト7 sourceコマンドでスクリプトを実行

bsh % source("JFrameSample.bsh");
bsh % f.setLocation(200,300);

多くのスクリプト言語と同様、動的な型付けにも対応しているため、JFrameSample.bshは型宣言を省略してリスト2のように記述することもできる。

リスト2 動的型付けにも対応

import javax.swing.*;

f = new JFrame("BeanShell Sample");
f.getContentPane().setLayout(new FlowLayout());

button1 = new JButton("Hello");
button2 = new JButton("BeanShell!");
f.getContentPane().add(button1);
f.getContentPane().add(button2);

f.setSize(300, 200);
f.setVisible(true);

当然、クラスパスさえ通っていれば自作のJavaクラスを利用することも可能だ。たとえばリスト3のようなクラスを作ったとすると、リスト4のようなスクリプトが実行できる。クラスのプロパティに対してはアクセッサメソッド(getXXXX())を用意することでアクセスできるようになっている。

リスト3 Point2D.java

public class Point2D {
        private double x;
        private double y;

        public Point2D(double x, double y) {
                this.x = x;
                this.y = y;
        }

        public double getX() {
                return this.x;
        }

        public double getY() {
                return this.y;
        }

        public void print() {
                System.out.println("(" + this.x + ", "+ this.y + ")");
        }
}

リスト4 自作Javaクラスの利用

point = new Point2D(100, 200);
point.print();
print("x = " + point.x);

Scripted MethodとScripted Object

メソッド(Scrioted Methodと呼ぶ)の定義はリスト5のように行う。distanceは先ほどのPoint2Dクラスを使い、2点間の距離を計算するメソッドである。

リスト5 メソッドの利用例

distance(p1, p2) {
        double xx = Math.pow(p2.x - p1.x, 2);
        double yy = Math.pow(p2.y - p1.y, 2);
        return Math.sqrt(xx + yy);
}

point1 = new Point2D(60, 40);
point2 = new Point2D(20, 10);
print(distance(point1, point2));

BeanShellではメソッドクロージャの役割を果たす「Scripted Object」を定義することができる。Scripted Objectは複数の変数/メソッドをまとめたもので、リスト6のように定義する。最後の「return this;」は必須で、これによって自身のオブジェクトを返す。このスクリプトを実行するとプロンプト8のような結果になる。

リスト6 ScriptedObjectSample.bsh

square(p, x, y) {
        Point2D point = p;
        double dx = x;
        double dy = y;

        printArea() {
                print("area = " + (dx * dy));
        }

        return this;
}

p1 = new Point2D(20, 50);
square = square(p1, 20, 30);
square.p.print();
square.printArea();

プロンプト8 ScriptedObjectSample.bshの実行例

bsh % run("ScriptedObjectSample.bsh");
(20.0, 50.0)
area = 600.0

Javaプログラムからの呼び出し

JavaプログラムからBeanShellスクリプトを呼び出すこともできる。BeanShellを構成するJavaクラスの中に「bsh.Interpreter」がある。インタプリタモードで起動するときに使うクラスだ。このクラスを利用することにより、Javaプログラム側からBeanShellスクリプトを実行できる。

具体的には、Interpreterのsource()メソッドまたはrun()メソッドにスクリプトのファイル名を渡せばよい。たとえばリスト7のような具合だ。

リスト7 ScriptCallSample.java - JavaプログラムからBeanShellスクリプトの呼び出し

import bsh.Interpreter;
import bsh.EvalError;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ScriptCallSample {
        public static void main(String[] args) {
                try {
                        Interpreter interpreter = new bsh.Interpreter();
                        Object result = interpreter.source("JFrameSample.bsh");
                }
                catch (FileNotFoundException e) {
                        e.printStackTrace();
                }
                catch (IOException e) {
                        e.printStackTrace();
                }
                catch (EvalError e) {
                        e.printStackTrace();
                }
        }
}

上記のように、BeanShellはJavaのために作られたスクリプト言語であり、Javaプログラムとの親和性が非常に高い。Java SE 6では標準でJavaScriptエンジンであるRhinoがバンドルされているが、Java SE 7ではそれ以外の言語の処理系も標準サポートに加えることが検討されており、その候補としてBeanShellの名前も挙がっている。Javaプラットフォームでのスクリプト言語サポートが強化される中、BeanShellを始めとしたJava系スクリプト言語にかけられる期待は大きい。