quinta-feira, 10 de julho de 2014

JavaFX apps using Javascript and Gainda

In my last posts I've worked the with Nashorn Javascript engine to create JavaFX application.

It's great to work with JavaFX and Javascript, however, we can't forget that a boring part of the javascript code is that we have to import Java classes do be used and this can be a tedious things because you just want to script the view... See some examples of imports I had to use in my last projects

var Scene = Java.type('javafx.scene.Scene')
var VBox = Java.type('javafx.scene.layout.VBox')
var Label = Java.type('javafx.scene.control.Label')
var TextField = Java.type('javafx.scene.control.TextField')
var PieChart = Java.type('javafx.scene.chart.PieChart')
var TableView = Java.type('javafx.scene.control.TableView')
var TableColumn = Java.type('javafx.scene.control.TableColumn')
var PropertyValueFactory = Java.type('javafx.scene.control.cell.PropertyValueFactory')
var SearchService = Java.type('org.jugvale.sentiments.service.SearchService')
var TextSentimentService = Java.type('org.jugvale.sentiments.service.TextSentimentService')
var TableView = Java.type('javafx.scene.control.TableView')
var FXCollections = Java.type('javafx.collections.FXCollections')


Well, it can be improved and that's the goal of the Gainda project. Gainda means Nashorn in Hindi (गैंडा) and Nashorn is the name of the new Javascript engine that comes with Java 8.
With Gainda, we have like a DSL for JavaFX based on Javascript. See how a Hello World application looks like with Gainda:

load('./dist/gainda.js');

Gainda.run([ 'base', 'graphics', 'controls' ], function (stage) {

    var button = new Button();
    var root = new StackPane();

    stage.title = "Hello World!";
    
    button.text = "Say 'Hello World'";
    button.onAction = function() print("Hello World!");
    
    root.children.add(button);

    stage.scene = new Scene(root, 300, 250);
    stage.show();

});


Awesome! The project was created by Rajmahendra, the leader of the Chennai JUG.

My first application using Gainda

Here's the step-by-step to create an application using this framework:

1) Download the gainda.core.js file from github;
2) load it in your application: load('./gainda.core.js');
3) everything happens on run function, you must call this function and pass the modules you want to load, then you are ready to write JavaFX app using Gainda
4) Run it using jjs that can be found in JDK 8 bin folder, use the following command to run it:

$ jjs -fx myApp.js

My first application is a simple app that blinks a text when we click on it:

load('./gainda.core.js');
Gainda.run([ 'base', 'graphics', 'controls' ], function (stage) {
    var txt = new Text("Hello, Gainda!");
    var blink = new FadeTransition(Duration.millis(200), txt);
    txt.effect = new Reflection();
    txt.font = new Font(50);
    txt.fill = Color.RED
    blink.fromValue = 1;
    blink.toValue = 0;
    blink.autoReverse = true;
    blink.cycleCount = 2;
    txt.onMousePressed = function(e){
            blink.playFromStart();    
    };  
    stage.title = "Hello Gainda!";
    stage.scene = new Scene(new StackPane(txt), 350, 150);
    stage.show();
});

Conclusion

Gainda is a new utility for who likes to use another language to create user interface. The project is still starting and if you want to contribute, please contact Rajmahendra.

terça-feira, 24 de junho de 2014

Another World Cup App... Using JavaFX, FXML, Javascript, CSS

The World Cup Brazil is happening! It's exciting to see people from all over the world visiting my country! Today I decided to create another World Cup App, this time I'll use JavaFX, but I won't write any Java code.
It's a simple app to visualize all the world cup matches and click on a game for details about it. I spent less than 3 hours working on the core of the application and a few more hours working on the details.

Getting the resources to create the App

By resources I meant images and to do this I downloaded all the flags images from Fifa site. It was a small and easy scrapping. Notice that all images are in under the following URL: http://img.fifa.com/images/flags/4/{CODE}.png. To download all flags I used the following Python Script:

import urllib2
codes = open('countries_codes.txt', 'r')
for line in codes:
        code = line.replace('\n', '').lower() + '.png'
        img_url = 'http://img.fifa.com/images/flags/4/' + code
        print img_url
        req = urllib2.Request(img_url)
        response = urllib2.urlopen(req)
        img = response.read()
        f = open(code, 'w')
        f.write(img)

