木曜日 5 23, 2013

Nashorn から JavaFX を呼び出す

はじめに

Nashorn から JavaFX を呼び出す方法が決まった様です。サンプルコードとともにご覧下さい。

おことわり

以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文は https://blogs.oracle.com/nashorn/entry/jjs_fx でご覧頂けます。翻訳文の URL は https://blogs.oracle.com/nashorn_ja/entry/jjs_fx です。

訳文

JavaFX の開発者達と話し合った結果、JavaFX と Nashorn シェルの起動スクリプトの連携方法について良い案が見つかりました。この案では jjs コマンドに -fx フラグを付けるとjavafx.application.Application を使って起動します。その後は Nashorn から JavaFX の呼び出しはとても簡単です。

基本的なコマンドラインは jjs -fx fxscript.js の様な形になります。そこに -scripting オプションや -- オプションを付けて jjs -fx -scripting fxscript.js -- my script args の様に実行することも出来ます。

以下のサンプルコードの処理内容は過去にこの Blog に載せたサンプルから取っています。jjs に渡すスクリプトに JavaFX 用の init, start, stop 関数を含める事も可能です。以前と異なるのは、それら JavaFX 用の関数を定義せずに、いきなりスクリプトを書き始めることが出来ることです。元々の Hello World プログラムはこうでした。

var Button    = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene     = javafx.scene.Scene;

function start(stage) {
    stage.title = "Hello World!";
    var button = new Button();
    button.text = "Say 'Hello World'";
    button.onAction = function() print("Hello World!");
    var root = new StackPane();
    root.children.add(button);
    stage.scene = new Scene(root, 300, 250);
    stage.show();
}

これを次の様に start 関数を定義しない形で書く事が出来ます。

var Button    = javafx.scene.control.Button;
var StackPane = javafx.scene.layout.StackPane;
var Scene     = javafx.scene.Scene;

$STAGE.title = "Hello World!";
var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();

stage 変数は $STAGE グローバル変数に代わり、start() 関数の引数として渡す必要はなくなりました。

また、利便性を上げるために JavaFX のクラスをまとめてロードするための仕組みも用意しました。推奨されるのは(オブジェクトの生成と静的なフィールドアクセスに)必要なクラスだけをロードすることですが、プロトタイピングなどで手早く実装を進めたい場合には、まとめてロード出来るととても便利です。

その仕組みを利用すると、先ほどの Hello World プログラムは次の様に書き換えられます。

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

$STAGE.title = "Hello World!";
var button = new Button();
button.text = "Say 'Hello World'";
button.onAction = function() print("Hello World!");
var root = new StackPane();
root.children.add(button);
$STAGE.scene = new Scene(root, 300, 250);
$STAGE.show();

この方法で読み込めるクラスは以下の通りです。

fx:base.js
    javafx.stage.Stage
    javafx.scene.Scene
    javafx.scene.Group
    javafx/beans
    javafx/collections
    javafx/events
    javafx/util

fx:graphics.js
    javafx/animation
    javafx/application
    javafx/concurrent
    javafx/css
    javafx/geometry
    javafx/print
    javafx/scene
    javafx/stage

fx:controls.js
    javafx/scene/chart
    javafx/scene/control

fx:fxml.js
    javafx/fxml

fx:web.js
    javafx/scene/web

fx:media.js
    javafx/scene/media

fx:swing.js
    javafx/embed/swing

fx:swt.js
    javafx/embed/swt

もう少しサンプルコードをご紹介します。

// fx3d.js

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

var material = new PhongMaterial();
material.diffuseColor = Color.LIGHTGREEN;
material.specularColor = Color.rgb(30, 30, 30);

var meshView = Java.toJavaArray([
    new Box(200, 200, 200),
    new Sphere(100),
    new Cylinder(100, 200)
], "javafx.scene.shape.Shape3D");

for (var i = 0; i != 3; i++) {
    meshView[i].material = material;
    meshView[i].translateX = (i + 1) * 220;
    meshView[i].translateY = 500;
    meshView[i].translateZ = 20;
    meshView[i].drawMode = DrawMode.FILL;
    meshView[i].cullFace = CullFace.BACK;
};

var pointLight = new PointLight(Color.WHITE);
pointLight.translateX = 800;
pointLight.translateY = -200;
pointLight.translateZ = -1000;

