Monday Mar 17, 2014

Latest news from the Nashorn team

Things have been busy on the Nashorn front, getting ready for the JDK 8 rollout and all.  March 18 is the magic day (EclipseCon.)

 I see that JetBrains has added Nashorn debugging to IntelliJ. This rounds out the debugging story with support in NetBeans and Eclipse.

 The Nashorn team has been working hard on performance improvements for the next round.  The first set involves caching of compiled scripts.  This will make a huge difference for reoccurring scripts (think servers.)  The second set of changes involve optimistic typing, where code generated for functions assumes optimal data types in expressions (ex. integers) and falls back to broader types (ex. double) if it doesn't work out.  This provides a huge performance win, since most of the time the assumptions prove correct.  These fixes are in staging repos moving to the JDK9-dev and JDK8u20-dev repos in the next few weeks.  Both these changes should be in the JDK 8u20 update targeted for August.

Finally, on the Node.js front, Node has found a permanent home in the Java EE world.  The project has been renamed to Avatar.js to tie in with the large Avatar project and is taking full advantage of multithreading.

Thursday Sep 26, 2013

setInterval and setTimeout JavaScript Functions

With people porting HTML to Nashorn + JavaFX more frequently, I often get asked the question "how do you port functions like setInterval and setTimeout" (setInterval is used to have a function repeated at regular intervals, where setTimeout is used to have a function delayed.) The following code duplicates the functionality of the each of the setInterval family of functions, when used in a JavaFX application.

var Platform = Java.type("javafx.application.Platform");
var Timer    = Java.type("java.util.Timer");

function setInterval(func, milliseconds) {
	// New timer, run as daemon so the application can quit
	var timer = new Timer("setInterval", true);
	timer.schedule(function() Platform.runLater(func), milliseconds, milliseconds);	
	return timer;
}

function clearInterval(timer) {
	timer.cancel();
}

function setTimeout(func, milliseconds) {
	// New timer, run as daemon so the application can quit
	var timer = new Timer("setTimeout", true);
	timer.schedule(function() Platform.runLater(func), milliseconds);	
	return timer;
}

function clearTimeout(timer) {
	timer.cancel();
}

<Update March 21, 2014>

I was requested by a reader to implement setInterval, setTimeout and setImmediate with the same arguments as defined in DOM.

var Platform = Java.type("javafx.application.Platform");
var Timer    = Java.type("java.util.Timer");

function setTimerRequest(handler, delay, interval, args) {
    handler = handler || function() {};
    delay = delay || 0;
    interval = interval || 0;

    var applyHandler = function() handler.apply(this, args);
    var runLater = function() Platform.runLater(applyHandler);

    var timer = new Timer("setTimerRequest", true);

    if (interval > 0) {
        timer.schedule(runLater, delay, interval);
    } else {
        timer.schedule(runLater, delay);
    }

    return timer;
}

function clearTimerRequest(timer) {
    timer.cancel();
}

function setInterval() {
    var args = Array.prototype.slice.call(arguments);
    var handler = args.shift();
    var ms = args.shift();

    return setTimerRequest(handler, ms, ms, args);
}

function clearInterval(timer) {
    clearTimerRequest(timer);
}

function setTimeout() {
    var args = Array.prototype.slice.call(arguments);
    var handler = args.shift();
    var ms = args.shift();

    return setTimerRequest(handler, ms, 0, args);
}

function clearTimeout(timer) {
    clearTimerRequest(timer);
}

function setImmediate() {
    var args = Array.prototype.slice.call(arguments);
    var handler = args.shift();

    return setTimerRequest(handler, 0, 0, args);
}

function clearImmediate(timer) {
    clearTimerRequest(timer);
}

Monday Sep 09, 2013

Nashorn + FX 350fps vs. Chrome 120fps

I was reading an article written by Felix Bembrick where he took a Chrome canvas example and ported it to Nashorn with JavaFX.  I ran the two tests myself and was surprised to see that Nashorn + FX ran at 350fps where Chrome ran at 120fps (iMac, 3.4 GHz Intel Core i7, AMD Radeon HD 6970M 2048M.)  Much of the credit for that is JavaFX's rendering engine, but Nashorn allowed Felix to leverage that.  The original article is at JavaFX with Nashorn Canvas example

Wednesday Sep 04, 2013

JavaOne: JavaScript Sessions

These are the sessions I found related to JavaScript.  There are likely others.  If you spot others, send a comment and I'll update this entry.  The session on the list is mine and the second is Attila's. Teaching the Java Platform with Nashorn is done with Ben Evans of the London JUG.  Come to the Script Bowl and vote for the only first time entry (Nashorn.)

Nashorn: JavaScript on the JVM [CON7835]

  • Monday, Sep 23, 11:30 AM - 12:30 PM - Hilton - Yosemite B/C


Nashorn: Java and JavaScript—Shaken, Not Stirred [BOF5793]

  • Monday, Sep 23, 6:30 PM - 7:15 PM - Hilton - Yosemite B/C


Teaching the Java Platform with Nashorn [BOF5225]

  • Tuesday, Sep 24, 4:30 PM - 5:15 PM - Hilton - Yosemite B/C


