Showing posts with label nashorn. Show all posts
Showing posts with label nashorn. Show all posts

Wednesday, April 29, 2015

And the Nashorn again...

I know I know...it's a long time ago...but a lot of things happened within the last couple of months which kept me away from blogging...
So today my colleague Bruno Borges asked me if I would have an example where I use JavaFX with Nashorn only...means without writing any Java code and because I had no example I've created one :)

So here you go, simply copy the following code in a file named e.g. nashorn.js :


load('fx:base.js');
load('fx:graphics.js');

var Random        = java.util.Random;
var lastTimerCall = 0;

var Gauge         = Packages.eu.hansolo.enzo.gauge.Gauge;
var Section       = Packages.eu.hansolo.enzo.common.Section;

var timer         = new AnimationTimer() {
    handle: function handle(now) {
        if (now > lastTimerCall + 5000000000) {
            gauge.value = new Random().nextDouble() * 40;
            lastTimerCall = now;
        }
    }
};

var gauge      = new Gauge();
gauge.title    = 'NashornFX';
gauge.unit     = '°C';
gauge.minValue = 0;
gauge.maxValue = 40;
gauge.sections = [new Section(18, 26),
                  new Section(26, 32),
                  new Section(32, 40)];
gauge.style = ('-section-fill-0: rgba(0, 200, 50, 0.5);' +
               '-section-fill-1: rgba(200, 100, 0, 0.5);' +
               '-section-fill-2: rgba(200, 0, 0, 0.5);');

$STAGE.title = 'Nashorn FX';
$STAGE.scene = new Scene(new StackPane(gauge), 400, 400);
$STAGE.show();

timer.start();

And with a few lines of code we created a JavaFX scene and add a stage that contains a Gauge that has three sections with custom colors. In addition we also added an AnimationTimer that changes the value of the Gauge every 5 seconds.
For those of you who are wondering how I can access my Enzo-0.2.4.jar file here is solution...you can simply add it to the classpath when calling the script.

To be able to run the application you have to copy the Enzo-0.2.4.jar file to the same folder as the nashorn.js file. The Enzo library can be downloaded here.

To start the application simply execute the following on the command line:

#: jjs -cp Enzo-0.2.4.jar -fx nashorn.js

Please make sure you run that script on JDK 8 !

If everything is correct you should see something like this...



And that's it for today...keep coding... :)

Thursday, April 3, 2014

Poor mans live editor...

Aloha,

I was looking for an idea on how to show what one could do with the JavaFX WebView except showing html content and had an idea on how to combine the WebView and Nashorn for some live editing.
So you might have seen live editors where you can edit code and directly see some visual feedback (e.g. SwingPad).
Well I was not after recreating this editors but only on how to do this in a very simple way and this is what I've came up with.
Create a simple HTML page with just a textarea like follows:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        var doc = document;
        function getCode() {
            window.status = doc.getElementById('codeArea').value;
        }
    </script>
    <style type="text/css">
        textarea {
            font-size: 12px;
        }
    </style>
</head>
<body style='background-color: rgb(216, 222, 227)'>
<textarea id='codeArea' name='codeArea' onkeyup='getCode()' rows='23' cols='55'>
// Import JavaFX classes
var Color   = javafx.scene.paint.Color
var Led     = Packages.eu.hansolo.enzo.led.Led
var LedType = Packages.eu.hansolo.enzo.led.Led.LedType


// JavaFX Code in JavaScript syntax
led.ledType  = LedType.ROUND
led.ledColor = Color.RED
led.on       = false
</textarea>
</body>

</html>

