水曜日 3 19, 2014

Java 8 GA!

Java 8 が リリース されました!
これが Nashorn の最初のリリースになります。

Nashorn の JavaScript インタラクティブ・シェルを起動するコマンドは jjs です。
jjs コマンドは JRE 8 と JDK 8 の両方に含まれています。

Nashorn を使用したスクリプティングの方法は こちら をご覧下さい。
jjs コマンドのリファレンスは ここ にあります。

金曜日 7 05, 2013

Nashorn と Lambda

はじめに

今回は Java 8 の Lambda と Nashorn の連携についてご紹介します。Lamdba を引き数に取る Java のメソッドを Nashorn から呼び出す方法と Nashorn から Java の Parallel Stream を操作する方法をご覧頂けます。

おことわり

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

訳文

Java 8 Lambda を設計したブライアン・ゲーツから、Nashorn で Lambda を利用するサンプルプログラムを書いてみてはどうかと提案を受けました。私はずっと Nashorn にかかりきりでしたので、Lambda は殆ど触っていませんでした。そこで スチュワート・マークスが書いた Lambda のサンプルコード を読んでみたところ、それ程難しくない事が分かりました。Lambda API の詳細は JDK 8 b92 API 仕様 のページで見つかりました。

Lambda を Nashorn から使用する仕組みは、JavaScript の知識があれば直ぐに理解できます。

  • Lambda を受け取るメソッドには JavaScript の function を渡します
  • JavaScript の配列は事前に Java の Collection に変換しておく必要があります
  • 複数行に渡るメソッド呼び出しを記述する際、Java では '.' を行の最初に書く事が多いですが、JavaScript にはセミコロンの自動挿入があるため、'.' は行の最後に書く必要があります。

これらを除けば、JavaScript から Lambda にアクセスするプログラムは Java で書いた場合と殆ど変わりありません。

#!/usr/bin/env jjs -scripting

var copyright = <<<EOS;
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
EOS

var Collectors = java.util.stream.Collectors;

// コピーライトの文字列を空白文字で分割します
var tokens = copyright.split(/\s+/);

// JavaScript の配列 (tokens) を Java の ArrayList に変換します
var list = new java.util.ArrayList();
tokens.map(function(e) list.add(e));

// 結果を格納するための JavaScript の配列
var result = [];

// 処理を並列化します
list.parallelStream().
    // 単語のみを抜き出します
    filter(function(t) t.match(/^[A-Za-z]+$/)).
    // 大文字を小文字に変換します
    map(function(t) t.toLowerCase()).
    // 重複している単語を除きます
    collect(Collectors.groupingBy(function(t) t)).
    // 処理結果を JavaScript の配列に格納します
    forEach(function(t) result.push(t));

// 結果を並び替えます
result.sort();

print(result);

処理結果は以下の通りです。

a,above,advised,all,and,any,are,arising,be,binary,business,but,by,caused,code,
conditions,consequential,contributors,copyright,damages,derived,disclaimer,
documentation,endorse,even,event,express,fitness,following,for,form,from,
goods,holders,however,if,implied,in,is,its,liable,limited,list,loss,materials,
may,merchantability,must,name,names,negligence,neither,no,nor,not,of,on,or,
oracle,other,out,owner,particular,permitted,possibility,prior,procurement,
products,promote,provided,purpose,redistribution,redistributions,reproduce,
retain,rights,shall,software,source,specific,strict,substitute,such,that,
the,theory,this,to,tort,use,used,warranties,way,whether,with,without,written

補足

サンプルコード中の collect(Collectors.groupingBy(function(t) t)) の部分は distinct() で置き換え可能です。

filter, map, collect, forEach の引き数に弓括弧が無く、return 無しに値を返せているのは JavaScript 1.8 の Expression Closure です。この拡張記法は --no-syntax-extensions オプションで抑制出来ます。EcmaScript 6 のアロー記法 (t) => t.match(/^[A-Za-z]+$/) も使えるようにする予定です。function(t) => { return t.match(/^[A-Za-z]+$/); } の様に記述することも可能です。

木曜日 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

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

水曜日 4 10, 2013

Nashorn から JavaFX へのアクセスの実装比較

はじめに

Nashorn の開発チームでは Nashorn から JavaFX にアクセスする仕組みを幾つか検討している様です。現在検討されている実装とその pros / cons について紹介している記事がありましたので、翻訳してみました。ご参照下さい。

おことわり

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

訳文

Nashorn から JavaFX にアクセスする方法を検討しています。二種類の実装を試していますが、そのどちらもそれぞれ解決しないといけない問題があります。