I also used to following image as a inspiration to our app layout:



The app view

The view was entirely created on Scene Builder and it was given a styles and IDs to the view components, so from Java we could highlight some parts according to the world cup information.



We have all games displayed on the app and when the user clicks on a game, the game details are showed in a central pane:



The app style

TO change the application to have Brazil colors, we used a very simple CSS file which also changes the appearance of a match when the mouse is on it, see:

.root{
        -fx-base: CornflowerBlue;
        -fx-background: rgb(227,231,239);
}
.TitledPane{
        -fx-text-fill: FIREBRICK;
}
#match_details{
        -fx-background-color: rgb(177,174,218);
}
.match:hover{
        -fx-font: bold 12pt "Calibri";
        -fx-effect: dropshadow(three-pass-box , blue, 20, 0.4 , 0 , 0 );
        -fx-cursor: hand;
}

The app logic

As this is a temporal application, we won't need to create model classes to represent the data, it was used only Javascript to write the application logic!
The logic is simple: we will the app information from a JSON served using HTTP. A good soul scrapped FIFA site to make the results available in JSON. We request this JSON and basically fill our application elements with the JSON. Notice I give an id to every match (that was tedious!) and from the json I fill the data, see:

var matches = downloadMatchesInfo()
...
function downloadMatchesInfo(){
        print("Trying to download the matches information.........")
        var out, scanner
        try{
                var urlStream = new java.net.URL(MATCHES_DATA_URL).openStream()
                scanner = new java.util.Scanner(urlStream, "UTF-8")
                // TODO: save the latest downloaded JSON
        }catch(e){
                print("Couldn't download the updated information, using a cached one.....")
                scanner = new java.util.Scanner(new java.io.File(CACHED_DATA_URL), "UTF-8")
    
        }    
        scanner.useDelimiter("\\A")
        out = scanner.next();
        scanner.close();
        return eval(out);
}


Using eval transform our String into a Javascript object! So I can access the JSON data directly, see how the matches are filled:

function fillMatch(match){
        var viewMatch = $STAGE.scene.lookup("#match_" + match.match_number)
        if(viewMatch && match.home_team.country){
                viewMatch.children[0].image = getImg(match.away_team.code)
                viewMatch.children[1].text = match.status == "future"? "_": match.home_team.goals;
                viewMatch.children[3].text = match.status == "future"? "_": match.away_team.goals;
                viewMatch.children[4].image = getImg(match.home_team.code)
        }
        viewMatch.onMouseClicked = function(e){
                fillMatchDetails(match)
        }
}

function getImg(code){
        var imgUrl = code?"./images/teams/" + code.toLowerCase() + ".png":"./images/no_team.png"
        if(!imgCache[imgUrl])
                imgCache[imgUrl] = new javafx.scene.image.Image(new java.io.FileInputStream(imgUrl))
        return imgCache[imgUrl]

}


See in the code above how easy is to use Javascript structures in a code that deal with Java libraries(particularly see our images cache named imgCache). Notice that when the user clicks on a match, we register a listener to show the match details on the central pane:

function fillMatchDetails(match){
        var s = $STAGE.scene;
        detailsTransition.playFromStart()
        var notPlayedScore = match.status == "completed"?"0":"_"
        s.lookup("#match_home_team").image = getImg(match.home_team.code)
        s.lookup("#match_away_team").image = getImg(match.away_team.code)
        s.lookup("#match_home_score").text = match.home_team.goals?match.home_team.goals:notPlayedScore
        s.lookup("#match_away_score").text = match.away_team.goals?match.away_team.goals:notPlayedScore
        s.lookup("#match_status").text = "Match " + match.status
        s.lookup("#match_time").text =  match.datetime.substring(0, 16)
        s.lookup("#match_location").text =  match.location
}


Instead injecting elements on a controller, I decided to get it by id using the Scene lookup method.

Running the application

The applications isn't compiled, it's interpreted! To run it, you just need to have Java 8 on you path to use the jjs utility. As the code is on github, you can clone it and run:

$ git clone https://github.com/jesuino/another-world-cup-app.git
$ cd another-world-cup-app
$ jjs -fx app.js

You can also set JAVA_HOME on run.sh and make it an executable to run it:

$ chmod +x run.sh
$ ./run.sh

If you are on windows, my girlfriend, Luana, tested it on this "operating system" and she just had to create a run.bat file on the application root directory with the following content:

"C:\Program Files\Java\jre8\bin\jjs" -fx app.js

Notice you might have to change it according to your Java 8 installation.

Conclusion


As you know, it's really easy to create
JavaFX apps and we don't even need to write Java code. We used Javascript to read the resulting JSON and interact with the view. which showed to be a great approach since we didn't have to use any third part library to deal with the JSON.
I could also teach my girlfriend how to run and change the app using Scene Builder and appearance and she is not a Java programmer and she actually changed the style of the application by modifying the CSS!

Here's the final application:





terça-feira, 15 de abril de 2014

Writing JavaFX apps using Javascript with the Java 8 Nashorn engine

Java 8 was just a great release! We already talked about Lambdas and created JavaFX 8 apps on this blog. Bruno Borges created a fx2048, a JavaFX version of the famous 2048 game, which shows Lambdas and the Stream library usage.

Today I'll rewrite the Sentiments App view in Javascript using the new JS engine named Nashorn, one of the exciting Java 8 new features.

Loading the Script


We load a file with the view contents, but before doing this, we add the main Stage so it can be referenced from the javascript file. That's enough for the Java part!

package org.jugvale.sentiments.view;

import javafx.application.Application;
import javafx.stage.Stage;
import javax.script.*;
import java.io.FileReader;

public class AppJS extends Application{

        private final String SCRIPT = getClass().getResource("/sentimentsView.js").getPath();

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

        @Override
        public void start(Stage stage){
                try{
                        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
                        engine.put("stage", stage);
                        engine.eval(new FileReader(SCRIPT));
                }catch(Exception e){ 
                        e.printStackTrace();
                }   
        }   
 }



Notice in my case I'm calling the script from my Java code, but it's possible to use jjs to invoke scripts and build JavaFX apps.

Writing Javascript

After this, we can already start writing Javascritp code and leave Java behind for a moment. The core of the application (the service and model classes)  is written in Java and we can refer to it in our JS.

The good parts

The best thing about JS I can highlight is that we don't need to use get/set methods anymore! Nashorn threat Java beans properties (those accessed by get and set methods) as an object property and behind the scenes it will invoke the get/set methods!
We also take full advantage of the scripting language: don't need to use semiclon, easier to declare variables, more clar
I liked the fact that I don't need to recompile the class to update the view. If I modify the script and re-run the java part, the view will be updated!

The bad parts

I didn't like how to handle the imports. It seems that I have to either declare the type or write the FQN of a class to instantiate it.
I also prefer Java and FXML over writing the view in pure javascript. Remember you can script on FXML as well!

Code and running

Here's the final script for the Sentiments App view:

// Imports
var Scene = Java.type('javafx.scene.Scene')
var VBox = Java.type('javafx.scene.layout.VBox')
var Label = Java.type('javafx.scene.control.Label')
var TextField = Java.type('javafx.scene.control.TextField')
var PieChart = Java.type('javafx.scene.chart.PieChart')
var TableView  = Java.type('javafx.scene.control.TableView')
var TableColumn  = Java.type('javafx.scene.control.TableColumn')
var PropertyValueFactory  = Java.type('javafx.scene.control.cell.PropertyValueFactory')
var SearchService  = Java.type('org.jugvale.sentiments.service.SearchService')
var TextSentimentService  = Java.type('org.jugvale.sentiments.service.TextSentimentService')
var TableView  = Java.type('javafx.scene.control.TableView')
var FXCollections = Java.type('javafx.collections.FXCollections')

// variable declaration
var root = new VBox(10)
var txtQuery = new TextField()
var table = new TableView()
var chart = new PieChart()
var textSentimentService = new TextSentimentService()
var searchService = new SearchService()