As you can see the HTML page only has one textarea that contains some code. The code is JavaScript and should set some parameters of an Enzo Led JavaFX control later on.
I hooked up an onkeyup listener that will trigger the getCode() method each time a key was pressed.
This method only set the window.status to the current value of the textarea tag.
The nice thing in the JavaFX WebView is the possibility to hook up a ChangeListener to the window.status of the loaded HTML page.
That means as soon as the window.status in the loaded HTML page changed, the ChangeListener in the JavaFX application will be triggered and one could read the new status of the HTML page (which is the text in the textarea in this case). 
This is the point where Nashorn joins the game, I simply pass the text from the textarea to the Nashorn JavaScript engine and try to evaluate it as code. If the code is valid it will be executed immediately. To see the changes in the JavaFX application I've added the Enzo JavaFX Led control to the Scene. The last thing we need to do is to inject the Led control instance to the Nashorn scripting engine so that Nashorn is able to change the parameters of the Led control.

Here is the JavaFX code that demonstrates the live editor.

public class Demo extends Application {
    private ScriptEngineManager scriptEngineManager;
    private ScriptEngine        scriptEngine;
    private StackPane           ledContainer;
    private Led                 led;
    private WebView             webView;
    private WebEngine           webEngine;


    // ******************** Initialization ************************************
    @Override public void init() {
        led = new Led();

        ledContainer = new StackPane(led);
        ledContainer.setPrefSize(400, 400);
        ledContainer.setPadding(new Insets(10, 10, 10, 10));        

        initNashorn();
    }

    private void initNashorn() {
        scriptEngineManager = new ScriptEngineManager();
        scriptEngine        = scriptEngineManager.getEngineByName("nashorn");

        // Inject led object into Nashorn scripting engine
        scriptEngine.put("led", led);
    }

    private void initWebView() {
        webView = new WebView();
        webView.setPrefSize(600, 400);
        webEngine = webView.getEngine();
        webEngine.load(Demo.class.getResource("editor.html").toExternalForm());
        webEngine.getLoadWorker().stateProperty().addListener((ov, o, n) -> {
            if (Worker.State.SUCCEEDED == n) {
                webEngine.setOnStatusChanged(webEvent -> {
                    try {
                        scriptEngine.eval(webEvent.getData());
                    } catch (Exception e) {/* code was not valid */}
                });
            }
        });
    }


    // ******************** Application start *********************************
    @Override public void start(Stage stage) {
        initWebView();

        HBox pane = new HBox();
        pane.setSpacing(10);
        pane.getChildren().addAll(ledContainer, webView);

        Scene scene = new Scene(pane);

        stage.setTitle("WebView live edit");
        stage.setScene(scene);
        stage.show();
    }

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

}

Keep in mind that you JDK8 and Enzo to run that code. The result should look similar to this...




This is a very easy poor man live editor that has a lot potential for improvements (like adding a syntax highlighting JavaScript library like highlight.js or codemirror) but it shows the power of the JavaFX WebView in combination with Nashorn.

That's it again...so enjoy Java and keep coding...

Friday, July 19, 2013

Give the Nashorn some Pi...

Hi there,

Exciting times...since 2 weeks we have an updated version of JDK8 available for the Raspberry Pi and it's based on build b97 (well in the meantime we have b98 and b99 should be available soon). This means the restrictions that we had with the december build of JDK8 for the Pi are gone...e.g. custom controls that have been developed for the desktop will run on the Pi without modification...YAY!!! Finally I can test my standard Enzo build on the Pi which is awesome.
In addition to this we now also have 3D support and Nashorn on the Pi...and again YAY!!!
Now it's time to give the Nashorn some Pi and see if it works. I explained my idea some weeks ago, if you develop Java for the Raspberry Pi it's not that bad, you can develop your application on your desktop computer, create a jar, copy the jar to the Pi and start it there. That's ok...BUT if you would like to change something or test something this procedure could get boring because it's timeconsuming. So one possible solution might be using property files that you load at startup and so you have the chance to modify parameters. The disadvantage is that you have take care about each property in your app which means you have to know all the parameters you would like to change up front...not a really good solution.
When I read about Nashorn the idea came to my mind that it might be cool if I could access JavaFX controls from a script that could be loaded at runtime, so not only at startup but also when the application is running.
Well I tested that and it worked on the desktop so now it's time to test it on the Raspberry Pi.
In principle you could use the same code that I use for the "Taming the Nashorn again..." blog post to run a script at startup and you will figure out that it simply works.