var root = new Group(meshView);
root.children.add(pointLight);

var scene = new Scene(root, 800, 800, true);
scene.fill = Color.rgb(127, 127, 127);
scene.camera = new PerspectiveCamera(false);
$STAGE.scene = scene;
$STAGE.show();

// ColorfulCircles.js

load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

var WIDTH = 500;
var HEIGHT = 600;
var animation;

function setup(primaryStage) {
    var root = new Group();
    primaryStage.resizable = false;
    var scene = new Scene(root, WIDTH, HEIGHT);
    scene.title = "Colourful Circles";
    primaryStage.scene = scene;

    // 1 つ目の Circle のリスト
    var layer1 = new Group();
    for(var i = 0; i < 15; i++) {
        var circle = new Circle(200, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.2);
        circle.strokeWidth = 4;
        layer1.children.add(circle);
    }

    // 2 つ目の Circle のリスト
    var layer2 = new Group();
    for(var i = 0; i < 20; i++) {
        var circle = new Circle(70, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.1);
        circle.strokeWidth = 2;
        layer2.children.add(circle);
    }

    // 3 つ目の Circle のリスト
    var layer3 = new Group();
    for(var i = 0; i < 10; i++) {
        var circle = new Circle(150, Color.web("white", 0.05));
        circle.strokeType = StrokeType.OUTSIDE;
        circle.stroke = Color.web("white", 0.16);
        circle.strokeWidth = 4;
        layer3.children.add(circle);
    }

    // それぞれのレイヤーに blur エフェクトを掛ける
    layer1.effect = new BoxBlur(30, 30, 3);
    layer2.effect = new BoxBlur(2, 2, 2);
    layer3.effect = new BoxBlur(10, 10, 3);

    // ウィンドウと同じ大きさの矩形を作成し、カラーグラデーションを設定する
    var colors = new Rectangle(WIDTH, HEIGHT,
            new LinearGradient(0, 1, 1, 0, true, CycleMethod.NO_CYCLE,
                               new Stop(0,    Color.web("#f8bd55")),
                               new Stop(0.14, Color.web("#c0fe56")),
                               new Stop(0.28, Color.web("#5dfbc1")),
                               new Stop(0.43, Color.web("#64c2f8")),
                               new Stop(0.57, Color.web("#be4af7")),
                               new Stop(0.71, Color.web("#ed5fc2")),
                               new Stop(0.85, Color.web("#ef504c")),
                               new Stop(1,    Color.web("#f2660f"))));
    colors.blendMode = BlendMode.OVERLAY;

    // メインのコンテンツを作成
    var group = new Group(new Rectangle(WIDTH, HEIGHT, Color.BLACK),
                          layer1, 
                          layer2,
                          layer3,
                          colors);
    var clip = new Rectangle(WIDTH, HEIGHT);
    clip.smooth = false;
    group.clip = clip;
    root.children.add(group);

    // 全ての円を含むリストを作成
    var allCircles = new java.util.ArrayList();
    allCircles.addAll(layer1.children);
    allCircles.addAll(layer2.children);
    allCircles.addAll(layer3.children);

    // allCircles に入っている円がランダムに移動するアニメーションを作成
    animation = new Timeline();
    for each (var circle in allCircles) {
        animation.getKeyFrames().addAll(
              new KeyFrame(Duration.ZERO, // 開始時間を 0 秒に設定
                           new KeyValue(circle.translateXProperty(), Math.random() * WIDTH),
                           new KeyValue(circle.translateYProperty(), Math.random() * HEIGHT)),
              new KeyFrame(new Duration(20000), // 終了時間を 20 秒に設定
                           new KeyValue(circle.translateXProperty(), Math.random() * WIDTH),
                           new KeyValue(circle.translateYProperty(), Math.random() * HEIGHT))
              );
    }
    animation.autoReverse = true;
    animation.cycleCount = Animation.INDEFINITE;
}

function stop() {
    animation.stop();
}

function play() {
    animation.play();
}

function start(primaryStage) {
    setup(primaryStage);
    primaryStage.show();
    play();
}

追記 : この仕組みは CCC に承認され確定しました。

火曜日 5 21, 2013

Nashorn で iTunes に曲を再生させる

はじめに

Nashorn を日常使いする例として、Nashorn と AppleScript の連携方法が紹介されていました。ご参照下さい。

