[Pesquisar este blog]

quinta-feira, 12 de janeiro de 2017

JavaScript na JVM

O uso do JavaScript tem crescido consistentemente nestes últimos anos, tanto que a linguagem não apenas figura, mas aparece bem posicionada nos mais conhecidos rankings de utilização de linguagens de programação (TIOBE Index, GitHub PYPL, RedMonk, IEEE Spectrum, etc).


Não é preciso justificar porque o JavaScript é importante. Introduzido em 1995 por Brendan Eich, sob o nome original Mocha, acabou sendo renomeado para JavaScript para aproveitar o sucesso inicial do Java, lançado no mesmo ano. Sua característica mais marcante é trazer interatividade a páginas HTML, sendo executada pelo browser, ou seja, operando do lado do cliente.

Com o passar do tempo e apesar das críticas de muitos programadores, o JavaScript evoluiu e hoje permite o controle sofisticado do conteúdo de páginas web, permitindo a troca de estilos, a ocultação/exibição de elementos, a realização de validações e outras operações mais complexas.

A partir de 2005, com o advento do AJAX (Asynchronous JavaScript and XML), ocorre seu renascimento do JavaScript, e surgem frameworks importantes como Prototype, JQuery, Dojo e Mootools, assim como esforço consistente para sua padronização (ECMAScript 3.1 a 5.1). Hoje o JavaSript é uma linguagem de programação amplamente utilizada.

Nashorn

O Nashorn é uma implementação de um mecanismo (ou motor) de execução para linguagem de programação JavaScript integrado a plataforma Java, disponível a partir da versão 8, compatível com a especificação ECMAScript 262 v5.1, que se beneficia da enorme quantidade de ferramentas e bibliotecas disponíveis para JavaScript, possibilitando sua integração com a plataforma Java e, portanto, conjugando as vantagens destes dois mundos.

Até a versão 7 do Java, o mecanismo de execução padrão do JavaScript disponível no Java era o Rhino, baseado num projeto de mesmo nome voltado para os navegadores Mozilla. Apenas como curiosidade, a palavra nashorn significa rinoceronte em alemão e também denominou um tanque de guerra conhecido como destruidor de tanques.

O mecanismo de execução Nashorn é acessível por meio da API javax.script e da ferramenta de linha de comando jjs, suportando a execução de scripts escritos em JavaScript embutidos em programas Java, incluindo aplicações JavaFX.

A sintaxe da linguagem JavaScript é bastante semelhante a do Java e o acesso a elementos Java a partir do JavaScript, como arrays e objetos, é praticamente idêntico, de modo que o Java tem acesso a valores e elementos do JavaScript e vice-versa.

Programa Java executa script JavaScript

O exemplo que segue (classe Nashorn01) mostra um programa de console Java que utiliza o Nashorn para executar um comando JavaScript.

import javax.script.*;

public class Nashorn01 {
    public static void main(String[] arg) {
        // Obtem acesso ao mecanismo de execucao JS Nashorn
        final ScriptEngine nashorn =
               new ScriptEngineManager().getEngineByName("nashorn");
        try {
            // Script JS pode ser hardcoded ou
            // obtido por outros meios (p.e., arquivo)
            String script = "print('Hello World!')";
            // Execucao do script JS
            nashorn.eval(script);
        } catch (final ScriptException se) {
            System.out.println(se);
        }
    }
}

Na classe Nashorn01, uma instância de ScriptEngineManager é criada para que, por meio de seu método getEngineByName(String) e o nome "nashorn", se possa obter uma instância do mecanismo de execução correspondente ao Nashorn. Com o objeto ScriptEngine obtido é possível executar-se diretivas JavaScript individuais ou scripts inteiros com uso do método eval(String). É necessário monitorar tal avaliação com um try/catch.

Retorno de valor do script JavaScript para Java

Neste outro exemplo (classe Nashhor02), o script é lido de um arquivo cujo nome é fornecido como argumento para o programa. O mecanismo de execução Nashorn é obtido da mesma maneira, mas sua utilização é um pouco diferente.

import java.io.*;
import javax.script.*;