問題のポイントは JavaFX と JDK と Nashorn の依存関係にあります。JavaFX は JDK と一緒に配布されていますが、JDK の一部ではありません。ビルドサイクルも JDK とは異なります。一方、Nashorn は JDK の一部です。Nashorn が JDK の一部であるという事は、JDK の外にある JavaFX に依存するコードは含められない事になります。さらに、JavaFX のプログラムを作る際には JavaFX に依存したコードを書く必要があります。具体的には javafx.application.Application クラスのサブクラスのインスタンスから処理を開始する様にプログラムを記述します。その為 Nashorn と JavaFX を橋渡しするプログラムを実装する際も JavaFX に依存したコードを書く必要がある事になりますが、そうするとそれは先ほどの依存関係があるため、JavaFX のコードは JDK には含められず、独立した場所に置いておく必要があります。

JavaFX へのアクセスを実装する一つ目の方法は jjs と同じようなシェルプログラムを用意する方法です。シェルプログラムの中で JavaFX の Application クラスの init, start, finish メソッドをオーバーライドしておき、外部スクリプトから即座に JavaFX の機能が使える様にします。このシェルプログラムの実装は Nashorn のソースコードリポジトリの中の nashorn/tools/fxshell に入れてあります。実装は以下の様になっています。

 /*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.nashorn.tools;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.stage.Stage;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
/**
 * このシェルは Nashorn JavaScript 向けに書かれたアプリケーションから JavaFX を実行する為のプログラムです
 */

public class FXShell extends Application {
    /**
     * スクリプトエンジンマネージャ
     */
    private ScriptEngineManager manager;
    /**
     * Nashorn スクリプトエンジンファクトリー
     */
    private NashornScriptEngineFactory factory;
    /**
     * Nashorn スクリプトエンジンのメインインスタンス
     */
    private ScriptEngine engine;
    /**
     * FX ランチャーがこのクラスのインスタンスを作成する際に必要
     */

