JavaFX と ScriptEngine を組み合わせた学習用アプリケーションを作る

この記事は [JavaFX Advent Calendar 2015](http://qiita.com/advent-calendar/2015/javafx) の 5日目の記事です。

昨日の4日目は skrb さんの「[Wheel or Touch](http://skrb.hatenablog.com/entry/2015/12/04/001000)」です。明日の6日目は bitter_fox さんです。

# 挨拶
はじめまして、トースト小僧と申します。今回を機にQiitaを始めました。よろしくお願いします。

# 自己紹介
Java で GUI アプリケーションを作るのが趣味です。インターネット系企業でプログラマーをやっています。会社では Storm Trident を使ったバックエンドのシステムや API や Android アプリを作っています。

# 概要
この記事では JavaFX と ScriptEngine を組み合わせて、プログラミング言語の学習に使えるツールを作る過程を紹介します。

## ScriptEngine
Java からスクリプト言語を動かすための仕組みで、デフォルトでは JavaScript がサポートされています。ほか、Groovy や Jython, JRuby, Scala 等も実装されているようです。

今回はGroovyとJythonを実装してみます。

## 依存の追加
build.gradle の dependencies に下記を追加します。

```groovy:dependencies
compile 'org.codehaus.groovy:groovy-all:2.4.5'
compile 'org.python:jython-standalone:2.7.0'
```

## ScriptEngine の取得
各言語のScriptEngineオブジェクトは下記の通り取得します。

```java
final ScriptEngine python = new PyScriptEngineFactory().getScriptEngine();
final ScriptEngine groovy = new GroovyScriptEngineFactory().getScriptEngine();
```

ちなみに JavaScript は標準で用意されていて、下記で取得できます。

```
final ScriptEngine js = new ScriptEngineManager().getEngineByName("javascript");
```

## 標準入出力のリダイレクト
通常、ScriptEngineを使って実行したコードのログ・エラー出力は標準入出力にされます。JavaFXのアプリケーション上でこれを表示したい場合はリダイレクトする必要があります。

今回はStringWriterを使ってStringBuilderに処理結果を入れます。

```java
final StringBuilder result = new StringBuilder();
final StringWriter writer = new StringWriter();

try {
    if (python != null) {
        final ScriptContext context = python.getContext();
        context.setWriter(new PrintWriter(writer));
        context.setErrorWriter(new PrintWriter(writer));
    }
    if (groovy != null) {
        final ScriptContext context = groovy.getContext();
        context.setWriter(new PrintWriter(writer));
        context.setErrorWriter(new PrintWriter(writer));
    }

    final java.lang.Object run = js.eval(script);
    result.append(writer.toString()).append(System.lineSeparator());
    if (run != null) {
        result.append("return = ").append(run.toString());
    }
    writer.close();
}

……
```

## JavaFX成分
左側のTextAreaにスクリプトコードを入力し、右側のTextAreaで実行結果を表示します。それだけです。~~もう(別にJavaFXじゃなくて)いいじゃん……~~

```java
final String result = func.runScript(scripterInput.getText(), lang).get();
if (StringUtils.isEmpty(result)) {
    return;
}
scripterOutput.setText(result);
```

# 試す
![advent2015_1.png](https://qiita-image-store.s3.amazonaws.com/0/102004/d65465d5-9e84-052c-1ea6-39ddf49d9665.png)
起動するとこんな感じです。

![advent2015_2.png](https://qiita-image-store.s3.amazonaws.com/0/102004/73ce08b7-b7c3-3bcd-5bc7-49f91d473098.png)
左側のTextAreaに、適当にスクリプトを打ち込んでみましょう。

![advent2015_3.png](https://qiita-image-store.s3.amazonaws.com/0/102004/02e06bda-aa06-4874-b20c-3d90e7dfe91f.png)
run ボタンを押すかctrl+enterでスクリプトが実行され、その結果が右側のTextAreaに表示されます。
めでたしめでたし……

---

# おまけ
CSS を切り替える機能をつけてみました。
![advent2015_4.png](https://qiita-image-store.s3.amazonaws.com/0/102004/5301880e-6e06-554e-8b3a-0dd3afe006b5.png)



詳しくは[ソースコード](https://github.com/toastkidjp/javafx_advent2015/blob/master/src/main/java/gui/jfx/MainController.java#L139)をご覧ください。

[こんな感じ](https://github.com/toastkidjp/javafx_advent2015/blob/master/src/main/resources/css/leaf.css)で用意した CSS を下記のように適用すればOKです。

```java
final ObservableList<String> stylesheets = thisStage.getScene().getStylesheets();
// 前回適用した内容をクリア.
if (stylesheets != null) {
    stylesheets.clear();
}
Application.setUserAgentStylesheet("MODENA");
stylesheets.add("path/to/css");
```

# GitHub repository
[javafx_advent2015](https://github.com/toastkidjp/javafx_advent2015)