public class Nashorn02 {
    public static void main(String[] arg) {
        // Script obtido do arquivo indicado como argumento
        StringBuffer script = new StringBuffer();       
        try (BufferedReader br =
                 new BufferedReader(new FileReader(arg[0]))) {
            String line = null;
            while ( (line=br.readLine()) != null) {
                script.append(line);
                script.append("\n");
            }
        } catch (Exception exc) {
            System.out.println("Erro: " + exc);
            System.exit(-1);
        }
        try {
            // Obtem acesso ao mec. execucao JS Nashorn
            final ScriptEngine nashorn =
             = new ScriptEngineManager().getEngineByName("nashorn");
            // Execucao do script JS
            Object returnValue = nashorn.eval(script.toString());
            System.out.println("Java: " + returnValue);
        } catch (final ScriptException se) {
            System.out.println(se);
        }
    }
}

O resultado do método eval(String) é atribuído a uma variável Java do tipo Object, de modo que é possível transferir um resultado do script Javascript para o programa Java. Basta para isso que a variável cujo valor deseja-se retornar seja indicada como uma diretiva ao final do script, como no script simples que segue, onde a variável f tem seu valor retornado para o programa Java.

// teste01.js
var c = 33;
print('JavaScript: ' + c);
var f = c*9.0/5.0 + 32.0;
print('JavaScript: ' + f);
f; // retorno de valor

O valor retornado pode ser convertido em outros tipos por meio de operações de coerção, no caso, como o resultado é do tipo double, poderia ser aplicado:
double resultValue = (Double) nashorn.eval(script.toString);

Passagem de valores entre programa Java e script JavaScript

Também é possível passar valores do programa Java para o script Javascript com o uso do método put(String, Object) disponível na classe ScriptEngine. que define uma variável com o valor indicado (ou global variable binding). O exemplo que segue transfere dois valores do programa Java para o script, que soma tais valores, retornando tal resultado para o programa, usando o método get(String).

import javax.script.*;

public class Nashorn03 {
    public static void main(String[] arg) {
        // Obtem acesso ao mecanismo de execucao JS Nashorn
        final ScriptEngine nashorn =
               new ScriptEngineManager().getEngineByName("nashorn");
        // Script JS hardcoded
        String script =
               "var c = a + 2*b;\nprint('JavaScript: ' + c);";
        try {
            // Transferência de valores: programa -> script
            nashorn.put("a", 123);
            nashorn.put("b", 0.456);
            // Execucao do script JS
            nashorn.eval(script);
            // Transferência dos resultados: script -> programa
            double result = (Double) nashorn.get("c");
            System.out.println("Java: " + result);
        } catch (final ScriptException se) {
            System.out.println(se);
        }
    }
}

Script JavaScript que utiliza classes Java

Outra possibilidade interessante decorrente da integração do mecanismo de execução Nashorn na JVM é o compartilhamento da API Java por parte dos script JavaScript. Um objeto Java pode ser instanciado de duas formas:
  • Instanciação direta:var lista = new java.util.ArrayList();
  • Instanciação indireta:var AL = java.util.ArrayList;
    var lista = new AL;



As duas formas produzem o mesmo efeito. O que muda é a quantidade de código digitado: a instanciação direta é mais verborrágica, portanto, mais longa, no entanto, clara e direta; a instanciação indireta permite instanciar objetos com menos código, embora possa ser menos clara conforme os apelidos usados para as classes efetivamente usadas.

O uso dos objetos é como no Java, tal como mostrado no exemplo que segue, onde um script JavaScript utilizar uma tabela de hashing, um array elástico e um buffer para String para manipular uma lista de linguagens de programação e seus inventores.

// teste02.js
var HM = java.util.HashMap; // instanciação indireta
var map = new HM();
var list = new java.util.ArrayList(); // instanciação diretas
var buffer = new java.lang.StringBuffer("|");

map.put("JavaScript","Brendan Eich");
map.put("Java","James Gosling");
map.put("C#","Anders Hejlsberg");
map.put("Pascal","Niklaus Wirth");