The Curious Case of JavaScript on the JVM [CON2952]

  • Tuesday, Sep 24, 1:00 PM - 2:00 PM - Hilton - Yosemite B/C


Embedding JVM Scripting Languages [CON2585]

  • Tuesday, Sep 24, 3:00 PM - 4:00 PM - Hilton - Yosemite B/C


Modular JavaScript [CON2959]

  • Wednesday, Sep 25, 3:00 PM - 4:00 PM - Hilton - Yosemite B/C


MySQL User-Defined Functions....in JavaScript! [CON1738]

  • Saturday, Sep 21, 1:00 PM - 2:00 PM - Hilton - Powell


Effective Use of HTML5 JavaScript for Java EE 7 Web Applications [BOF7838]

  • Monday, Sep 23, 4:30 PM - 5:15 PM - Parc 55 - Cyril Magnin II/III


Script Bowl 2013: The Battle for Supremacy and Survival [CON2653]

  • Wednesday, Sep 25, 4:30 PM - 5:30 PM - Hilton - Imperial Ballroom B


Developing JavaScript Applications for Node.js with MySQL and NoSQL [CON6805]

  • Sunday, Sep 22, 4:00 PM - 5:00 PM - Hilton - Powell

JavaOne: What a Difference a Year Makes

I haven't been blogging for a while.  JavaOne examples and slides are taking up all my time.  Marcus and Attila leaked out that I was doing a demo of the Nashorn Debugger prototype, but I still have some surprises up my sleeve.  I was reviewing examples from last year and felt that they needed some updating.  The FX Fireworks example had all kinds of Java code to support applications, timers and callbacks.  This year's Fireworks version is pure Nashorn.  I posted source and backdrop here.  Just run with jjs -fx -scripting fireworks.js  Enjoy.

var ArrayList      = Java.type("java.util.ArrayList");
var File           = Java.type("java.io.File");

var AnimationTimer = Java.type("javafx.animation.AnimationTimer");
var BlendMode      = Java.type("javafx.scene.effect.BlendMode");
var Canvas         = Java.type("javafx.scene.canvas.Canvas");
var Color          = Java.type("javafx.scene.paint.Color");
var CycleMethod    = Java.type("javafx.scene.paint.CycleMethod");
var Group          = Java.type("javafx.scene.Group");
var ImageView      = Java.type("javafx.scene.image.ImageView");
var Pane           = Java.type("javafx.scene.layout.Pane");
var RadialGradient = Java.type("javafx.scene.paint.RadialGradient");
var Reflection     = Java.type("javafx.scene.effect.Reflection");
var Scene          = Java.type("javafx.scene.Scene");
var Stop           = Java.type("javafx.scene.paint.Stop");

var AnimationTimerExtend = Java.extend(AnimationTimer);

var doubleArray    = Java.type("double[]");

var GRAVITY = 0.06;

function Particle(posX, posY, velX, velY, targetX, targetY, color, size, usePhysics, shouldExplodeChildren, hasTail) {
    this.posX                  = posX;
    this.posY                  = posY;
    this.velX                  = velX;
    this.velY                  = velY;
    this.targetX               = targetX;
    this.targetY               = targetY;
    this.color                 = color;
    this.size                  = size;
    this.usePhysics            = usePhysics;
    this.shouldExplodeChildren = shouldExplodeChildren;
    this.hasTail               = hasTail;
    this.alpha                 = 1;
    this.easing                = Math.random() * 0.02;
    this.fade                  = Math.random() * 0.1;
}

Particle.prototype.update = function() {
    this.lastPosX = this.posX;
    this.lastPosY = this.posY;

    if(this.usePhysics) { // on way down
        this.velY += GRAVITY;
        this.posY += this.velY;
        this.alpha -= this.fade; // fade out particle
    } else { // on way up
        var distance = (this.targetY - this.posY);
        // ease the position
        this.posY += distance * (0.03 + this.easing);
        // cap to 1
        this.alpha = Math.min(distance * distance * 0.00005, 1);
    }

    this.posX += this.velX;

    return this.alpha < 0.005;
}


Particle.prototype.draw = function(context) {
    var x    = Math.round(this.posX);
    var y    = Math.round(this.posY);
    var xVel = (x - this.lastPosX) * -5;
    var yVel = (y - this.lastPosY) * -5;

    // set the opacity for all drawing of this particle
    context.globalAlpha = Math.random() * this.alpha;
    // draw particle
    context.fill = this.color;
    context.fillOval(x - this.size, y - this.size, this.size + this.size, this.size + this.size);

    // draw the arrow triangle from where we were to where we are now
    if (this.hasTail) {
        context.fill = Color.rgb(255, 255, 255, 0.3);
        var x = new doubleArray(3);
        var y = new doubleArray(3);
        x[0] = this.posX + 1.5;  y[0] = this.posY;
        x[1] = this.posX + xVel; y[1] = this.posY + yVel;
        x[2] = this.posX - 1.5;  y[2] = this.posY;
        context.fillPolygon(x, y, 3);
    }
}