おことわり

以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文は https://blogs.oracle.com/nashorn/entry/playing_itunes_from_nashorn でご覧頂けます。翻訳文の URL は https://blogs.oracle.com/nashorn_ja/entry/playing_itunes_from_nashorn です。

訳文

もしあなたが Mac OS X をお使いでしたら、Nashorn から AppleScript を呼び出すことが出来ます。例えば iTunes の曲を再生したい場合は、次のようなスクリプトで実現できます。

#!/usr/bin/jjs -scripting
#
var song = "Bring It On Home";

$EXEC("/usr/bin/osascript", <<<EOD);
tell application "iTunes"
    activate
    play track "${song}" in playlist 1
end tell
EOD

月曜日 5 20, 2013

Nashorn で HTTP サーバを実装する

はじめに

Nashorn で HTTP サーバを実装するサンプルが紹介されていました。ご参照下さい。

おことわり

以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文は https://blogs.oracle.com/nashorn/entry/http_server_written_in_nashorn でご覧頂けます。翻訳文の URL は https://blogs.oracle.com/nashorn_ja/entry/http_server_written_in_nashorn です。

訳文

今日は少しひねりを加えたサンプルプログラムをご紹介します。

Node.js はホットで、沢山の可能性を秘めています。Node.js を触っていて、Oracle に加わる前に働いていたサーバ会社で使っていたツールの事を思い出しました。その時はネットワーク越しにサーバに接続して開発をしていました。私はキーボード作業が好きじゃなかったのですが、そこでは SSH かコンソール接続しかサーバにアクセスする手段がありませんでした。そこで簡単な HTTP サーバを作り、それをプロキシーにして、リポジトリの作業やプログラムのビルド、ファイルの編集などをブラウザ経由で実行出来るようにすることにしました。この仕組みは簡単に作成できて、なおかつとても柔軟でした。

世の中には既に HTTP サーバは沢山あります(その多くが Java で掛かれています)。それらを自分用に手直しすることも出来ますが、Nashorn で一から実装出来ることを証明してみたくなりました。ささっと作ってみたのがこちらです。

#!/usr/bin/jjs -scripting
#

var Thread            = java.lang.Thread;
var ServerSocket      = java.net.ServerSocket;
var PrintWriter       = java.io.PrintWriter;
var InputStreamReader = java.io.InputStreamReader;
var BufferedReader    = java.io.BufferedReader;
var FileInputStream   = java.io.FileInputStream;
var ByteArray         = Java.type("byte[]");

var PORT = 8080;
var CRLF = "\r\n";
var FOUROHFOUR = <<<EOD;
<HTML>
    <HEAD>
        <TITLE>404 Not Found</TITLE>
    </HEAD>
    <BODY>
        <P>404 Not Found</P>
    </BODY>
</HTML>
EOD

var serverSocket = new ServerSocket(PORT);

while (true) {
    var socket = serverSocket.accept();

    try {
        var thread = new Thread(function() { httpRequestHandler(socket); });
        thread.start();
        Thread.sleep(100);
    } catch (e) {
        print(e);
    }
}

function httpRequestHandler(socket) {
    var out       = socket.getOutputStream();
    var output    = new PrintWriter(out);
    var inReader  = new InputStreamReader(socket.getInputStream(), 'utf-8');
    var bufReader = new BufferedReader(inReader);

    var lines = readLines(bufReader);

    if (lines.length > 0) {
        var header = lines[0].split(/\b\s+/);

        if (header[0] == "GET") {
            var URI = header[1].split(/\?/);
            var path = String("./serverpages" + URI[0]);

            try {
                if (path.endsWith(".jjsp")) {
                    var body = load(path);
                    if (!body) throw "JJSP failed";
                    respond(output, "HTTP/1.0 200 OK", "text/html", body);
                } else {
                    sendFile(output, out, path);
                }
            } catch (e) {
                respond(output, "HTTP/1.0 404 Not Found", "text/html", FOUROHFOUR);
            }
        }
    }

    output.flush();
    bufReader.close();
    socket.close();
}

function respond(output, status, type, body) {
    sendBytes(output, status + CRLF);
    sendBytes(output, "Server: Simple Nashorn HTTP Server" + CRLF);
    sendBytes(output, "Content-type: ${type}" + CRLF);
    sendBytes(output, "Content-Length: ${body.length}" + CRLF);
    sendBytes(output, CRLF);
    sendBytes(output, body);
}