// main block
root.children.addAll(txtQuery, chart, table)
stage.scene = new Scene(root)
stage.title = "Sentiments"
stage.width = 400
stage.height = 550
stage.show()
initializeApp()
// functions 
function initializeApp(){
 txtQuery.promptText = "Enter your query for text and press enter"
 chart.title = "Sentiments for \"" + txtQuery.getText() +"\""
 txtQuery.onAction = queryAction
 addTableColumns()
 queryAction(null)
}
function queryAction(e){
 var textSentiments = getTextSentiments(txtQuery.getText());
 fillTable(textSentiments);
 fillChart(textSentiments);
}
function fillTable(textSentiments){
 table.items = FXCollections.observableList(textSentiments)
}
function fillChart(textSentiments){
 var polarity0Count = textSentiments.stream().filter(function (s) s.getPolarity() == 0).count();
 var polarity2Count = textSentiments.stream().filter(function(s)  s.getPolarity() == 2).count();
 var polarity4Count = textSentiments.stream().filter(function(s)  s.getPolarity() == 4).count();
 chart.data = 
  FXCollections.observableArrayList(
   new PieChart.Data(":(", polarity0Count),
   new PieChart.Data(":|", polarity2Count),
   new PieChart.Data(":)", polarity4Count)
 )
}
function getTextSentiments(q){
 return textSentimentService.getSentiments(query(q)).textSentiments
}
function addTableColumns(){
 var textCol = new TableColumn("Text")
 var polarityCol = new TableColumn("Polarity")
 textCol.cellValueFactory = new PropertyValueFactory("text")
 textCol.minWidth = 200
 polarityCol.cellValueFactory = new PropertyValueFactory("polarity")
 polarityCol.minWidth = 80
 polarityCol.resizable = false
 table.columns.setAll(textCol, polarityCol)
}
function query(q){
 if(!q || q.trim().isEmpty()){
  return java.util.Arrays.asList("obama is awesome", "obama sucks", "obama eats potato");
 }else{
  return  searchService.search(q, SearchService.Provider.TWITTER);
 }
}


The full code is on github and you can build it by simply typing the following maven instruction on command line (remember to have maven and JAVA_HOME pointing to your Java 8 installation root directory):

$ mvn package exec:java -Dexec.mainClass="org.jugvale.sentiments.view.AppJS" -DskipTests

Conclusion

Using Javascript from Java is easy and with the new JS engine we have increased the performance due the Invoke Dynamic feature.
Using JS can be a good advantage for a team who has JS programmers and needs to build a desktop app, so we can use their full potential on Javascript and take all advantages and libraries from Java.

terça-feira, 8 de abril de 2014

Having fun creating a JavaFX App to Visualize Tweet Sentiments

A cool think that people loves to do is to create apps that analyze the sentiment of some text stream, such as Twitter. To better understand, see a graphical sentiment analysis for the Java 8 release messages on Twitter (source):



In this post, I'm going to create a simple, really simple, app that queries twitter, and then access the Sentiment140 REST API to analyze the tweet's content sentiment. After this, I'll count the results in a chart and also list the text content in a table.

Using the Sentiment140 REST API

The API is really simple to use. Since I'm lazy, I love simplicity. To use it, submit a text and it will return a simple JSON with a number value called polarity, which possible values are:
  • 0: negative
  • 2: neutral
  • 4: positive
The API documentation contains all the information you need to use it. Again, I simply loved how easy is to use it. For example, if I want to submit the text "I Love REST APIs" to the API, I would have to HTTP POST a JSON containing it to the URL http://www.sentiment140.com/api/bulkClassifyJson  I would use the following curl command:


$ curl -d "{'data': [{'text': 'I Love REST APIs'}]}" http://www.sentiment140.com/api/bulkClassifyJson

And it will return:

{"data":[{"text":"I Love REST APIs","polarity":4,"meta":{"language":"en"}}]}

 See, it's not hard!

Querying Twitter

I wanted to query twitter, it was my plan... However, it's "new" API requires me to use OAuth to query. Twitter4j API, however, is great to handle Twitter, so I decided to keep twitter... See the code I use to query twitter:

private List searchTwitter(String q){ 
        Twitter twitter = new TwitterFactory().getInstance();
        twitter.setOAuthConsumer(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_KEY_SECRET);
        Query twitterQuery = new Query("lang:en " + q); 
        try{
                return twitter.search(twitterQuery)
                        .getTweets().stream()
                        // map to text and replace all special chars
                        .map(s -> s.getText().replaceAll("[^a-zA-Z0-9\\s\\,\\.\\']+",""))
                        .peek(System.out::println)
                        .collect(Collectors.toList());
        }catch(Exception e){ 
                throw new RuntimeException(e);
        }   
}

Accessing the APIs with Java

The first thing to do is to use model what we want in a class and what is want is basically what the Sentiment140 API returns. So we created a Java bean named TextSentiment which code is below:

@JsonIgnoreProperties(ignoreUnknown = true)
public class TextSentiment{
        private String text;
        private int polarity;

         // getters setters and constructors
}

@XmlRootElement
public class TextSentimentList{
        @XmlElement(name="data")
        private List textSentiments;
         // getters setters 
}


Now, we have need to find a good way to send HTTP requests to this API and return instances of TextSentiment. To do this We created a class called TextSentimentService, and in this class we make use of JAX-RS 2 Client API to query Sentiment140, so we don't waste time parsing data... The method getSentiment140 was used to get the sentiment of all the Strings we passed

import org.jugvale.sentiments.model.TextSentimentList;
import org.jugvale.sentiments.model.TextSentiment;
import java.util.List;
import java.util.stream.Collectors;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;

public class TextSentimentService{

        private final String SENTIMENTS_BASE_URL = "http://www.sentiment140.com/api/bulkClassifyJson";
        private final String MEDIA_TYPE = "application/json";

        public TextSentimentList getSentiments(List texts){
                TextSentimentList requestEntity = new TextSentimentList();
                requestEntity.setTextSentiments(
                        texts.stream().map(TextSentiment::new).collect(Collectors.toList())
                );  
                return ClientBuilder.newClient()
                                .target(SENTIMENTS_BASE_URL)
                                .request(MEDIA_TYPE)
                                .buildPost(Entity.entity(requestEntity, MEDIA_TYPE))
                                .invoke(TextSentimentList.class);
        }   

}



To access Twitter, we created a class SearchService, which has a method called search. In this method, we specify the provider we are wanting to retrieve texts to analyze, in our case, we have twitter:

import java.util.List;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import twitter4j.*;
import java.util.stream.Collectors;
public class SearchService{

        /*
         *It's mine!!! Please, use yours
         * */

        private final static String TWITTER_CONSUMER_KEY = "{KEY}";
        private final static String TWITTER_CONSUMER_KEY_SECRET = "{SECRET}";

        public static enum Provider{
                TWITTER("Twitter");

                private String name;
                Provider(String name){
                        this.name = name;
                }   
                public String toString(){
                        return this.name;
                }   
        }   

        public List search(String q, Provider p){ 
                switch (p){
                        case TWITTER:
                                return searchTwitter(q);
                        default:
                                throw new IllegalArgumentException("Provider \"" + p + "\" not implemented");
                }   
        }  
        // searchTwitter method...
}



Notice that both Service classes can be extended to make use of another sentiment API or another social media API.

Testing our service classes

Before continuing with our application, let's make a simple test of the Sentiment Service API, see the code below:

import org.junit.Test;
import org.jugvale.sentiments.service.*;
import org.jugvale.sentiments.model.*;
import java.util.Arrays;

public class TestService{

        @Test
        public void testServices(){
                String TXT_1 = "obama is awesome";
                String TXT_2 = "obama sucks";
                String TXT_3 = "obama is eating a potato";
                TextSentimentService s = new TextSentimentService();
                TextSentimentList list = s.getSentiments(Arrays.asList(TXT_1, TXT_2, TXT_3));
                list.getTextSentiments().stream().forEach(System.out::println);
                //TODO: Create a Map with key equal the text and polarity is the value
        }   
}


With this small and simple test, we make sure we are accessing the API and returning something, so we also confirm the parsing of the response if correct.

The JavaFX APP

 Our small APP contains one text field, one chart and one table! The user enters the query on the TextField, press enter, then our app takes that query to search the social media, get the results and then it sends the texts to the Sentiment140 API analyze. With the results, we update the user interface. See the code:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.layout.VBox;