function drawFireworks(gc) {
    var iter = particles.iterator();
    var newParticles = new ArrayList();

    while(iter.hasNext()) {
        var firework = iter.next();

        // if the update returns true then particle has expired
        if(firework.update()) {
            // remove particle from those drawn
            iter.remove();

            // check if it should be exploded
            if(firework.shouldExplodeChildren) {
                if(firework.size == 9) {
                    explodeCircle(firework, newParticles);
                } else if(firework.size == 8) {
                    explodeSmallCircle(firework, newParticles);
                }
            }
        }

        firework.draw(gc);
    }

    particles.addAll(newParticles);
}

function fireParticle() {
    particles.add(new Particle(
        surface.width * 0.5, surface.height + 10,
        Math.random() * 5 - 2.5, 0,
        0, 150 + Math.random() * 100,
        colors[0], 9,
        false, true, true));
}

function explodeCircle(firework, newParticles) {
    var count = 20 + (60 * Math.random());
    var shouldExplodeChildren = Math.random() > 0.5;
    var angle = (Math.PI * 2) / count;
    var color = (Math.random() * (colors.length - 1));

    for(var i=count; i>0; i--) {
        var randomVelocity = 4 + Math.random() * 4;
        var particleAngle = i * angle;

        newParticles.add(
            new Particle(
                firework.posX, firework.posY,
                Math.cos(particleAngle) * randomVelocity, Math.sin(particleAngle) * randomVelocity,
                0, 0,
                colors[Math.ceil(color)],
                8,
                true, shouldExplodeChildren, true));
    }
}

function explodeSmallCircle(firework, newParticles) {
    var angle = (Math.PI * 2) / 12;

    for(var count = 12; count > 0; count--) {
        var randomVelocity = 2 + Math.random() * 2;
        var particleAngle = count * angle;
        newParticles.add(
            new Particle(
                firework.posX, firework.posY,
                Math.cos(particleAngle) * randomVelocity, Math.sin(particleAngle) * randomVelocity,
                0, 0,
                firework.color,
                4,
                true, false, false));
    }
}

function fileToURL(file) {
    return new File(file).toURI().toURL().toExternalForm();
}

var timer = new AnimationTimerExtend() {
    handle: function handle(now) {
        var gc = surface.graphicsContext2D;
        // clear area with transparent black
        gc.fill = Color.rgb(0, 0, 0, 0.2);
        gc.fillRect(0, 0, 1024, 708);
        // draw fireworks
        drawFireworks(gc);

        // countdown to launching the next firework
        if (countDownTillNextFirework <= 0) {
            countDownTillNextFirework = 10 + (Math.random() * 30);
            fireParticle();
        }

        countDownTillNextFirework--;
    }
};

// Kill timer before exiting.
function stop() {
    timer.stop();
}

var particles = new ArrayList();
var countDownTillNextFirework = 40;

// create a color palette of 180 colors
var colors = new Array(181);
var stops = new ArrayList();
stops.add(new Stop(0, Color.WHITE));
stops.add(new Stop(0.2, Color.hsb(59, 0.38, 1)));
stops.add(new Stop(0.6, Color.hsb(59, 0.38, 1,0.1)));
stops.add(new Stop(1, Color.hsb(59, 0.38, 1,0)));
colors[0] = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, stops);

for (var h = 0; h < 360; h += 2) {
    var stops2 = new ArrayList();
    stops2.add(new Stop(0, Color.WHITE));
    stops2.add(new Stop(0.2, Color.hsb(h, 1, 1)));
    stops2.add(new Stop(0.6, Color.hsb(h, 1, 1,0.1)));
    stops2.add(new Stop(1, Color.hsb(h, 1, 1,0)));
    colors[1 + (h / 2)] = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, stops2);
}

var surface = new Canvas(1024, 500);
surface.blendMode = BlendMode.ADD;
surface.effect = new Reflection(0, 0.4, 0.15, 0);

var imageUrl = fileToURL("sf.jpg");
var background = new ImageView(imageUrl);

var pane = new Pane();
pane.children.add(background);
pane.children.add(surface);

var root = new Group();
root.children.add(pane);
$STAGE.scene = new Scene(root);

timer.start();


Thursday Aug 08, 2013

Repost: A simple Real-time Web App built with Nashorn Scripting

Peter Hausel posted a new blog entry on Nashorn.  Cool example.  A simple Real-time Web App built with Nashorn Scripting

Tuesday Jul 09, 2013

Nashorn Multithreading and MT-safety

Tobias Schlotte asked the question (via twitter @tobsch), "Where is thread-safety on your agenda for Nashorn?"  The quick answer is that multithreading is very high on the list, but MT-safety is a very complicated issue. I'll attempt to explain our plan of action.

As Attila responded http://mail.openjdk.java.net/pipermail/nashorn-dev/2013-July/001567.html "ECMAScript 5.1 language specification doesn't define multithreading semantics for programs written in the language; they are inherently single threaded. If we were to make them thread safe, we'd be sacrificing single threaded performance for a behaviour that falls outside of the specification."

