Friday Dec 21, 2012

Open For Business

After a lot of blood, sweat and tears, we finally got Nashorn into OpenJDK.

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

or for just Nashorn

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

 More details about the tools you'll need and using OpenJDK are at 

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

This is just the first drop and needs some larger integration work, but you can browse the source, build and play.  Be sure to read the README and RELEASE_README,  If you have any questions or issues please send them along to nashorn-dev@openjdk.java.net .  Responses may be a little slow over the holidays, but we will respond.


Friday Dec 07, 2012

The Vote Is In

Just got the following e-mail.  Gonna be busy next week.

Voting on the Nashorn Project with initial Lead Jim Laskey [1] is

closed.

Yes:  20
Veto:  0
Abstain:  0

According to the Bylaws definition of Lazy Consensus, this is
sufficient to approve the new Project and its initial Lead.

-John Coomes

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

Saturday Dec 01, 2012

Nashorn in the Twitterverse, Continued

After doing the Twitter example, it seemed reasonable to try graphing the result with JavaFX.  At this time the Nashorn project doesn't have a JavaFX shell, so we have to go through some hoops to create an JavaFX application.  I thought showing you some of those hoops might give you some idea about what you can do mixing Nashorn and Java (we'll add a JavaFX shell to the todo list.)

First, let's look at the meat of the application.  Here is the repackaged version of the original twitter example.

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;
}

Instead of just printing out tweets, getTrendingData tallies "tweets per date" during the sample period (since "2012-11-21", the date "New Project: Nashorn" was posted.)   getTrendingData then returns the resulting tally object.

Next, use JavaFX BarChart to display that data.

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);
}

I should point out that there is a lot of subtlety in this sample.  For example;
stage.scene = new Scene(root) is equivalent to stage.setScene(new Scene(root)). If Nashorn can't find a property (scene), then it searches (via Dynalink) for the Java Beans equivalent (setScene.)  Also note, that Nashorn is magically handling the generic class FXCollections.  Finally,  with the call to observableArrayList(dates), Nashorn is automatically converting the JavaScript array dates to a Java collection.  It really is hard to identify which objects are JavaScript and which are Java.  Does it really matter?

Okay, with the meat out of the way, let's talk about the hoops.

When working with JavaFX, you start with a main subclass of javafx.application.Application.  This class handles the initialization of the JavaFX libraries and the event processing.  This is what I used for this example;

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"));
         }
    }
}
To initialize Nashorn, we use JSR-223's javax.script. 

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

This code sets up an instance of the Nashorn engine for evaluating scripts.

The  load method reads a script into memory and then gets engine to eval that script.  Note, that load also returns the result of the eval.

Now for the fun part.  There are several different approaches we could use to communicate between the Java main and the script.  In this example we'll use a Java interface.  The JavaFX main needs to do at least start and stop, so the following will suffice as an interface;

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

At the end of the example's script we add;

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

        stop: function() {
        }
    }

}

newTrending();

which instantiates a new subclass instance of Trending and overrides the start and stop methods.  The result of this function call is what is returned to main via the eval.

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

To recap, the script Trending.js contains functions getTrendingData, graph and newTrending, plus the call at the end to newTrending.  Back in the Java code, we cast the result of the eval (call to newTrending) to Trending, thus, we end up with an object that we can then use to call back into the script. 

trending.start(stage);

Voila.
twitterverse











About

Technical discussions and status of the Nashorn JavaScript Project.

Search

Categories
Archives
« December 2012 »
SunMonTueWedThuFriSat
      
2
3
4
5
6
8
9
10
11
12
13
14
15
16
17
18
19
20
22
23
24
25
26
27
28
29
30
31
     
Today