    public FXShell() {
    }
    /**
     * メインエントリーポイント。実際には使用されない
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        launch(args);
    }
    /*
     * アプリケーション側でオーバーライドする
     */
    @Override
    public void init() throws Exception {
        // スクリプトエンジンマネージャ
        this.manager = new ScriptEngineManager();
        // Nashorn スクリプトエンジンファクトリーの取得。引数の処理に必要。
        for (ScriptEngineFactory engineFactory : this.manager.getEngineFactories()) {
             if (engineFactory.getEngineName().equals("Oracle Nashorn") &&
                 engineFactory instanceof NashornScriptEngineFactory) {
                this.factory = (NashornScriptEngineFactory)engineFactory;
            }
        }
        // 取得出来なかった場合
        if (this.factory == null) {
            System.err.println("Nashorn script engine not available");
            System.exit(1);
        }
        // コマンドラインと Java Network Launch Protocol パラメータの取得
        final Parameters parameters = getParameters();
        // スクリプトの場所とコマンドライン引数の格納用
        final List<String> paths = new ArrayList<>();
        final List<String> args = new ArrayList<>();
        // 適切な JNLP 名前付きパラメータの取得
        final Map<String, String> named = parameters.getNamed();
        for (Map.Entry<String, String> entry : named.entrySet()) {
            final String key = entry.getKey();
            final String value = entry.getValue();
            if ((key.equals("cp") || key.equals("classpath")) && value != null) {
                args.add("-classpath");
                args.add(value);
            } else if (key.equals("source") && value != null &&
                       value.toLowerCase().endsWith(".js")) {
                paths.add(value);
            }
        }
        // コマンドライン引数として適切な値の取得
        boolean addNextArg = false;
        boolean addAllArgs = false;
        for (String parameter : parameters.getUnnamed()) {
            if (addAllArgs || addNextArg) {
                args.add(parameter);
                addNextArg = false;
            } else if (parameter.equals("--")) {
                args.add(parameter);
                addAllArgs = true;
            } else if (parameter.startsWith("-")) {
                args.add(parameter);
                addNextArg = parameter.equals("-cp") || parameter.equals("-classpath");
            } else if (parameter.toLowerCase().endsWith(".js")) {
                paths.add(parameter);
            }
        }
        // 取得したパラメータで Nashorn スクリプトエンジンを作成する
        engine = factory.getScriptEngine(args.toArray(new String[args.size()]));
        // 外部スクリプトの読み込み
        for (String path : paths) {
            load(path);
        }
        // 外部スクリプトに init 関数が定義されていた場合は実行する
        try {
            ((Invocable) engine).invokeFunction("init");
        } catch (NoSuchMethodException ex) {
            // init 関数は存在していなくても良い
        }
    }
    @Override
    public void start(Stage stage) throws Exception {
        // 外部スクリプトに start 関数が定義されていた場合は実行する
        try {
            ((Invocable) engine).invokeFunction("start", stage);
        } catch (NoSuchMethodException ex) {
            // start 関数は存在していなくても良い
        }
    }
    @Override
    public void stop() throws Exception {
        // 外部スクリプトに stop 関数が定義されていた場合は実行する
        try {
            ((Invocable) engine).invokeFunction("stop");
        } catch (NoSuchMethodException ex) {
            // stop 関数は存在していなくとも良い
        }
    }
    /**
     * 指定された JavaScript ファイルの読み込みと実行
     * @param path JavaScript ファイルの UTF-8 でエンコードされたパス名
     * @return 最後に評価された関数の返り値(使用されない)
     */
    private Object load(String path) {
        try {
            FileInputStream file = new FileInputStream(path);
            InputStreamReader input = new InputStreamReader(file, "UTF-8");
            return engine.eval(input);
        } catch (FileNotFoundException | UnsupportedEncodingException | ScriptException ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

このプログラムのコンパイルは、まず Nashorn のリポジトリをダウンロードし、(cd make ; ant build-fxshell) を実行します。すると nashorn/dist/nashornfx.jar ファイルが作成されますので、java -cp dist/nashornfx.jar jdk.nashorn.tools.FXShell で実行出来ます。JDK に詳しい方は jdk/makefiles/CompileLaunchers.gmk の jjs エントリーの下に定義を書き加える事で、独自のランチャーを作成する事も可能です。

このシェルプログラム方式の優位点は、JavaFX の実行に必要な処理の殆ど全てをシェルプログラム側が引き受けてくれることです。このシェルプログラムを使ってコードを書く場合は、自分のスクリプトには start メソッドと幾つかのクラスの定義を記述するだけで済みます。このシェルプログラムを JDK に含ませる事が出来れば良いのですが、先ほどの依存関係により JDK に JavaFX に依存したコードを入れることが出来ません。

JavaFX へのアクセスのもう一つの実装方法は、jjs プログラムを利用する方法です。Nashorn リポジトリにある最新の Java.extend では、javafx.application.Application クラスのサブクラスを作成することが出来ます。これを利用して JavaFX にアクセスするプログラムを書くことが出来ます。プログラムの制御が JavaFX 側で管理されること、JavaFX の初期化処理を行う場所に制約があることに注意が必要です。

この実装方法の簡易的なプロトタイプとして fxinit.js を作成しました。見慣れない部分もあるかもしれませんが、中で実装している処理はとても簡単です。

GLOBAL = this;
javafx = Packages.javafx;
com.sun.javafx.application.LauncherImpl.launchApplication(
(Java.extend(javafx.application.Application, {
    init: function() {
        // FX のパッケージとクラスはこれより以前には使用出来ないため、
        // ここで定義する必要がある
        Stage          = javafx.stage.Stage;
        scene          = javafx.scene;
        Scene          = scene.Scene;
        Group          = scene.Group;
        chart          = scene.chart;
        control        = scene.control;
        Button         = control.Button;
        StackPane      = scene.layout.StackPane;
        FXCollections  = javafx.collections.FXCollections;
        ObservableList = javafx.collections.ObservableList;
        Chart          = chart.Chart;
        CategoryAxis   = chart.CategoryAxis;
        NumberAxis     = chart.NumberAxis;
        BarChart       = chart.BarChart;
        XYChart        = chart.XYChart;
        Series         = chart.XYChart$Series;
        Data           = chart.XYChart$Data;
        TreeView       = control.TreeView;
        TreeItem       = control.TreeItem;
        if (GLOBAL.init) {
            init();
        }
    },
    start: function(stage) {
        if (GLOBAL.start) {
            start(stage);
        }
    },
    stop: function() {
        if (GLOBAL.stop) {
            stop();
        }
    }
})).class, new (Java.type("java.lang.String[]"))(0));

このプログラムの使用方法は簡単です。JavaFX の HelloWorld.java サンプル を Nashorn 向けに JavaScript で書き直したプログラムがこちらです。

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();
}
load("fxinit.js");

load("fxinit.js") で fxinit.js を読み込む場所には注意が必要です。fxinit.js を読み込んだ時点でプログラムの制御が JavaFX 側に移ります。それより後ろに書いたコードはアプリケーション終了時に実行されることになってしまいます。

それから、JavaFX クラスの一部は JavaFX の実行が開始されていないと初期化する事が出来ません。その為 JavaScript プログラムのトップレベルでこれらのクラスを使用することは出来ません。必ず JavaFX の実行が開始されてから呼び出されるメソッドの中で使わなくてはいけません。この様に、fxinit.js の様な実装方法は、プログラムの書き方に制約が生じてしまいます。

これら以外に三番目の実装方法も検討しています。コマンドラインが複雑になりますが、jjs を使って jjs fxinit.js -- myscript.js -- my scripts args と実行する様なイメージです。ここでの -- は、引き続いて引数が出現する事を意味しています。この場合は jjs によってまず fxinit.js が読み込まれ、JavaFX が起動してから myscript.js が実行されます。この様にすれば、myscript.js の部分のプログラムはこれまで見てきたような制約が無く記述する事が出来ます。コマンドラインの記述が複雑になることだけが問題です。

皆さまのご意見をお聞かせ下さい。

水曜日 2 13, 2013

Nashorn でシェルスクリプトを書く

はじめに

Nashorn は日常のスクリプティングにも力を発揮します。Nashorn でシェルスクリプトを書く実例が紹介されていましたので、翻訳してみました。ご覧下さい。

おことわり

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

訳文

The Register の『Gnome プロジェクトが唯一のアプリ開発言語に JavaScript を採用』と題された記事を読んでいた時、Nashorn を使ったシェルスクリプティングについて、まだお話をしていない事を思い出しました。

これは JavaScript のユーザにとってもシェルスクリプトのユーザにとっても守備範囲から外れる話かもしれませんが、一考の価値があります。ウェブブラウザ上の JavaScript にとって利便性の高い機能は、シェルスクリプトでも同じように良い機能です。その上、Nashorn は Java のライブラリや JavaFX にもアクセスできます(プログラムに視覚的な効果を追加する事も可能です)。

Nashorn には -scripting オプションが用意されており、JavaScript を拡張して、シェルスクリプトを書き易くする機能を追加する事が出来ます(ファイルの先頭行が #! で始まるスクリプトファイルも作成出来ます)。-scripting オプションを付けた場合に有効になる機能は以下の通りです。

  • $EXEC("コマンド名 引数", "入力文字列") 関数で OS のコマンドを別プロセスとして実行
  • 標準入力の引き渡し
  • 標準出力 ($OUT)
  • 標準エラー($ERR)
  • 終了コード ($EXIT)
  • `コマンド名 引数` 形式でのコマンド実行文字列の簡易指定
  • ダブルクォートで括った文字列とコマンド実行文字列は置換可能で、シングルクォートで括った文字列は置換不可
  • ヒアストリングによる複数行文字列(<<TAG...TAG 形式のヒアストリングは最後に改行が入り、<<<TAG...TAG 形式のヒアストリングは最後の改行が入りません)
  • 環境変数へのアクセス ($ENV)
  • コマンドライン引数へのアクセス ($ARG)
  • exit(code) と quit() によるスクリプトの終了
  • # によるコメント

今後もスクリプト作成の実例をたくさんご用意する予定ですが、今回の記事では手始めとしてスクリプトを一つご紹介します。

ソースコードを書いたプログラマの調査

私たちが Nashorn の開発を行っている中で、意図が掴めないコードに突き当たり、そのコードを書いたプログラマを探し出して話をしたいと思うことがよくあります。blaming (問責)と呼ばれる事もありますが、なるべく苦労することなしにコードを理解するには、書いた人に聞くのが一番です。Nashorn は openjdk.java.net にある Mercurial のリポジトリで管理されています。Mercurial もソースコードの変更毎の注釈を保存する機能を持っていますが、ソースコードの特定の場所を直接指定して調査できると便利です。

次のシェルスクリプト(suspect スクリプト)は、ソースコードのファイル名と調べたい箇所の行番号を与えると、ソースコードの変更履歴からその行に関連する情報を抜き出して表示します。

#!/usr/bin/jjs
# このスクリプトはソースコードのファイル名と行番号からソースコードの
# 変更情報を見つけ出します
#

// コマンドの使用方法を表示
function usage() {
    error(<<EOS);
usage: suspect javaFileName lineNumber
    javaFileName - name of file in local mercurial repository
    lineNumber   - file line number
EOS
}

// 標準エラー出力にメッセージを出力
function error(message) {
    java.lang.System.err.print(message);
    exit(1);
}

// コマンドの引数にわかりやすい名前を与える
var fileName   = $ARG[0];
var lineNumber = $ARG[1];

// 引数が足りない場合は、コマンドの使用方法を表示
if (!fileName || !lineNumber) {
    usage();
}

// ファイル名に .java が欠けていた場合は補完
if (!fileName.endsWith(".java")) {
    fileName += ".java";
}

// 引数で与えたファイル名にマッチするファイルを探し、
// 見つかったパス名を配列に格納
var where = `find . -name ${fileName}`.trim().split("\n");

// ファイルがみつからなかった場合
if (where == "") {
    error("File ${fileName} is not in the current repository.\n");
} else if (where.length != 1) {
    error("File ${fileName} found in multiple locations\n${where.join('\n')}\n");
}

// ファイルの注釈情報を変更履歴番号付きで取得
var annotated = `hg annotate -c ${where}`.split("\n");

// 引数で指定した行番号の行を抜き出す
var line = annotated[lineNumber];

// 行が存在している事を確認
if (!line) {
    error("File ${fileName} does not contain line number ${lineNumber}\n");
}

// 履歴番号を抜き出す
var revision = line.substring(0, 12);

// 履歴番号から履歴情報を取り出して表示
print(`hg log -r ${revision}`);

もし SpillProperty.java ファイルの 63 行目の履歴情報を調べたくなったら、suspect スクリプトを以下の様に実行して下さい。

>> suspect SpillProperty 63
changeset:   2:da1e581c933b
user:        jlaskey
date:        Fri Dec 21 16:36:24 2012 -0400
summary:     8005403: Open-source Nashorn

今回はまだほんの手始めです。今後の更新にもご期待下さい。

木曜日 1 17, 2013

JVM 上に動的言語を実装する

はじめに

JVM 上に動的言語を実装する技術を解説したプレゼンテーション動画の紹介文を翻訳致しました。是非ご覧下さい。

おことわり

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

訳文

java.lang.invoke.* ライブラリや invokedynamic バイトコードは、JVM 上に動的言語を実装する際に役に立つ、強力な新ツールです。昨年の 11 月にアントワープで開催されました Devoxx イベントで、Java7+ を使って動的言語を実装し JVM をその runtime とする方法についてスピーチしてきました。スピーチの内容は特定の実装に依存した話ではありませんが、Nashorn Project についての話題も沢山取り扱っています。

Devoxx の主催者のご厚意で、プレゼンテーションの映像が無料で公開されています。JVM 上の動的言語の実装の最新動向にご興味がありましたら、是非ご覧下さい。

動画へのリンク: Nashorn - JVM 上に動的言語を実装する

土曜日 12 22, 2012

Nashorn のソースコードが公開されました

はじめに

昨日、予告通りに Nashorn のソースコードが公開されました。プロジェクトのウェブページメーリングリストも用意されています。オープンソースになった Nashorn のこれからが楽しみですね。

おことわり

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

訳文

沢山の血と、汗と、涙が流されて、ついに Nashorn が OpenJDK に加わりました。

ソースコードの取得は Mercurial を使って

hg fclone http://hg.openjdk.java.net/nashorn/jdk8 nashorn~jdk8

とするか、単に Nashorn を指定します。

hg clone http://hg.openjdk.java.net/nashorn/jdk8/nashorn nashorn

より詳細な手順と OpenJDK の使い方は、開発者ガイドをご参照ください。

http://openjdk.java.net/guide/index.html

今回のコードリリースはまだ序の口で、これから更に大きな統合作業が待ち構えていますが、ソースコードを読んだり、ビルドして動かして頂く準備は整いました。まずは README と RELEASE_README からご覧下さい。質問や問題点を見つけたら、それらを是非 nashorn-dev@openjdk.java.net に投稿して下さい。冬休みの間は反応が遅くなるかもしれませんが、必ず返信するつもりです。

土曜日 12 08, 2012

OpenJDK が Nashorn プロジェクトを承認

はじめに

Nashorn が OpenJDK のプロジェクトとして承認されました。Nashorn は OpenJDK の一部として、オープンソースで公開されます。以前の話では、公開は冬休み前との事でしたので、ダウンロードが可能になるまで時間は掛からないと思われます。楽しみですね。

おことわり

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

訳文

先ほど、こんなメールが届きました。来週は忙しくなりそうです。

Jim Laskey 氏が初期リーダーを務める Nashorn プロジェクト承認の投票 [1] が
締め切られました。

賛成: 20
反対: 0
棄権: 0

明確な反対が無ければ賛同されたものと看做すという決まりにより、
これでこの新プロジェクトとプロジェクトのリーダーは承認されました。

-John Coomes

[1] http://mail.openjdk.java.net/pipermail/announce/2012-November/000139.html

木曜日 12 06, 2012

Twitter 上の Nashorn への反響(続き)

はじめに

Nashorn と Java を組み合わせて Twitter にアクセスするサンプルの続きです。今回は JavaFX を使用してグラフを表示します。

おことわり

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

訳文

Twitter にアクセスするサンプルが完成しましたので、今度は JavaFX を使用してグラフ化してみましょう。この記事を書いている時点では Nashorn には JavaFX 用のシェルがありませんので、JavaFX アプリケーションを作成するには少し工夫が必要になります。今回ご紹介する方法で、Nashorn と Java を相互に呼び出すプログラムのイメージを掴んで頂けるのではないかと思います(JavaFX 用のシェルは今後の実装予定に加える予定です)。

まずはアプリケーションの中で実質的な処理を行う部分を見て行きましょう。こちらが前回ご紹介した Twitter にアクセスするサンプルを書き直したプログラムです。

var twitter4j      = Packages.twitter4j;
var TwitterFactory = twitter4j.TwitterFactory;
var Query          = twitter4j.Query;

function getTrendingData() {
    var twitter = new TwitterFactory().instance;
    var query   = new Query("nashorn OR nashornjs");
    query.since("2012-11-21");
    query.count = 100;
    var data = {};

    do {
        var result = twitter.search(query);
        var tweets = result.tweets;
        for each (var tweet in tweets) {
            var date = tweet.createdAt;
            var key = (1900 + date.year) + "/" +
                      (1 + date.month) + "/" +
                      date.date;
            data[key] = (data[key] || 0) + 1;
        }
    } while (query = result.nextQuery());

    return data;
}

今回は、ツイートを表示する代わりに、getTrendingData() 関数の中でサンプリング期間中(このプログラムでは、Nashorn のプロジェクトが OpenJDK に申請された 2012 年 11 月 21 日以降)の日別のツイート数を計算し、結果をオブジェクトに格納して返しています。

続いて、JavaFX の BarChart でデータを表示します。

var javafx         = Packages.javafx;
var Stage          = javafx.stage.Stage
var Scene          = javafx.scene.Scene;
var Group          = javafx.scene.Group;
var Chart          = javafx.scene.chart.Chart;
var FXCollections  = javafx.collections.FXCollections;
var ObservableList = javafx.collections.ObservableList;
var CategoryAxis   = javafx.scene.chart.CategoryAxis;
var NumberAxis     = javafx.scene.chart.NumberAxis;
var BarChart       = javafx.scene.chart.BarChart;
var XYChart        = javafx.scene.chart.XYChart;
var Series         = javafx.scene.chart.XYChart.Series;
var Data           = javafx.scene.chart.XYChart.Data;

function graph(stage, data) {
    var root = new Group();
    stage.scene = new Scene(root);
    var dates = Object.keys(data);
    var xAxis = new CategoryAxis();
    xAxis.categories = FXCollections.observableArrayList(dates);
    var yAxis = new NumberAxis("Tweets", 0.0, 200.0, 50.0);
    var series = FXCollections.observableArrayList();
    for (var date in data) {
        series.add(new Data(date, data[date]));
    }
    var tweets = new Series("Tweets", series);
    var barChartData = FXCollections.observableArrayList(tweets);
    var chart = new BarChart(xAxis, yAxis, barChartData, 25.0);
    root.children.add(chart);
}

このサンプルプログラムの中には興味深い点が沢山あります。例えば、stage.scene = new Scene(root) は stage.setScene(new Scene(root)) と同じ処理をより簡潔に記述出来ています。Nashorn が stage オブジェクトに scene プロパティを見つけられなかった場合は、(Dynalink ライブラリを通して)Java Beans の命名規則的に同等となるメソッド (setScene()) を探して来るので、こういう記述が可能になっています。もう一つ、Nashorn は FXCollections 等の総称クラスのハンドリングも自動で行ってくれます。そして、observableArrayList(dates) 呼び出しの部分では、Nashorn が JavaScript の配列 (dates) を Java のコレクションに自動で変換しています。どのオブジェクトが JavaScript のオブジェクトで、どのオブジェクトが Java のオブジェクトであるかを判定するのはとても難しい問題ですが、それをプログラマが明示的に指定しなくても良い様になっています。

プログラムの本質的な処理の部分の説明は以上で終わりましたので、次は JavaFX と連携するための仕組みについて見て行きましょう。

JavaFX のプログラムを作成する際は、javafx.application.Application クラスのサブクラスをメインクラスにします。このクラスが JavaFX ライブラリの初期化とイベント処理を行います。こちらがこのサンプルプログラム用に作成したコードです。

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javafx.application.Application;
import javafx.stage.Stage;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class TrendingMain extends Application {

    private static final ScriptEngineManager
                                        MANAGER = new ScriptEngineManager();
    private final ScriptEngine engine = MANAGER.getEngineByName("nashorn");
    private Trending trending;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        trending = (Trending) load("Trending.js");
        trending.start(stage);
    }

    @Override
    public void stop() throws Exception {
        trending.stop();
    }

    private Object load(String script) throws IOException, ScriptException {
         try (final InputStream is = TrendingMain.class.getResourceAsStream(script)) {
            return engine.eval(new InputStreamReader(is, "utf-8"));
         }
    }
}

ここでは Nashorn の初期化の為に JSR-223 の javax.script を使用しています。

private static final ScriptEngineManager MANAGER = new ScriptEngineManager();
private final ScriptEngine engine = MANAGER.getEngineByName("nashorn");

コードのこの部分は JavaScript を処理するための Nashorn エンジンのインスタンスを作成しています。

load メソッドで外部スクリプトをメモリ上に読み込み、engine でそのスクリプトを評価します。load は評価した結果を返します。

ここからが特に面白い部分です。Java のメインクラスと外部スクリプトの間で相互にデータをやりとりする方法は何通りかありますが、このサンプルでは Java のインターフェイスを使用します。JavaFX のメインクラスは start メソッドと stop メソッドを実行する必要がありますので、この様なインターフェイスを作成します。

public interface Trending {
    public void start(Stage stage) throws Exception;
    public void stop() throws Exception;
}

そして、サンプルスクリプトの最後に、次のコードを追加します。

function newTrending() {
    return new Packages.Trending() {
        start: function(stage) {
            var data = getTrendingData();
            graph(stage, data);
            stage.show();
        },

        stop: function() {
        }
    }

}

newTrending();

このコードは Trending クラスのサブクラスのインスタンスを作成し、start メソッドと stop メソッドをオーバーライドしています。この関数から返されたオブジェクトが eval を通して Java のメインメソッドに返されます。

trending = (Trending) load("Trending.js");

全体の動きを簡単にまとめますと、Trending.js スクリプトには getTrendingData 関数の定義と、一番最後に newTrending 関数の呼び出しが実装されています。そこから Java のコードに戻って、newTrending 関数を評価している eval メソッドの返り値を Trending クラスにキャストしています。そして、その返ってきたオブジェクトを使用して、次のコードでスクリプトの実行を行います。

trending.start(stage);

これで完成です。

twitterverse

訳者補足

Nashorn のリンカーの動きは http://www.myexpospace.com/JavaOne2012/SessionFiles/CON5251_PDF_5251_0001.pdf をご参照下さい。 Dynalink に付きましては https://github.com/szegedi/dynalink をご参照下さい。

火曜日 12 04, 2012

Twitter 上の Nashorn への反響

はじめに

Nashorn を使用して Twitter にアクセスするサンプルがありましたので翻訳してみました。

おことわり

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

訳文

Nashorn がインターネット上でどれだけ話題になっているのかを追ってみました。Project Nashorn をアナウンスしてから、Nashorn についての沢山のツイートがありましたので、Nashorn の日毎のトレンドがどうなっているのか調べてみたくなりました。出来れば結果をグラフ化出来ると面白そうです。その為に、ツイート数をひとつひとつ数えるのは賢いやり方とは言えません。集計用のプログラムを作って数えてみましょう。

実は、これこそ Nashorn + Java の組み合わせが得意としている分野です。Java には Twitter4J https://github.com/yusuke/twitter4j という素晴らしいライブラリがあり、Twitter に関することは全てお任せ出来ます。bin/getAccessToken.sh を実行し、twitter4j.properties に認証情報を記録したら、後は簡単な調査用アプリを実行するだけです。

nashorn -cp $TWITTER4J/twitter4j-core-3.0.1.jar GetHomeTimeline.js

GetHomeTimeline.js の中身はこうです。

var twitter4j      = Packages.twitter4j;
var TwitterFactory = twitter4j.TwitterFactory;
var Query          = twitter4j.Query;

var twitter = new TwitterFactory().instance;
var query   = new Query("nashorn OR nashornjs");
query.count = 100;

do {
    var result = twitter.search(query);
    var tweets = result.tweets;

    for each (tweet in tweets) {
        print("@" + tweet.user.screenName + "\t" + tweet.text);
    }
} while (query = result.nextQuery());

とても簡単ですね。さて、次は JavaFX のグラフ化ライブラリと連携させてみましょう...

火曜日 11 27, 2012

Project Nashorn 申請 (オープンソース)

はじめに

Nashorn の OpenJDK への申請がアナウンスされていましたので、翻訳してみました。

おことわり

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

訳文

先週の木曜日(2012 年 11 月 22 日)、OpenJDK のコミュニティに Project Nashorn が申請されました。 http://mail.openjdk.java.net/pipermail/announce/2012-November/000139.html Project 承認の投票期限は 2012 年 12 月 6 日、UTC の深夜です。うまく行けば、OpenJDK に Nashorn が入り、この冬休みにはお試し頂ける事になると期待しています。幸運に恵まれますように。

月曜日 10 29, 2012

Nashorn Blog へようこそ

はじめに

JavaOne での Nashorn のセッションが紹介されていましたので、翻訳してみました。

おことわり

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

訳文

皆さんようこそ。沈黙を破り、Nashorn ブログを開始する時が来ました。このブログは定期的に更新して行きたいと思っておりますが、ちょうど次の開発マイルストーンに向けてとても忙しくしており、もちろん Nashorn をオープンソースとして公開する為の準備もあります。ですので、万が一、更新が滞る事がありましたらご容赦下さい。

我々は丁度 JavaOne から戻ったばかりで、Nashorn のセッションへの好意的な反応にとても感激しています。キーノートの Georges Saab のスライドの中で重要な位置を占められた事は我々のチームにとって素晴らしいことでした。色々な方からの支持を頂けていると感じています。

JavaOne の殆どのセッションは動画が用意されています。リンク先をご覧下さい。

『Nashorn: JVM 上での JavaScript と動的言語の実行の最適化』 不幸にも、マーカス(コード生成の神様)は初日の最初のセッションを仰せつかりました。それでも、なかなかの出席者数に恵まれました。話の内容は、JVM から良い性能を引き出すために、我々が施した最適化についてでした。まだまだやるべきことは沢山残っていますが、良い感触を得ています。

『Nashorn: JVM 上の JavaScript』 これが Nashorn に関するメインのセッションです。このセッションでは私がオーバービューやオープンソース化についての補足、Nashorn の概要の紹介、デモなど、様々な内容について話しました。250 席くらいの部屋が満席でした。一番素晴らしかったのは、Twitter の Sam Pullara による Mustache.js が如何に快適に(Rhino の 20 倍の速度で)実行できるかの説明と、NetBeans の John Ceccarelli による Nashorn が Netbeans でどんなに不可欠な位置を占めているかの説明です。最後には有益な Q & A もあり、とても励みになりました。

『Nashorn JavaScript Team 懇談会』 Michel と Attila と Marcus と私で Q & A を開催しました。出席者は数名(他の良いセッションとかち合ってしまった為だと思います)でした。殆どの質問は Node.jar についてでしたので、Nashorn + Node.jar の組み合わせが最も注目されているという印象を受けました。ミスター Node.jar の Akhil が参加者からの質問に受け答えしました。

『Nashorn と Node と Java Persistence』 Doug Clarke と Akhil と私で表題の件について討論を行い、(警備員によって解散 させられるまで)長時間に渡る Q & A を行いました。出席者は 80 名ほどでした。 多くの質問は Node.jar についてでした。Doug の Nashorn + JPA の使い方の話は とても素晴らしかったです。Nashorn が優雅に上品に活用されていました。

『メタオブジェクトプロトコルの適用 : Nashorn の Java バインディング』 Attila が、どのように Dynalink を Nashorn に適用したかについて話しました。このセッションもなかなかの盛況でした。ここで扱われた技術が一般に行き渡れば、JVM 上の全てのプログラミング言語で素晴らしいことが起きそうな予感がしました。

最後に、JavaOne では Java 以外の言語や、それらが JVM に与えている影響についてのセッションが沢山ありました。私はいつも、誰もが複数のプログラミング言語を使い分けるべきだと思っています。問題の領域やタスクに応じて使い分けるだけでなく、問題解決への考え方やアプローチを向上させる事にも役立ちます。

概ね、今後の記事は Nashorn に関する how to について書いていこうと思っています。もし何か書いてほしい題材がありましたら、お知らせ下さい。

それでは、ご機嫌よう!

About

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

Search

Archives
« 4月 2014
  
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
   
       
Today