Attention:
You have to put the nashorn.jar which is in $JAVA_HOME/jre/lib/ext on the classpath (don't ask me why but Jim Laskey figured that out and it worked...thanx to Jim for the hint).

To load a script during runtime you can use the same routine as for loading it at startup, so that's easy but I would like to go a step further, add a Filesystem Listener to a javascript file and reload the file if it was modified. With this approach I would be able to start a JavaFX application on the Raspberry Pi, connect to the Pi via SSH and modify the JavaScript file with a simple text editor like nano on the shell. After I save the JavaScript file the JavaFX application will be triggered because the file was modified and so the file will be reloaded and re-applied to the application (make sure that you are on the FXApplicationThread by calling Platform.runLater(new Runnable())...).
Because the polling of the Filesystem Listener is not that fast it could take a second or two before the Application will change but this is no problem because I can now modify a running application...sweet.
Ok, that's nice but what if I don't have a direct network connection to my Raspberry Pi? Means I can't use SSH easily to connect to the Pi and modify the JavaScript file...
Well as you might know I'm a big fan of XMPP so why not using a simple chat to send the script directly to the JavaFX application on the Raspberry Pi and evaluate it by the Nashorn scripting engine. Long story short...it's just to easy to implement because you simply send a text message to the JavaFX application via XMPP and pass the script text to the Nashorn scripting engine where it will get interpreted...done :)
To give you an idea about how that works I've created a little screen video that I've put on YouTube.



To see the text in the shell window you should increase the screen size of the video.

I won't show any code here because it's not finished yet so please be give me some time to prepare a little project that will contain the code...

UPDATE:
I've put the code I used for the demo on a BitBucket repo, so feel free to take a look at it...

That's it for today...enjoy taming the Nashorn and keep coding...

Monday, June 17, 2013

Taming the Nashorn...AGAIN...

Hi there,
Like I said some post ago I will make use of the Nashorn scripting capabilities more often in the future and so here is another example.
To be honest I'm looking in the Nashorn scripting stuff only because I would like to improve my workflow for the Raspberry Pi embedded development. Usually I create and test the jar files on my desktop computer before I copy them via scp to the Raspberry Pi. On the Pi I then execute them on the command line. In principle this is ok but if you would like to test new things you have to do the modification-compile-copy procedure very often and that takes time. So the idea is that as soon as a new JDK8 developer preview for the Raspberry Pi is out (and hopefully contains Nashorn) I could create a Java application that loads a JavaScript file at runtime and executes the code in the JavaScript file. With this scenario I could edit the JavaScript file with nano for example and simply start the jar again. This would improve the development workflow a lot. So because we don't have Nashorn on the Raspberry Pi at the moment I have to test it on my desktop where it is part of the JDK8 developer preview already.

Example:
Let's take a gauge from the Enzo library that will look like this...


The idea is to configure the gauge with default values if no JavaScript file is present and if there is a JavaScript file with a given name at the same place as the jar file it should be loaded and the configuration should be taken from the JavaScript file.
To realize that we first of all need a simple JavaFX application that contains the gauge.
So this is the code for that...

public class GaugeDemo extends Application {
    private Gauge gauge;

    @Override public void init() {
        gauge = new Gauge();
    }

    @Override public void start(Stage stage) {
        StackPane pane = new StackPane();
        pane.getChildren().add(gauge);

        Scene scene = new Scene(pane);

        stage.setTitle("Nashorn demo");
        stage.setScene(scene);
        stage.show();
    }

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

}

Simple...isn't it...? Ok, now let's create a method that configures the gauge with some default values. In this case we will define things like a title, a unit, some sections and modify the colors of the sections. So the code will look like this...