Why is performance affected?  Well, generally, the internal structure of a script object is complex.  Any modification to the object would have to be done with mutex locks (synchronize) or compare/swap.  As an example, take obj.x = 1.  In a static language, this code would likely generate a simple native store that can be executed, somewhat, atomically.  With multiple threads, obj.x = ? would simply be "last thread that stores, wins" (ignoring coherency et al) and the application would continue to run.  In dynamic languages like JavaScript, what happens when property "x" doesn't exist yet?  Internally, the script object has to be restructured to include the new property before the store takes place.  Now add the fact that another thread could be adding or removing properties on the same object; objects would have to be locked for (nearly) every operation. Then add in the thread coherency issue…  Slowness, reigns.

On the other hand, having a language on the JVM and not supporting multithreading is just plain daft.  Especially, since our target platforms include multi-core servers.

So, our agenda is two fold.  The first is to provide a "workers" library (timeline is not tied to JDK8) which uses an onevent model that JavaScripters are familiar with.  No synchronization/locking constructs to be added to the language.  Communication between threads (and potentially nodes/servers) is done using JSON (under the covers.) Going this route allows object isolation between threads, but also allows maximal use of CPU capacity.

The second part is, we will not guarantee MT-safe structures, but we have to face reality.  Developers will naturally be drawn to using threads.  Many of the Java APIs require use of threads.  So, the best we can do is provide guidelines on how to not shoot yourself in the foot.  These guidelines will evolve and we'll post them 'somewhere' after we think them through.  In the meantime,  I follow some basic rules;
  • Avoid sharing script objects or script arrays across threads (this includes global.)  Sharing script objects is asking for trouble.  Share only primitive data types, or Java objects.
  • If you want to pass objects or arrays across threads, use JSON.stringify(obj) and JSON.parse(string) to transport using strings.
  • If you really really feel you have to pass a script object, treat the object as a constant and only pass the object to new threads (coherency.)  Consider using Object.freeze(obj).
  • If you really really really feel you have to share a script object, make sure the object's properties are stabilized.  No adding or removing properties after sharing starts.  Consider using Object.seal(obj).
  • Given enough time, any other use of a shared script object will eventually cause your app to fail.
The number of reallys in a rule reflects the uncertainty of the outcome.

Note in the first rule, I stated that you shouldn't share global.  The reason is simple, almost everything adds properties to global.  How do you avoid share modifying global?  Simply give each thread a new global.

Nashorn has two builtin functions for starting scripts; load and loadWithNewGlobal.  Each takes an arg specifying which script to load and evaluate; either an URL/file string or an object with a name and script properties (name is for source location, script is body of script.)  Additional arguments are passed as the arguments global when evaluating.  Unlike load, loadWithNewGlobal creates a fresh global before loading the script.  This isolates evaluation from the current global and thus suppresses sharing.

Going back to Tobias' example (the original is at https://gist.github.com/tobsch/5955518);

import javax.script.CompiledScript;
import javax.script.Compilable;
import javax.script.ScriptException;
 
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.NashornScriptEngine;
 
import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Callable;
 
import java.util.ArrayList;
 
public class SSCCE {
  private static final NashornScriptEngineFactory engineFactory = new NashornScriptEngineFactory();
   
  public static void main(String[] args) throws ScriptException, InterruptedException, ExecutionException {
    Compilable engine = (Compilable) engineFactory.getScriptEngine();
     
    String script = new StringBuilder("i = 0;")
      .append("i += 1;")
      .append("shortly_later = new Date()/1000 + Math.random;") // 0..1 sec later
      .append("while( (new Date()/1000) < shortly_later) { Math.random() };") //prevent optimizations
      .append("i += 1;")
      .toString();
     
    final CompiledScript onePlusOne = engine.compile(script);
     
    Callable<Double> addition = new Callable<Double>() {
      @Override
      public Double call() {
        try {
          return (Double) onePlusOne.eval();
        }
        catch(ScriptException e) {
          throw new RuntimeException(e);
        }
      }
    };
     
    ExecutorService executor = Executors.newCachedThreadPool();
    ArrayList<Future<Double>> results = new ArrayList<Future<Double>>();
     
    for(int i = 0; i < 50; i++) {
      results.add(executor.submit(addition));
    }
     
    int miscalculations = 0;
    for(Future<Double> result : results) {
      int jsResult = result.get().intValue();
       
      if(jsResult != 2) {
        System.out.println("Incorrect result from js, expected 1 + 1 = 2, but got " + jsResult);
        miscalculations += 1;
      }
    }
     
    executor.awaitTermination(1, TimeUnit.SECONDS);
    executor.shutdownNow();
     
    System.out.println("Overall: " + miscalculations + " wrong values for 1 + 1.");
  }
}

Output;

Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 10
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 7
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 7
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 12
Incorrect result from js, expected 1 + 1 = 2, but got 8
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 8
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 7
Overall: 41 wrong values for 1 + 1.

I rewrote in JavaScript and introduced loadWithNewGlobal;

#!/usr/bin/env jjs -scripting

