
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. :(