private void setGaugeDefaults() {
    gauge.setTitle("Default");
    gauge.setUnit("°F");
    gauge.setMinValue(32);
    gauge.setMaxValue(212);
    gauge.setSections(new Section(104, 140),
                      new Section(140, 176),
                      new Section(176, 212));
    gauge.setStyle("-needle       : rgb(  0,   0, 255);" +
                   "-section0-fill: rgb(  0, 255,   0);" +
                   "-section1-fill: rgb(255, 255,   0);" +
                   "-section2-fill: rgb(255,   0,   0);");

}

Please keep in mind that calling setStyle() is not the best way to set a JavaFX CSS style but it's ok for this example!!!

If we call this method in the init() method after we have initialized the Gauge object the result will look like this...


So we now have the default style that should be used if no JavaScript file is present. So now we need a method to load a JavaScript file. We need the JavaScript file to be loaded into a String object that we could evaluate with the JavaScript engine. The code that we need to realize that looks like follows...

private String getScript(final String fileName) {
    StringBuilder scriptContent = new StringBuilder();
    try {
        Path         path  = Paths.get(fileName);
        List<String> lines = 
            Files.readAllLines(path, StandardCharsets.UTF_8);
        for(String line : lines) {
            scriptContent.append(line);
        }        
    } catch (IOException | URISyntaxException exception) {}
    return scriptContent.toString();

}

Now we are able to load a JavaScript file into a String so it's time to initialize the JavaScript engine...means unleash the Nashorn... :)
But before we can do that we need a JavaScript file. The idea is that we could pass the gauge object to the JavaScript file and modifiy the gauge within the JavaScript file.
So the JavaScript file will look like follows...

var ArrayList = java.util.ArrayList;
var Gauge     = Packages.eu.hansolo.enzo.gauge.Gauge;
var Section   = Packages.eu.hansolo.enzo.gauge.Section;

var obj = new Object();

obj.initGauge = function(gauge) {
    gauge.title      = 'Nashorn';
    gauge.unit       = '°C';
    gauge.minValue   = 0;
    gauge.maxValue   = 100;

    var sections     = new ArrayList();
    sections.add(new Section(40, 60));
    sections.add(new Section(60, 80));
    sections.add(new Section(80, 100));
    gauge.sections   = sections;

    gauge.style      = "-needle         : rgb(243,56,28);" +
                       "-section0-fill  : rgb(192, 215, 123);" +
                       "-section1-fill  : rgb(217, 191, 78);" +
                       "-section2-fill  : rgb(225, 75, 69);"

}

So the JavaScript above will initialize the gauge for measuring temperature in degree celsius where the setGaugeDefaults() method in our Java class will initialize the gauge for measuring the temperature in degree fahrenheit. Means it should be very easy to see if the script was loaded or not.
Because we could have more than one method in our JavaScript file that we might call in our Java code I created a method that loads the script once, get's the obj Object from the script an then we can use this script object to call the methods. So here is the method that will instantiate three member variables (the ScriptEngine, the so called Invocable and an Object that contains the JavaScript obj)...

private void initScript(final String script) {
    try {
        engine.eval(script);
        inv          = (Invocable) engine;
        scriptObject = engine.get("obj");
    } catch (ScriptException exception) { 
        script = ""; 
    }

}

Now we only have to add a method that will initialize the gauge object in dependence on the presence of a JavaScript file. So if the JavaScript file is present it should be taken to initialize the gauge otherwise the gauge should be initialized by calling the setGaugeDefaults() method. And here is the code...

private void initGauge() {
    gauge = new Gauge();
    if (script.isEmpty()) {
        // Default Gauge settings
        setGaugeDefaults();
    } else {
        // Settings from Script
        try {
            inv.invokeMethod(scriptObject, "initGauge", gauge );
        } catch (ScriptException | NoSuchMethodException | 
            RuntimeException exception) {
            // In case of an error set default values
            setGaugeDefaults();
        }
    }

}