var Executors = java.util.concurrent.Executors;
var TimeUnit  = java.util.concurrent.TimeUnit;
var ArrayList = java.util.ArrayList;

var script = <<EOD
    i = 0;
    i += 1;
    shortly_later = new Date()/1000 + Math.random;
    while( (new Date()/1000) < shortly_later) { Math.random() };
    i += 1;
EOD

function addition() {
    return loadWithNewGlobal({ name: "addition", script: script });
}

var executor = Executors.newCachedThreadPool();
var results = new ArrayList();

for(var i = 0; i < 50; i++) {
    // Clarify Runnable versus Callable
    results.add(executor["submit(java.util.concurrent.Callable)"](addition));
}

var miscalculations = 0;
for each (var result in results) {
    var jsResult = result.get().intValue();
    if (jsResult != 2) {
        print("Incorrect result from js, expected 1 + 1 = 2, but got " + jsResult);
        miscalculations += 1;
    }
}

executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdownNow();
print("Overall: " + miscalculations + " wrong values for 1 + 1.");

Works as expected;

Overall: 0 wrong values for 1 + 1.

Wednesday Jun 19, 2013

Nashorn and Lambda, What the Hey!

Yesterday, Brian Goetz (Lambda architect) suggested that I produce an example of Lambda being used from Nashorn.  Since, I've been heads down in Nashorn, I really haven't played with Lambda that much.  After looking at some examples from Stuart Marks,  I figured, what the hey, it doesn't look that hard.  Details of the Lambda APIs are available at JDK 8 b92 API .

Many constructs are familiar to JavaScript developers.  The main things to note;
  • where you can use a Lambda you can use a JavaScript function
  • JavaScript arrays need to be converted to Java collections
  • JavaScript syntax requires the '.' end a phrase, not start one (forces continuation)
Other than that, the JavaScript source and Java source looks very much the same.

#!/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;

// Break the copyright into tokens.
var tokens = copyright.split(/\s+/);

// Convert to ArrayList.
var list = new java.util.ArrayList();
tokens.map(function(e) list.add(e));

// The JavaScript collection for the result.
var result = [];

// Parallelize some of the activity.
list.parallelStream().
    // Select only words.
    filter(function(t) t.match(/^[A-Za-z]+$/)).
    // Make case comparable.
    map(function(t) t.toLowerCase()).
    // Fold duplicates.
    collect(Collectors.groupingBy(function(t) t)).
    // Move results to JavaScript collection.
    forEach(function(t) result.push(t));

// Sort the result.
result.sort();

print(result);
The 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

Wednesday May 29, 2013

Repost: Taming the Nashorn (first impressions)...

[This is a repost of an article written by our friend @hansolo_ with his permission.  The original article is at  Taming the Nashorn (first impressions)...]

Hi there,

JavaScript is everywhere these days and so it is also part of JDK8 dev. prev. with Project Nashorn which is a lightweight high-performance JavaScript runtime engine.

Because I never used Rhino (which is in principle the same just older and slower) I had no  idea how to use it and for what reason.

After I met Marcus Lagergren (@lagergren) at JAX in Mainz (Germany), Geecon in Krakow (Poland) and at Java Forum in Malmö (Sweden) I decided that I have to take a look at the Nashorn to get an idea on how to use it. Of course I'm especially interested on how to use Nashorn in combination with JavaFX. Lucky me that Nashorn is part of the weekly developer previews of JDK8 since a few weeks ago and because Jim Laskey (@wickund) blogged about this use case I was able to start with it yesterday.

After some starting problems I slowly begin to understand and would like to share it with you...

First of all I would like to see how I could use Nashorn with JavaScript in combination with my controls, so the idea is to visualize a Lcd control of my Enzo library with Nashorn.

For the following example you'll need the jdk8 developer preview (grab it here) and a current version of my Enzo library (grab it here).

And with a shell and a text editor you are good to go...really nice :)


So first of all we need to create a JavaScript file that should create a JavaFX application which should show the Lcd control and set's the value of the Lcd control every 3 seconds to a random value between 0 and 100.


This is the JavaFX code to realize that...


