Linguagens dinâmicas apresentam várias vantagens sobre as linguagens compiladas. Uma dessas vantagens é a habilidade de adicionar código em tempo de execução com facilidade. Por exemplo, imagine um servidor que de alguma forma receba e processe comandos. Num ambiente Java (onde o servidor é um programa Java assim como os comandos são objetos Java) é um certo malabarismo com o Classloader para fazer isso.
http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=2
Enquanto que esse dinamismo é ainda “sustentável” para Classes, a situação é mais complicada para fragmentos de código. Nesse caso é preciso compilar uma classe Java e carregá-la. Com Javascript, a situação é outra. Uma vez que a linguagem é interpretada, é simplesmente trivial adicionar fragmentos de código. Para provar isso vou implementar um programa que imprime gráficos, para funções arbitrárias de uma variável x “real” (aliás, radicalmente arbitrárias... não irei nem mesmo validar se a entrada é de fato uma função).
Inicialmente definimos uma interface para a função. Não queremos que o código cliente seja impregnado com detalhes técnicos do funcionamento do javax.script. Queremos definir a interface mais simples possível sem comprometer o desempenho da aplicação.
public interface Function { double[] function(double xInit, double xfinal, double step); public double function(double x); }
Agora faremos teste com uma função bem conhecida x^2 (ou f(x) = x*x). Aqui o teste unitário da função:
/** * Test of function method, of class RhinoCalc. */ @Test public void functionXQuadrado() throws Exception { setFunction(); double xInit = 0.0; double xfinal = 10.0; double step = 1; double[] result = function.function(xInit, xfinal, step); assertTrue(Arrays.equals(new double[] { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 0 }, result)); for (int i = 0; i < result.length; i++) { System.out.println(result[i]); } }Para implementar a classe vou seguir os passos mais simples para adicionar o Javascript. Isso se resume a carregar o motor de scripts, adicionar a função e realizar a chamada necessária. Para carregar o motor de scripts:
public RhinoFunction() { ScriptEngineManager mgr = new ScriptEngineManager(); jsEngine = mgr.getEngineByName("JavaScript"); }Atribuir uma função arbitrária:
public void setFunction(String function) throws ScriptException { jsEngine.eval("function f(x) { \n" + " return " + function + ";}"); invocableEngine = (Invocable) jsEngine; }e invocar uma função:
public double function(double x) { try { return (Double) invocableEngine.invokeFunction("f", x); } catch (Exception e) { Logger.getLogger(RhinoFunction.class.getName()).log(Level.SEVERE, null, e); throw new RuntimeException(e); } } public double[] function(double xInit, double xfinal, double step) { double deltaX = xfinal - xInit; int size = (int) Math.ceil(deltaX / step); double[] result = new double[size + 1]; int i = 0; for (double x = xInit; x < xfinal; x += step, i++) { try { result[i] = (Double) invocableEngine.invokeFunction("f", x); } catch (Exception ex) { Logger.getLogger(RhinoFunction.class.getName()).log( Level.SEVERE, null, ex); throw new RuntimeException(ex); } } return result; }Agora um teste que exige mais desempenho. Podemos utilizar a classe para gerar gráficos de funções. Aqui um modelo que resume o funcionamento dessas classes: Ao pressionar o botão “plot” o seguinte método atualiza o modelo do plano cartesiano e solicita a atualização do painel.
private void plotButtonActionPerformed(java.awt.event.ActionEvent evt) { CartesianPanel painel = (CartesianPanel) this.graphicPanel; try { rhinoFunction.setFunction(polinomioTextField.getText()); } catch (ScriptException e) { e.printStackTrace(); JOptionPane .showMessageDialog( this, "O interpretador não conseguiu interpretar a função escrita.", "Função Inválida", JOptionPane.ERROR_MESSAGE); } painel.setFunction(rhinoFunction); double initialX = Double.parseDouble(initialXTextField.getText()); double finalX = Double.parseDouble(xFinalTextField.getText()); double initialY = Double.parseDouble(initialYTextField.getText()); double finalY = Double.parseDouble(finalYTextField.getText()); painel.getCartesianPlan().setDimension(initialX, finalX, initialY, finalY); painel.repaint(); }Eu adicionei alguns métodos de “zoom” com o mouse de forma que o programa tivesse mais dinamismo. Essas funcionalidades foram implementadas tanto no painel do desenho que sofre a ação como numa classe especialista “adapter” para o mouse. Isso é praticamente um completo programa de “plot” extramente simples e compacto que ilustra as facilidades do JFC e javax.script. Outras Considerações Claro que o exemplo poderia ser estendido para compreender outros conceitos e funcionalidades facilmente implementadas com JFC (como a possibilidade de impressão do gráfico), a habilidade de desenhar retângulos com a área selecionada, a possibilidade de arrastar o gráfico deslocando o ponto inicial, etc. Mas isso vai longe: uma longa caminhada do ArcTest para o Kplot... http://java.sun.com/applets/jdk/1.4/demo/applets/ArcTest/example1.html http://edu.kde.org/kmplot/
Comentários
1 - O q vc tem contra o <pre>?
2 - Nessa linha:
CartesianPanel painel = (CartesianPanel) this.graphicPanel;
Pq o cast?
3 - Tem algum easter egg? ;-)
...
Respondendo
1) Acho que o pre não fica bom, não sei. Queria que ficasse colorido como no eclipse.
2) Essa é fácil:
Se eu não usasse o cast não poderia fazer isso depois:
painel.setFunction(rhinoFunction);
3) Ainda não tenho easter egg. :(