function contentType(path) {
    if (path.endsWith(".htm") ||
        path.endsWith(".html")) {
      return "text/html";
    } else if (path.endsWith(".txt")) {
      return "text/text";
    } else if (path.endsWith(".jpg") ||
               path.endsWith(".jpeg")) {
      return "image/jpeg";
    } else if (path.endsWith(".gif")) {
      return "image/gif";
    } else {
      return "application/octet-stream";
    }
}

function readLines(bufReader) {
    var lines = [];

    try {
        var line;
        while (line = bufReader.readLine()) {
            lines.push(line);
        }
    } catch (e) {
    }

    return lines;
}

function sendBytes(output, line) {
    output.write(String(line));
}

function sendFile(output, out, path) {
    var file = new FileInputStream(path);

    var type = contentType(path);
    sendBytes(output, "HTTP/1.0 200 OK" + CRLF);
    sendBytes(output, "Server: Simple Nashorn HTTP Server" + CRLF);
    sendBytes(output, "Content-type: ${contentType(path)}" + CRLF);
    sendBytes(output, "Content-Length: ${file.available()}" + CRLF);
    sendBytes(output, CRLF);
    output.flush();

    var buffer = new ByteArray(1024);
    var bytes = 0;

    while ((bytes = file.read(buffer)) != -1) {
        out.write(buffer, 0, bytes);
    }
}

たった 84 行の JavaScript コードで実装出来ました。HTTP の仕様をもっと沢山実装する事も出来ますが、実証テストとしてはこれで十分でしょう。

この HTTP サーバを単に HTML や JPEG ファイルのダウンロードの為に使用することも出来ますが、プログラムの真ん中当たりにある以下のコードにご注目下さい。

                if (path.endsWith(".jjsp")) {
                    var body = load(path);
                    if (!body) throw "JJSP failed";
                    respond(output, "HTTP/1.0 200 OK", "text/html", body);

これは、リクエストされたファイルのファイル名が .jjsp で終わっていたら、その中身を JavaScript のプログラムとして評価し、その実行結果を HTTP の応答として返すという処理です。

次のサンプル .jjsp ファイルは私がテストの為に用意したものです。

#!/usr/bin/jjs -scripting
#

var colours = {
    java: "BLUE",
    js: "RED", 
    css: "GREEN",
    html: "ORANGE"
};

function colorize(file) {
    var suffix = file.substr(file.lastIndexOf(".") + 1);
    var colour = colours[suffix];
    if (colour) {
        return "<FONT COLOR='${colour}'>${file}</FONT>";
    }
    return file;
}

var files = `ls`.trim().split("\n");
files = files.map(colorize);
files = files.map(function(file) "${file}<BR />");
files = files.join("\n");

var HTML = <<<EOD;
<HTML>
    <HEAD>
        <TITLE>Simple HTML</TITLE>
    </HEAD>
    <BODY>
        <img width="256" height="138" src="rrr256x138.jpg" alt="rrr256x138" />
        <BR />
        <FONT FACE="Courier New" SIZE="2">
        ${files}
        </FONT>
    </BODY>
</HTML>
EOD

HTML;

この様に JavaScript のコードも HTML のタグも高い可読性を保ったままプログラムにまとめる事が出来ます。このスクリプトは ls コマンドでディレクトリの一覧を取得し、ファイルの拡張子に応じて色付けをしています。私の手元では以下のような実行結果になりました。

FX3D.js
HTML.js
HelloWorld.java
Server
Server.zip
SimpleHTTPServer.js
Test.class
Test.java
Text.js
canvas.js
colourfulcircles.js
hellofxml.js
helloworld1.js
helloworld2.js
http.js
introspect.js
jogl
languages.css
languages1.js
languages2.js
script.js
scripting.fxml
serverpages
slow1.js
suspect.js
test.js
threading.js
trends.css
trends.js
twitter4j-core-3.0.1.jar
twitter4j.properties

このスクリプトは様々な応用が出来そうです。

About

JavaVM 用 JavaScript エンジンの Nashorn について情報発信しているブログです。Nashorn の読み方はナズホーンです。

Search

Archives
« 5月 2013 »
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
22
24
25
26
27
28
29
30
31
 
       
Today