import eu.hansolo.enzo.lcd.Lcd;
import eu.hansolo.enzo.lcd.LcdBuilder;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.Random;
public class EnzoFX extends Application {
  private Lcd            lcd;
  private Random         random;
  private long           lastTimerCall;
  private double         charge;
  private AnimationTimer timer;
  @Override public void init() {
    // Initialize the AnimationTimer
    random        = new Random();
    lastTimerCall = System.nanoTime();
    charge        = 0;
    timer         = new AnimationTimer() {
      @Override public void handle(long now) {
        if (now > lastTimerCall + 3_000_000_000l) {
          lcd.setValue(random.nextDouble() * 100);
          lcd.setTrend(Lcd.Trend.values()[random.nextInt(5)]);
          charge += 0.02;
          if (charge > 1.0) charge = 0.0;
          lcd.setBatteryCharge(charge);
          lastTimerCall = now;
          System.out.println(lcd.getValue());
        }
      }
    };
    // Initialize the Enzo Lcd control
    lcd = LcdBuilder.create()
                    .styleClass(Lcd.STYLE_CLASS_STANDARD_GREEN)
                    .title("Room Temp")
                    .unit("°C")
                    .decimals(2)
                    .minMeasuredValueDecimals(2)
                    .maxMeasuredValueDecimals(2)
                    .unitVisible(true)
                    .batteryVisible(true)
                    .alarmVisible(true)
                    .minMeasuredValueVisible(true)
                    .maxMeasuredValueVisible(true)
                    .lowerRightTextVisible(true)
                    .formerValueVisible(true)
                    .trendVisible(true)
                    .lowerRightText("Info")
                    .valueAnimationEnabled(true)
                    .foregroundShadowVisible(true)
                    .crystalOverlayVisible(true)
                    .build();
  }
  @Override public void start(Stage stage) {
    // Prepare stage and add controls
    StackPane root = new StackPane();
    root.setPadding(new Insets(10, 10, 10, 10));
    root.getChildren().add(lcd);
    stage.setTitle("Enzo in JavaFX");
    stage.setScene(new Scene(root, 528, 192));
    stage.show();
    // Start the timer
    timer.start();
  }
  public static void main(String[] args) {
    launch(args);
  }
}

Ok, so now we know how to achieve this in JavaFX but now let's take a look at the JavaScript code that leads to the same result. Here you go...


var System         = java.lang.System;
var Random         = java.util.Random;
var StackPane      = javafx.scene.layout.StackPane;
var Scene          = javafx.scene.Scene;
var Insets         = javafx.geometry.Insets;
var AnimationTimer = javafx.animation.AnimationTimer;
var Lcd            = Packages.eu.hansolo.enzo.lcd.Lcd;
// Initialize the AnimationTimer
var random        = new Random();
var lastTimerCall = System.nanoTime();
var charge        = 0;
var timer         = new AnimationTimer() {
    handle: function(now) {
        if (now > lastTimerCall + 3000000000) {
            lcd.value = random.nextDouble() * 100;
            lcd.trend = Lcd.Trend.values()[random.nextInt(5)];
            charge += 0.02;
            if (charge > 1.0) charge = 0.0;
            lcd.batteryCharge = charge;
            lastTimerCall = now;
            print(lcd.value);
        }
    }
}
// Initialize the Enzo Lcd control
var lcd   = new Lcd();
lcd.styleClass.add(Lcd.STYLE_CLASS_STANDARD_GREEN);
lcd.title                    = "Room Temp";
lcd.unit                     = "°C";
lcd.decimals                 = 2;
lcd.minMeasuredValueDecimals = 2;
lcd.maxMeasuredValueDecimals = 2;
lcd.unitVisible              = true;
lcd.batteryVisible           = true;
lcd.alarmVisible             = true;
lcd.minMeasuredValueVisible  = true;
lcd.maxMeasuredValueVisible  = true;
lcd.lowerRightTextVisible    = true;
lcd.formerValueVisible       = true;
lcd.trendVisible             = true;
lcd.lowerRightText           = "Info";
lcd.valueAnimationEnabled    = true;
lcd.foregroundShadowVisible  = true;
lcd.crystalOverlayVisible    = true;
// Prepare the stage and add controls
var root     = new StackPane();
root.padding = new Insets(10, 10, 10, 10);
root.children.add(lcd);
$STAGE.title = "Enzo with Nashorn";
$STAGE.scene = new Scene(root, 528, 192);
$STAGE.show();
// Start the timer
timer.start();

The JavaScript code that I saved to a file "javafx.js" looks not that different from the JavaFX code except...it's JavaScript...and the resulting application will look like this...




You might ask yourself how you could run this code and here is the answer, all it takes is one call on the command line that looks like this:


/PATH/TO/JDK/jdk1.8.0.jdk/Contents/Home/jre/bin/jjs
-cp /PATH/TO/LIBRARY/Enzo.jar -fx /PATH/TO/JS-FILE/javafx.js


Means you have to now the path to your jdk8 installation folder where you could find the jjs executable that is needed to start nashorn from the command line. In addition you have to add the Enzo.jar to the classpath so that you could use it within your JavaScript file and at last you have to call the JavaScript file with the -fx parameter which will start the application....BAM...that's all...sweet :)

I've put the commandline in a bash script so that I could call it by simple calling the bash script instead of typing the whole line over and over again.


If you start the javafx.js file you should see something like on this youtube video.


Ok you might say where is the advantage of using JavaScript to run a JavaFX application...just think about not starting an IDE, not compiling and building a JavaFX application just to make a short test. Isn't it be much nicer to simply have a small JavaScript, edit it with your default text editor and run it with from the command line with one single call...I really like that for testing things.


If you would like to know more about Nashorn you should definitely take a look at the Nashorn blog ,subscribe to the Nashorn mailing list and take a look at the Nashorn Wiki.


At this point I would say THANK YOU to Jim Laskey and Marcus Lagergren which helped me to getting this stuff done!!!


This is really a first test of using Nashorn with JavaFX but now that I know how to use it I will use more often...so stay tuned and keep coding...


Tuesday May 07, 2013