With this method in place we are able to initialize our gauge by loading a JavaScript file during runtime...sweet.
So the complete Java code will look similar to this...

public class GaugeDemo extends Application {
    private static final String  SCRIPT_FILE_NAME = "config.js";
    private static final Charset ENCODING         = StandardCharsets.UTF_8;
    private ScriptEngineManager  manager;
    private ScriptEngine         engine;
    private String               script;
    private Invocable            inv;
    private Object               scriptObject;
    private Gauge                gauge;


    // ******************** Initialization ************************************
    @Override public void init() {
        manager = new ScriptEngineManager();
        engine  = manager.getEngineByName("nashorn");
        script  = getScript(SCRIPT_FILE_NAME);
        initScript(script);
        initGauge();
    }

    private void initScript(final String script) {
        try {
            engine.eval(script);
            inv          = (Invocable) engine;
            scriptObject = engine.get("obj");
        } catch (ScriptException exception) { 
            script = ""; 
        }
    }

    private void initGauge() {
        gauge = new Gauge();
        if (script.isEmpty()) {
            // Default Gauge settings
            setGaugeDefaults();
        } else {
            // Settings from Script
            try {
                inv.invokeMethod(scriptObject, "initGauge", gauge );
            } catch (ScriptException | NoSuchMethodException | 
                RuntimeException exception) {
                System.out.println("Error executing initGauge() in JavaScript");
                setGaugeDefaults();
            }
        }
    }


    // ******************** Methods *******************************************
    private void setGaugeDefaults() {
        gauge.setTitle("Default");
        gauge.setUnit("°F");
        gauge.setMinValue(32);
        gauge.setMaxValue(212);
        gauge.setSections(new Section(104, 140),
                          new Section(140, 176),
                          new Section(176, 212));
        gauge.setStyle("-needle         : rgb(0, 150,   0);" +
                       "-section0-fill  : rgb(0, 100, 150);" +
                       "-section1-fill  : rgb(0, 100, 200);" +
                       "-section2-fill  : rgb(0, 100, 255);");
    }

    private String getScript(final String fileName) {
        StringBuilder scriptContent = new StringBuilder();
        try {

            // ******** NEEDED FOR SCRIPT IN SAME FOLDER AS JAR ***************
            final URL    root       = GaugeDemo.class.getProtectionDomain()
                                               .getCodeSource().getLocation();
            final String scriptPath =
                (new File(root.toURI())).getParentFile().getPath() + 
                File.separator + fileName;
            // ****************************************************************            

            Path         path       = Paths.get(scriptPath);
            List<String> lines      = Files.readAllLines(path, ENCODING);
            for (String line : lines) {
                scriptContent.append(line);
            }
            System.out.println("Script: " + scriptPath + " loaded");
        } catch (IOException | URISyntaxException e) {}
        return scriptContent.toString();
    }


    // ******************** Start application *********************************
    @Override public void start(Stage stage) {        
        StackPane pane = new StackPane();
        pane.setPadding(new Insets(10, 10, 10, 10));
        pane.setPrefSize(400, 400);

        pane.getChildren().addAll(gauge);

        Scene scene = new Scene(pane);

        stage.setTitle("Nashorn JavaFX");
        stage.setScene(scene);
        stage.show();        
    }

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

}

Be aware that in the code above the JavaScript file should be placed in the same folder as the jar. If you would like to load the JavaScript with an absolute path you simply could remove the block that is marked in the code and use the full path name for the SCRIPT_FILE_NAME constant.

If I start the code above the result looks like this...


As you can see the gauge was initialized by loading and evaluating the JavaScript file "config.js".

So let's hope the Nashorn will find it's way to the JDK8 ARM developer preview very soon... :)

I hope you enjoy playing with the Nashorn as much as I do...so keep coding...