import javafx.scene.chart.*;
import org.jugvale.sentiments.service.*;
import org.jugvale.sentiments.model.*;
import java.util.*;

public class App extends Application{

 final TextSentimentService textSentimentService = new TextSentimentService();
 final SearchService searchService = new SearchService();
 final PieChart chart =  new PieChart();
 final TableView table = new TableView<>();
 final TextField txtQuery = new TextField();

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

 @Override
 public void start(Stage stage){
  VBox root = new VBox(10);
  root.getChildren().addAll(txtQuery, chart, table);
  stage.setScene(new Scene(root));
  stage.setWidth(400);
  stage.setHeight(550);
  stage.setTitle("Sentiments");
  stage.show();
  txtQuery.setOnAction(e -> {
   List textSentiments = getTextSentiments(txtQuery.getText());
   fillTable(textSentiments);
   fillChart(textSentiments);
   chart.setTitle("Sentiments for \"" + txtQuery.getText() +"\"");
  });
  txtQuery.setPromptText("Enter your query for text and press enter");
  initializeApp();  
 }

 private void initializeApp(){
  txtQuery.setText(null);
  txtQuery.getOnAction().handle(null);
  addTableColumns();
 }
 
 private void addTableColumns(){
  TableColumn textCol = new TableColumn<>("Text");
  textCol.setCellValueFactory(new PropertyValueFactory("text"));
  textCol.setMinWidth(200);
  TableColumn polarityCol = new TableColumn<>("Polarity");
  polarityCol.setCellValueFactory(new PropertyValueFactory("polarity"));
  polarityCol.setMinWidth(80);
  polarityCol.setResizable(false);
  table.getColumns().setAll(textCol, polarityCol);
 }

 public void fillTable(List textSentiments){
  table.setItems(FXCollections.observableArrayList(textSentiments));
 }

 public void fillChart(List textSentiments){
  long polarity0Count = textSentiments.stream().filter(s -> s.getPolarity() == 0).count();
  long polarity2Count = textSentiments.stream().filter(s -> s.getPolarity() == 2).count();
  long polarity4Count = textSentiments.stream().filter(s -> s.getPolarity() == 4).count();
  chart.setData(
   FXCollections.observableArrayList(
    new PieChart.Data(":(", polarity0Count),
    new PieChart.Data(":|", polarity2Count),
    new PieChart.Data(":)", polarity4Count)
  ));
 }
 public List getTextSentiments(String q){
  return textSentimentService.getSentiments(query(q)).getTextSentiments();
 }

 public List query(String q){
  if(q == null){
   return Arrays.asList("obama is awesome", "obama sucks", "obama eats potato");
  }else{
   // TODO: add new providers and adapt the app to support it
   return  searchService.search(q, SearchService.Provider.TWITTER);
  }
 }
 }


Notice we use Java 8 features to deal with the List of  TextSentiment objects! Also, we used Lambda in some places of the code, can you spot it?

Do you wanna play with it?


The app code is on github and it uses Maven. if you want to play with it, use git app to clone the repository locally and build it. Remember to use Java 8 and Maven 3.x!  Steps:

 Clone the APP

$ git clone https://github.com/jesuino/sentiments-app.git
 
Go to the app root directory  and use the following command to build:

$ cd sentiments-app
$ mvn clean package

You can also run a test:

$ mvn test

Finally, you can run the App using the run.sh script. Remember to set JAVA_HOME correctly! run.sh content:

export JAVA_HOME=/opt/java/jdk1.8.0/
mvn package exec:java  -Dexec.mainClass="org.jugvale.sentiments.view.App" -DskipTests



App screenshots

Searching for happy words:




Searching for sad words:


Conclusion

 That's all, folks. Just wanted to share this small fun app with you. I made the code really simple because I'm lazy to someone extend it. If you extend it, please, send me pull requests :)
  • Extending by adding other text sentiment analyzer. Lots of text sentiments APIs here.
  • Extending by adding other source then twitter, for example:  news sites, google plus, facebook etc.
  • Improve the view by adding more controls, a "waiting screen", etc
  • Add a CSS to improve the look of the app