for (var key in map) { print('key: ' + key); list.add(key); }
for each (var value in map) {
    print('value: ' + value);
    buffer.append(value); buffer.append("|");
}
for each (var key in list)
    print('{ ' + key + ', ' + map.get(key) + ' }');

print(buffer.toString());
list // retorno de objeto


Este script, salvo sob o nome de teste02.js, é executado com uso da classe/exemplo Nashorn02, dada anteriormente.

Console JavaScript


Junto com o JDK 8 existe um console JavaScript (uma ferramenta de linha de comando) denominada jjs que provê acesso direto ao mecanismo de execução Nashorn, comportando-se como um REPL (Read-Evaluate-Print-Loop), ou seja, um console que lê comandos JavaScript, executando-os e imprimindo os resultados no próprio console, interativa e repetidamente, o que é muito conveniente para testar comandos, executar pequenas rotinas e testes, incluindo experimentar elementos Java e JavaScript sem a necessidade de escrever programas completos.

Para acioná-la basta digitar 'jjs' (considerando que a variável de ambiente path está corretamente configurada para incluir o diretório <java_install>/bin). Considere as várias sequências que seguem de comandos que podem ser digitados no jjs. A primeira define variáveis e exibe o resultado de uma expressão:

jjs> var a = 1.5;
jjs> var b = -3;
jjs> print (2*a - 0.5*b);
4.5

Agora a definição e a exibição de um array simples:

jjs> var array = [];
jjs> array[0] = 'Peter';
Peter
jjs> array[1] = 2017;
2017
jjs> array
Peter,2017
jjs> array.length  
2
jjs> for(var i=0; i<array.length; i++) print(array[i]);
Peter
2017
jjs> for each (var v in array) print(v);
Peter
2017

Para encerrar o jjs deve ser fornecido quit().
Objetos Java podem ser instanciados e utilizados, como segue:

jjs> var list = new java.util.ArrayList();
jjs> list.add('Peter');
true
jjs> list.add('Jandl');
true
jjs> list
[Peter, Jandl]
jjs> list.size();
2
jjs> var i = 10;
jjs> for each (var v in list) { print(i + ':' + v); i = i + 10; }
10:Peter
20:Jandl
30

Características avançadas do Java, como expressões lambda e streams também podem ser usadas (com alguns cuidados). Considerando o ArrayList list definido acima, poderia ser escrito:

jjs> list .stream() .filter(function(s) s.startsWith('J')) .forEach(function(s) print(s));
Jandl
null

Neste fragmento devem ser observados alguns aspectos:
  • onde é possível usar uma expressão lambdas no Java, emprega-se uma função JavaScript; de maneira que o lambda Java
    "s -> s.startsWith('J')"
    torna-se
    "function(s) s.startsWith('J')";
  • o valor 'Jandl' é o valor resultante da exibição dos elementos filtrados do stream obtido da coleção list;
  • como o método forEach(Consumer) é terminal, seu valor de retorno é void, assim sua avaliação pelo jjs retorna null.


O modo interativo do jjs é bastante útil para experiências e testes diversos. Mas não é conveniente quando desejamos executar (ou repetir) uma sequência conhecida (ou longa) de comandos. Um conjunto de comandos JavaScript, com ou sem uso de elementos do Java, pode ser salvo num arquivo de script, o qual pode ser executado pelo jjs em seu modo de interpretação (ou batch). Para executar o script teste02.js basta escrever:

> jjs –scripting teste02.js

Conclusões

Dada a importância atual do JavaScript, sua utilização integrada à plataforma Java é bastante interessante. Assim o Nashorn é bastante útil ao permitir que programas Java utilizem funcionalidades JavaScript; que programas JavaScript compartilhem elementos do Java; que o desenvolvedor disponha de um console JavaScript para testes diversos, execução de tarefas rápidas, prototipação, teste de características do Java e construção de scripts usando Java e JavaScript combinados.
Atualmente o Nashorn (Java 8) suporta completamente o padrão ECMAScript 5.1, além de algumas extensões. Pretende-se que com o Java 9 o Nashorn seja atualizado para suportar o ECMAScript 6. Tudo muito útil e conveniente.

Para Saber Mais