jjs -fx

So after some playing around and working with the JavaFX folks, I think we have a proposal for jjs that works with JavaFX.  The -fx flag on jjs will bootstrap scripts using a javafx.application.Application.  Thus, writing JavaFX scripts in Nashorn is very easy.

The basic command line is;
	jjs -fx fxscript.js
You can mix and match other jjs options like -scripting and -- ;
	jjs -fx -scripting fxscript.js -- my script args
The content of the script follows some of the examples I've posted before.  The script may optionally contain JavaFX init, start and/or stop functions.  What is new, is that you can leave them behind and just straight script.  The original hello world example;

var Button    = Java.type("javafx.scene.control.Button");
var StackPane = Java.type("javafx.scene.layout.StackPane");
var Scene     = Java.type("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();
}

becomes;

var Button    = Java.type("javafx.scene.control.Button");
var StackPane = Java.type("javafx.scene.layout.StackPane");
var Scene     = Java.type("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();

where the stage is now a global var $STAGE (instead of the start function argument.)

Also for convenience, we've predefined includes for all of the JavaFX classes.  I would recommend using only the classes you need (only needed for new and for static field access), but for prototyping having includes really helps things move along.

The hello world example can then be rewritten as;

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

The complete set of includes are as follows;

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

Here are a couple more examples;

// 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.to([
    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;
    
    // create first list of circles
    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);
    }
    
    // create second list of circles
    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);
    }
    
    // create third list of circles
    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);
    }
    
    // Set a blur effect on each layer
    layer1.effect = new BoxBlur(30, 30, 3);
    layer2.effect = new BoxBlur(2, 2, 2);
    layer3.effect = new BoxBlur(10, 10, 3);
    
    // create a rectangle size of window with colored gradient
    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;
    
    // create main content
    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);
    
    // create list of all circles
    var allCircles = new java.util.ArrayList();
    allCircles.addAll(layer1.children);
    allCircles.addAll(layer2.children);
    allCircles.addAll(layer3.children);
    
    // Create a animation to randomly move every circle in allCircles
    animation = new Timeline();
    for each (var circle in allCircles) {
        animation.getKeyFrames().addAll(
              new KeyFrame(Duration.ZERO, // set start position at 0s
                           new KeyValue(circle.translateXProperty(), Math.random() * WIDTH),
                           new KeyValue(circle.translateYProperty(), Math.random() * HEIGHT)),
              new KeyFrame(new Duration(20000), // set end position at 20s
                           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();
}



Playing ITunes from Nashorn

If you are on Mac OS X, it's possible to use Apple Script from Nashorn.  For example if you want to play a song in iTunes, the following script will do it;

#!/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

HTTP Server Written In Nashorn

This is just a little "thinking outside the box" example.


Node.js is hot and anyone who has used it knows there is a ton of applications possible. Some of Node.js features remind me of a tool kit I had, when I worked for another server company prior to Oracle. All my development was remote and had to run on remote servers. I hate typing and ssh/console was my only way in. So, I decided that I would create a little HTTP server on the remote end, to proxy all my routine repository, build and file editing tasks via a browser. This didn't take a lot of effort and was very flexible.


There are many many HTTP server apps out there (many written in Java.) I could modify one of them, but I just wanted to prove to myself it could all be done in pure Nashorn. This is what I whipped up;


#!/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);
    }
}

Short and sweet, all done in 84 lines of JavaScript. I could have handled more of the http spec, but this will do as POC.


You can use this server to download html, jpeg et al, but in the midst of this server code you will see the following lines;


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

This reads as, if the requested file ends with .jjsp, then evaluate the content as JavaScript and use the final result as an HTML response.


The following is a sample ".jjsp" file the I used for testing.


#!/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;

Note that both the JavaScript and HTML content of the .jjsp file is very easy to read.  This particular script catalogs a directory (ls) and colourizes the result depending on the file extension. In my case the result was;

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

Lots of possibilities.

Saturday Apr 06, 2013

To Shell Or Not To Shell

I find myself facing a dilemma today. How should I use Java FX from Nashorn? So far, I have two approaches I could use, but each comes with some issues. Some background first.


Java FX, even though ships with the JDK, is on a different build cycle and has dependencies on elements of the JDK. This arraignment limits Nashorn, which is part of the JDK, from actually having dependencies into Java FX. But, there is a dependency requirement to implement a Java FX application. Java FX applications begin with a subclass instance of javafx.application.Application. Therefore, whatever choice is made, it has to be independent of the JDK (at some point should be part of Java FX.)


The first approach, in general terms, is the easiest to use. It involves using a predefined shell that is similar to jjs but handles the overrides of Application methods init, start and finish. The source of this shell is currently checked into the Nashorn repo under 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;
/**
 * This shell is designed to launch a JavaFX application written in Nashorn JavaScript.
 */
public class FXShell extends Application {
    /**
     * Script engine manager to search.
     */
    private ScriptEngineManager manager;
    /**
     * Nashorn script engine factory.
     */
    private NashornScriptEngineFactory factory;
    /**
     * Main instance of Nashorn script engine.
     */
    private ScriptEngine engine;
    /**
     * Needed so that the FX launcher can create an instance of this class.
     */
    public FXShell() {
    }
    /**
     * Main entry point. Never actually used.
     * @param args Command line arguments.
     */
    public static void main(String[] args) {
        launch(args);
    }
    /*
     * Application overrides.
     */
    @Override
    public void init() throws Exception {
        // Script engine manager to search.
        this.manager = new ScriptEngineManager();
        // Locate the Nashorn script engine factory.  Needed for passing arguments.
        for (ScriptEngineFactory engineFactory : this.manager.getEngineFactories()) {
             if (engineFactory.getEngineName().equals("Oracle Nashorn") &&
                 engineFactory instanceof NashornScriptEngineFactory) {
                this.factory = (NashornScriptEngineFactory)engineFactory;
            }
        }
        // If none located.
        if (this.factory == null) {
            System.err.println("Nashorn script engine not available");
            System.exit(1);
        }
        // Get the command line and JNLP parameters.
        final Parameters parameters = getParameters();
        // To collect the script paths and command line arguments.
        final List<String> paths = new ArrayList<>();
        final List<String> args = new ArrayList<>();
        // Pull out relevant JNLP named parameters.
        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);
            }
        }
        // Pull out relevant command line arguments.
        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);
            }
        }
        // Create a Nashorn script engine with specified arguments.
        engine = factory.getScriptEngine(args.toArray(new String[args.size()]));
        // Load initial scripts.
        for (String path : paths) {
            load(path);
        }
        // Invoke users JavaScript init function if present.
        try {
            ((Invocable) engine).invokeFunction("init");
        } catch (NoSuchMethodException ex) {
            // Presence of init is optional.
        }
    }
    @Override
    public void start(Stage stage) throws Exception {
        // Invoke users JavaScript start function if present.
        try {
            ((Invocable) engine).invokeFunction("start", stage);
        } catch (NoSuchMethodException ex) {
            // Presence of start is optional.
        }
    }
    @Override
    public void stop() throws Exception {
        // Invoke users JavaScript stop function if present.
        try {
            ((Invocable) engine).invokeFunction("stop");
        } catch (NoSuchMethodException ex) {
            // Presence of stop is optional.
        }
    }
    /**
     * Load and evaluate the specified JavaScript file.
     *
     * @param path Path to UTF-8 encoded JavaScript file.
     *
     * @return Last evaluation result (discarded.)
     */
    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;
    }
}

To built it you can (cd make ; ant build-fxshell) from within the nashorn repo. The result is in nashorn/dist/nashornfx.jar. To use just java -cp dist/nashornfx.jar jdk.nashorn.tools.FXShell <myscript.js> … . For the JDK savvy you can create a launcher by modelling an entry in jdk/makefiles/CompileLaunchers.gmk after the jjs entry.


The big plus for this approach is that it handles almost everything for you. You just have to define a start method with a few class declarations and that is it. The down side is that ideally you would want this implemented as a jjsfx launcher embedded in the JDK. But then we run into the chicken and egg dependency on Java FX.


The second approach only relies on jjs. With a recent modification to Java.extend (currently only in the nashorn forest), it is now possible to subclass javafx.application.Application. and thus launch from within a script. This sounds like all pluses except for the fact you have to wrap your brain around the fact that FX applications take control of execution and has static init dependencies that require careful use in your program.


I prototyped a simple fxinit.js include that shows how we could implement such a scheme. Ignore the implementation quirks. It's simpler than it seems.

GLOBAL = this;
javafx = Packages.javafx;
com.sun.javafx.application.LauncherImpl.launchApplication(
(Java.extend(javafx.application.Application, {
    init: function() {
        // FX packages and classes must be defined here because they may not be
        // viable until launch time.
        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));

How you would use it is straight forward. Here is the FX HelloWorld.java example written for Nashorn;

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");
Note the placement of the load("fxinit.js");. Since this is where the FX Application takes control, anything after the load will not complete until the application exits.


One other quirk. Since you can not static init some of Java FX classes until after the application launches. You can not globally (script level) declare any uses of these classes. Uses can be embedded in methods used after the launch, but no where else. This is a style cramp for me.


There is a third approach I have been considering. It involves some argument trickery, but may play out as a better way of doing things. Imagine jjs fxinit.js -- myscript.js -- my scripts args . The -- indicates the beginning of arguments passed to the script. The notion here is that fxinit.js launches the application and then evals myscript.js. This cleanses my script of any quirks, while putting the onus on getting the command line right.


Thoughts?


Wednesday Mar 20, 2013

Nashorn Events Coming Up

The team will be going to several events in the next few weeks.

Attila will be at Devoxx UK next week,  starting with a JUG Hackday on Sunday March 24, 2013 and presentation titled "Project Nashorn In Java 8 - JavaScript As A First Class Language On The JVM" on Wednesday March 27, 2012.

Jim (me) will be at EclipseCon 2013 for the Thursday March 28, 2013 keynote.

About

Technical discussions and status of the Nashorn JavaScript Project.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
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