capa dinamismo e elegância na parceria java & · pdf filepodemos programar melhor na...

12
40 www.mundoj.com.br 40 www.mundoj.com.br capa Alexandre Gazola ([email protected]): é bacharel em Ciência da Computação pela Universidade Federal de Viçosa (UFV) e mestre em Informática pela PUC- Rio. Trabalha como analista de Sistemas no BNDES, e possui as certificações SCJP, SCWCD e CSM. Alex Marques Campos ([email protected]): é engenheiro da Computação, formado pela Pontifícia Universidade Católica do Rio de Janeiro (PUC-Rio). Trabalha como analista de sistemas no BNDES. Aprenda como estender suas aplicações Java com a linguagem Ruby por meio da Java Scripting API Dinamismo e elegância na parceria Java & Ruby É fato que as linguagens dinâmicas estão ganhando cada vez mais popularidade no mundo do desenvolvimento de software. A maioria de nós já ouviu falar de Ruby, Python, Groovy e outras linguagens. Ao mesmo tempo, a linguagem Java começa a dar sinais de sua rigidez e caducidade para muitos tipos de tarefas. A tendência em que muitos apostam é a polarização de plataformas, ao invés de linguagens. Num primeiro passo nessa direção, foi criada a Java Scripting API, introduzida a partir do Java 6, cuja ideia é possibilitar a execução de código escrito em linguagens de scripting na plataforma Java. Neste artigo, discorreremos sobre a linguagem Ruby e a Java Scripting API, mostrando como combinar a robustez da plataforma Java com o poder e a flexibilidade oferecidos por uma linguagem dinâmica. es Campos gmail.com): é engenheiro da , formado pela Pontifícia Universidade

Upload: vuongdiep

Post on 28-Mar-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

40 www.mundoj.com.br40 www.mundoj.com.br

c a p a

Alexandre Gazola

([email protected]): é bacharel em

Ciência da Computação pela Universidade Federal

de Viçosa (UFV) e mestre em Informática pela PUC-

Rio. Trabalha como analista de Sistemas no BNDES,

e possui as certificações SCJP, SCWCD e CSM.

Alex Marques Campos

([email protected]): é engenheiro da

Computação, formado pela Pontifícia Universidade

Católica do Rio de Janeiro (PUC-Rio). Trabalha

como analista de sistemas no BNDES.

Aprenda como estender suas aplicações Java com a

linguagem Ruby por meio da Java Scripting API

Dinamismo e elegância na

parceria Java & Ruby

É fato que as linguagens dinâmicas estão ganhando cada vez mais popularidade no mundo do desenvolvimento de software. A maioria de nós já ouviu falar de Ruby, Python, Groovy e outras linguagens. Ao mesmo tempo, a linguagem Java começa a dar sinais de sua rigidez e caducidade para muitos tipos de tarefas. A tendência em que muitos apostam é a polarização de plataformas, ao invés de linguagens. Num primeiro passo nessa direção, foi criada a Java Scripting API, introduzida a partir do Java 6, cuja ideia é possibilitar a execução de código escrito em linguagens de scripting na plataforma Java. Neste artigo, discorreremos sobre a linguagem Ruby e a Java Scripting API, mostrando como combinar a robustez da plataforma Java com o poder e a flexibilidade oferecidos por uma linguagem dinâmica.

ues Campos

[email protected]): é engenheiro da

o, formado pela Pontifícia Universidade

Page 2: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

41

m seu clássico livro “The Pragmatic Programmer”, Andy Hunt e Dave Thomas aconselham todo programador a aprender

uma nova linguagem de programação por ano, principalmente pelo processo de “abertura de mente” que isso proporciona. Ao conhecermos outras linguagens e paradigmas, expandimos nossa maneira de pensar e podemos programar melhor na nossa própria linguagem. Mas essa não é a única motivação. Atualmente, têm-se percebido que a linguagem Java nem sempre é a melhor solução para endereçar todo tipo de problema com que os desenvolvedores modernos se deparam.

Neste cenário, linguagens dinâmicas (também conhecidas como lingua-gens de scripting), tais como Ruby, Python e Groovy, por exemplo, vêm ganhando cada vez mais espaço no mundo do desenvolvimento de software, por sua produtividade, flexibilidade e simplicidade. O próprio Java Community Process (JCP) reconheceu esse fato e aprovou a Java Specification Request (JSR) 223, que introduziu a Java Scripting API na linguagem Java, a partir de sua versão 6. Com isso, tem-se admitido que a linguagem Java em si vem dando sinais de caducidade e nem sempre tem se mostrado como a melhor ferramenta para todos os tipos de problemas. Não obstante, a plataforma Java (Java Virtual Machine e bibliotecas), por sua maturidade e robustez, continua com força total. Muitos apostam que, no futuro, a tendência é a de polarização de plataformas (plataforma Java e plataforma .NET são um exemplo) e não de linguagens. A ideia é que, com as plataformas multilinguagem, o desenvolvedor use a linguagem que melhor lhe convier para o problema em questão.

Neste artigo, apresentaremos os principais recursos da Java Scripting API por meio de exemplos na linguagem Ruby. Para que o leitor possa ter uma melhor compreensão dos exemplos (além de uma motivação para aplicar uma linguagem dinâmica num projeto Java), primeiramente faremos um breve resumo destacando as principais características da linguagem Ruby (essa introdução também é útil para os demais artigos de capa desta edição). Em seguida, entraremos na API de scripting do Java propriamente dita, explicando suas principais funcionalidades.

E

Ruby para desenvolvedores Java

Histórico e características gerais

Tipos básicos

Ruby foi criada em 1995 pelo japonês Yukihiro Matsumoto, também co-nhecido como “Matz”. É uma linguagem interpretada, multiplataforma, de tipagem dinâmica, totalmente orientada a objetos e com características de linguagens funcionais. Sua sintaxe foi inspirada na sintaxe das linguagens Smalltalk e Perl. Alguns dizem que Ruby segue o Princípio da Menor Sur-presa, que, em outras palavras, significa que programadores experientes nesta linguagem sabem exatamente o que esperar como resultado da execução do código que escrevem (quem já trabalhou com C++, por exemplo, sabe que esse nem sempre é o caso). Atualmente, as duas princi-pais implementações de Ruby compreendem: o MRI, interpretador oficial escrito pelo Matz, e o JRuby, implementação para a Java Virtual Machine patrocinada pela Sun Microsystems.

Nos últimos anos, o interesse pela linguagem Ruby cresceu vertigino-samente, principalmente devido ao surgimento do Ruby on Rails, um framework escrito em Ruby para desenvolvimento de aplicações web com elevada produtividade. O Rails foi criado em 2003 e rapidamente se

destacou no cenário de desenvolvimento de aplicações para a web, com seus princípios influenciando praticamente todos os novos frameworks que surgiram posteriormente. A popularidade do Rails contribuiu bastante para alavancar o interesse pela linguagem Ruby.

A seguir, daremos uma passada rápida em algumas das principais caracte-rísticas da linguagem Ruby. Falaremos sobre seus tipos básicos, orientação a objetos, herança e mixins, blocos, duck-typing e metaprogramação.

Os tipos básicos da linguagem Ruby são: números, booleanos, strings, símbolos, arrays, hashes, intervalos, expressões regulares. Todos os tipos básicos são objetos e possuem formas literais ou “built-in” na linguagem.

Números em Ruby são objetos da classe Fixnum, se forem inteiros, ou objetos da classe Float, se forem ponto-flutuante (isso mesmo, números em Ruby são objetos! Na verdade, tudo em Ruby são objetos, por isso diz-se que Ruby é puramente orientada a objetos). Quando fazemos x = 123, criamos o objeto 123 da classe Fixnum e atribuindo-o à variável x. Podemos, inclusive, chamar métodos em objetos do tipo Fixnum, por exemplo, -123.abs() retorna o valor absoluto de um número, neste caso, 123. Todos os “operadores” na verdade são métodos que os objetos das classes de número definem. Quando escrevemos 2 + 3, é o mesmo que escrevermos 2.+(3). As tabelas 1, 2 e 3 resumem os principais operadores usados na linguagem Ruby.

Note que Ruby não tem os operadores de incremento e decremento unários (++ e --).

Os booleanos literais true e false em Ruby são objetos das classes True-Class e FalseClass, respectivamente. Na verdade, todo objeto em Ruby pode ser avaliado em uma expressão booleana (ou seja, podem ser usa-dos como expressões em uma instrução if, por exemplo). Neste contexto, apenas false e nil (este tem a ideia do null do Java, mas com uma grande diferença: nil é um objeto da classe NilClass!) são avaliados para false. Os outros valores são considerados true em Ruby, inclusive o número 0 (neste ponto, o leitor que é acostumado com C/C++ deve ter cuidado!).

Strings em Ruby são objetos da classe String. Entre as possibilidades, a linguagem oferece dois tipos de literais para strings: com aspas simples (ex.: ‘hello world’) ou com aspas duplas (ex.: “hello world”). A diferença entre as duas formas é a quantidade de processamento que o Ruby realiza. Em strings com aspas simples, quase não há processamento, diferentemente do que ocorre com strings definidas com aspas duplas. Em especial, podemos fazer interpolação em strings com aspas duplas, usando a sequência #{expressão qualquer em Ruby}. Por exemplo, su-ponha que tenhamos em nosso programa uma variável nome cujo valor seja “José”. Então, uma string com o valor “Olá, Sr. #{nome}”, seria avaliada para “Olá, Sr. José”. A API da classe String provê diversos métodos para manipulação de strings, tais como capitalize, gsup, downcase, chomp, reverse etc. Strings em Ruby geralmente são objetos imutáveis (a menos que se faça uso dos métodos que terminam com o caracter !, os quais alteram o estado do próprio objeto string).

De difícil entendimento para muitos novatos na linguagem Ruby são os símbolos, objetos da classe Symbol. Um símbolo em Ruby é uma cadeia

Page 3: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

42 www.mundoj.com.br

Operador Matemático Descrição

+ Soma.

- Subtração.

* Multiplicação.

/ Divisão.

% Módulo (resto da divisão).

** Exponenciação. (xy == x ** y).

Operador de bit Descrição

& Conjunção (and).

| Disjunção (or).

^ Disjunção exclusiva (xor).

~ Negação.

<< Shift binário para esquerda.

>> Shift binário para direita.

Operador Condicional Descrição

== Igualdade. Retorna true se os operandos fo-rem iguais e false caso contrário. (também pode ser expresso pelo predicado “.eql?”, como em “x.eql? y”).

!= Desigualdade.

> Maior que.

>= Maior ou igual a.

< Menor que.

<= Menor ou igual a.

<=> Operador de comparação. Retorna 0 se os operandos forem iguais, 1 se o primeiro operando for maior do que o segundo e -1 se o primeiro operando for menor do que o segundo.

de caracteres usada como nome, ou identificador, para alguma coisa. Um símbolo é construído prefixando-se uma string com : (dois pontos). Exemplos de construções de símbolos incluem: :teste, :”uma_variavel_qualquer”, :’meu_programa’. Símbolos são objetos imutáveis, e um nome ou string usado para gerar um símbolo sempre dará origem ao mesmo símbolo. Mais a frente, veremos como os símbolos são geralmente utili-zados num programa Ruby.

Ruby possui duas classes básicas para coleções: Arrays e Hashes. Para criar um array usando a sintaxe literal basta colocar uma lista de obje-tos separados por vírgula entre colchetes, por exemplo: meu_array = [1.9999, “programa”, x]. O interessante é que os arrays de Ruby podem ser heterogêneos, ou seja, conter quaisquer tipos de objetos misturados. Para referenciarmos objetos presentes num array, usamos o nome do array seguido do índice entre colchetes. Por exemplo, meu_array[1] retorna a string “programa” no array citado anteriormente (assim como em Java, os índices de arrays em Ruby começam em 0).

Os hashes (objetos da classe Hash) são bastante parecidos com os arrays. Um hash é uma coleção em que associamos um objeto qualquer (cha-mado chave) a outro objeto (chamado valor). A diferença para os arrays é que nos arrays as chaves são sempre inteiras. Os hashes de Ruby são muito parecidos com os Maps do Java; só que Ruby tem sintaxe nativa literal para a criação destes. Para criar um hash usando a sintaxe literal, colocamos uma lista de par => valor separados por vírgula entre chaves. Por exemplo, meu_hash = {x => “teste”, 3 => outra_coisa, “cores” => [“vermelho”, “azul”, “verde”] }. Para aumentar a legibilidade do código, Ruby permite que, quando hashes literais são o último argumento passado para um método, então as chaves tornam-se opcionais. Hashes também são coleções heterogêneas, podendo ter seus pares chave/valor

sendo de diversos tipos. Para referenciar um valor de um hash, usamos o nome do hash seguido da chave do hash entre colchetes, por exemplo, meu_hash[x] retorna a string “teste” no exemplo de hash citado anterior-mente. Tanto arrays quanto hashes são coleções dinâmicas cujo número de objetos pode crescer ou diminuir indefinidamente. O interpretador é responsável por realizar o gerenciamento da memória, além de acionar o mecanismo de coleta de lixo.

Outro tipo “built-in” de Ruby, influência forte proveniente da linguagem Perl, são as expressões regulares, objetos da classe Regexp. Uma expres-são regular é constituída por um padrão, o qual é utilizado para fazer um match (casamento) contra uma string. A sintaxe literal de Ruby para criação de uma expressão regular é por meio da escrita do padrão entre barras, algo do tipo /padrão/. Para “casar” uma string contra um padrão, podemos usar o método =~, o qual retorna a posição inicial onde ocorreu o match ou retorna nil se o padrão não “casa” com a string. Por exemplo, se fizermos “Ruby é Legal” =~ /Legal/, teríamos como retorno 7.

Por fim, temos os ranges (intervalos), objetos da classe Range. Um range representa um intervalo, ou seja, um conjunto de valores compreendidos entre um valor inicial e um valor final. A sintaxe literal para construção de ranges é: inicio..fim (dois pontos) ou inicio...fim (três pontos). A primeira sintaxe indica que o intervalo vai de inicio até fim, incluindo fim. A sin-taxe com três pontos indica que o intervalo vai de início até fim, mas fim não está incluído. Exemplos de ranges incluem -10..10 e ‘a’...’f’.

A Listagem 1 traz alguns exemplos que resumem os principais tipos bá-sicos de Ruby. Repare que as sentenças da linguagem não precisam ter-minar com ; (ponto-e-vírgula) e que comentários de linha em Ruby são denotados pelo caracter #. Note também que não precisamos ter uma

Page 4: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

43

classe com um método main() para executar código Ruby. Isso ocorre porque, ao iniciar uma aplicação Ruby, o interpretador automaticamente cria um objeto da classe Object chamado “main”, que é o contexto onde as linhas de código “soltas” executam.

coisas = [1, &apos;abacaxi&apos;, :simbolo_qualquer, [&apos;a&apos;, &apos;e&apos;, &apos;i&apos;, &apos;o&apos;, &apos;u&apos;]] # um arrayputs coisas # imprime o array no console

x = coisas[1] =~ /xi/ # matching de &apos;abacaxi&apos; com a expressão regular /xi/puts x # imprime 5 no console

mapeamento = {“dolar” => 2.02, 12 => :dezembro, :pi => 3.1415} # um hashputs mapeamento # imprime o hash puts mapeamento[:pi] #imprime 3.1415

Listagem 1. Alguns tipos básicos do Ruby.

Comandos de controle de fluxo condicionais

Orientação a objetos

Como a maioria das linguagens condicionais, Ruby possui comandos condicionais. O principal deles é o if, que possui a seguinte sintaxe:

if expressao_condicional_qualquer then

# código a ser executado se verdadeiro

end

while expressão_condicional_qualquer then

# código a ser executado enquanto a ex-pressão continuar sendo verdadeira

end

A palavra-chave then é opcional. Ruby também possui a estrutura de comando alternativo com else e elsif (else if ), além dos comandos unless (equivalente ao if negado) e case (similar ao switch do Java). O comando if (e o unless) possui uma estrutura alternativa chamada de modifica-dor if. Por sua elevada legibilidade, essa forma é bastante utilizada por programadores Ruby para escrever ifs com apenas uma instrução a ser executada. A sintaxe é a seguinte: código a ser executado se verdadeiro if expressao_condicional_qualquer (ex.: puts “OK!” if x > 4).

Outra construção importante em uma linguagem de programação são os loops. Ruby possui algumas estruturas de loop. Entre elas, o while e o until. A sintaxe do while é a seguinte:

A palavra-chave then também é opcional e a semântica deve ser de sim-

ples entendimento por qualquer programador Java. O loop com a palavra-

chave until é análogo em sintaxe (substituindo-se apenas a palavra-chave

while por until), diferindo em semântica apenas no fato de que o loop é

executado enquanto a condição for falsa. Ruby também possui um açúcar

sintático com for, mas o uso de métodos iteradores com blocos é a maneira idiomática usual de se percorrer coleções em Ruby. Mais a frente, na seção referente a blocos, será mencionada essa característica.

Como mencionamos, Ruby é uma autêntica linguagem orientada a ob-jetos. Tudo que manipulamos são objetos. Objetos em Ruby, assim como em Java e na maioria das linguagens OO, são especificados por meio de classes, entidades dotadas de métodos e variáveis de instância. Classes são definidas por meio da palavra reservada class, seguida pelo nome da classe. Nas linhas seguintes temos o corpo da classe, e fechamos a definição da classe com a palavra reservada end.

Variáveis de instância, ou atributos, de uma classe começam com o carac-ter @. Métodos em Ruby são definidos por meio da palavra reservada def seguida do nome do método, seus argumentos separados por vírgula (se houver) e sua definição e encerramento com end. Não se especifica tipo de retorno de um método, pois Ruby possui tipagem dinâmica e inferida (estudaremos essas características adiante). Além disso, todo método em Ruby possui valor de retorno, seja explicitamente, usando uma instrução return, ou implicitamente, o que neste caso corresponde à avaliação da última instrução executada pelo método. Os parênteses envolvendo os argumentos do método são opcionais em Ruby (exceto nos casos em que possa haver ambiguidade), tanto na definição de um método quan-to na chamada a um método. A convenção que alguns programadores adotam é manter os parênteses na definição do método e omiti-los nas chamadas. Vale lembrar que Ruby não possui sobrecarga de métodos, ou seja, não é permitido declarar dois métodos com o mesmo nome numa mesma classe. Isso geralmente não representa um problema, visto que se pode conseguir o mesmo efeito usando outras características da linguagem (por exemplo, especificando valores default para parâmetros do método).

Aproveitando que tocamos no assunto de convenções usadas na progra-mação em Ruby, vejamos algumas das principais:

-lavras que formam o nome (mesma convenção adotada em Java); já os nomes compostos de métodos e variáveis são separados por sublinhado (ex.: find_all da classe String);

também são objetos) são escritos com um caracter ? (interrogação) ao final. Por exemplo, para saber se um array está vazio, chamamos o método empty? (a “?” faz parte do nome do método);

métodos que alteram o estado de um objeto são escritos com um ca-ractere “!” (interrogação) ao final. Por exemplo, a classe String oferece muitos métodos em duas versões. Temos capitalize() e capitalize!(): o primeiro retorna uma nova string com a primeira letra em maiúsculo, e o último faz a mesma coisa só que alterando a própria string.

A Listagem 2 ilustra a definição de uma classe em Ruby. Nesta classe, temos a presença do método especial initialize(), que faz o papel do construtor do objeto Aviao. Neste método, fazemos a criação e iniciali-zação da variável de instância @numero_de_passageiros. Como Ruby é uma linguagem dinamicamente tipada, cujos tipos não são declarados (tipagem inferida), uma variável existe a partir do momento em que atribuímos a ela algum valor. No exemplo, também definimos por meio do attr_accessor que a variável @numero_de_passageiros será acessível

Page 5: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

44 www.mundoj.com.br

e modificável de fora da classe (sintaxe: attr_accessor :nome_do_atribu-to1, nome_do_atributo2, ...), em outras palavras, provemos algo análogo a um getter e um setter para a variável de instância. Existem também outras possibilidades, como attr_reader e attr_writer.

class Aviao

attr_accessor :numero_de_passageiros

def initialize() puts “Ligando os motores do avião...” @numero_de_passageiros = 0 end

def embarcar() @numero_de_passageiros += 1 end

def decolar() puts “Preparando para decolagem... Total de passageiros: #{@nume-ro_de_passageiros}” end end

# testesaviao = Aviao.newaviao.embarcaraviao.decolar

module Log def log(msg) puts “LOG: #{msg}” endend

class Sistema include Log

def executar() log(“executando...”) endend

Listagem 2. Definindo uma classe em Ruby.

Listagem 3. Uso de módulos e mixins.

Ruby possui a poderosa característica de classes abertas. Basicamente, isso significa que é possível, a qualquer momento, modificarmos uma classe qualquer já carregada, adicionando novos métodos, atributos etc., inclusive as classes da biblioteca padrão de Ruby. Para isso, basta usarmos a sintaxe de declaração de classe class NomeDaClasse, acrescentarmos o que quisermos à classe, e fecharmos com end. A partir deste instante, as alterações são aplicadas em todas as instâncias da referida classe. O Ruby on Rails faz uso extensivo deste recurso, permitindo que possamos escre-ver coisas, como, por exemplo, 5.days.from_now, 5.hours.since(3.hours.ago) (isso implica abrir a classe Fixnum, incluindo esses novos métodos). Essa característica de Ruby permite o que se chama de “Monkey Patch”, o processo de se modificar ou estender código em tempo de execução sem alterar o código-fonte original.

Herança e mixins

Tipagem dinâmica

Blocos

Ruby possui suporte à herança simples, o que é denotado seguindo o nome da classe que definimos pelo caracter < (menor) e o nome da clas-se da qual estamos herdando, da seguinte maneira: class NomeDaClasse < NomeDaClasseMae. Assim com em Java, todas as classes em Ruby são implicitamente filhas de uma classe Object.

Além de classes, Ruby possui o conceito de módulos. Módulos possibi-litam o agrupamento conjunto de métodos, classes e constantes. Além de servirem como namespace, evitando a colisão de nomes, os módulos são interessantes pela funcionalidade de mixin (mistura), a maneira al-ternativa à herança múltipla implementada em Ruby. A Listagem 3 traz um exemplo que ilustra o uso de módulos. Definimos no módulo Log um método de instância chamado log. Na classe Sistema, então, incluímos (ou misturamos) o módulo Log utilizando a palavra reservada include

depois da definição da classe. A partir desse momento, todos os métodos de instância definidos em Log fazem parte da classe Sistema, como se es-tivessem sido definidos nesta. Uma classe pode incluir diversos módulos.

Ruby implementa o interessante conceito de blocos, também conheci-dos como closures em linguagens como LISP. Um bloco é um conjunto de comandos e expressões entre chaves (ou entre um par do/end) que pode ser associado a um método (deve aparecer imediatamente após a chamada do método). O bloco pode receber argumentos, os quais são especificados por meio de uma lista entre barras verticais. Internamente, o método pode decidir invocar o bloco de código passado para ele, isto é feito usando-se a palavra reservada yield. Na prática, é como se o bloco de código fosse um argumento implícito passado para um método. Ruby faz uso extensivo de blocos nas classes de sua biblioteca padrão, par-ticularmente em coleções. Para percorrer um array em Ruby, podemos chamar o método each, passando o bloco de código a ser executado em cada iteração, por exemplo:

estacoes = [:primavera, :verao, :outono, :inverno] # array com as estações do ano

estacoes.each { |estacao| puts estacao } # imprime cada estação do ano

O interessante é que o bloco mantém o contexto no qual foi definido, isto é, o ambiente ao redor (valor das variáveis usadas na definição do bloco) é todo capturado no bloco (daí o termo closure, “fechamento” em inglês) e está disponível quando da invocação do bloco pelo método ao qual foi associado. Se necessário, podemos tratar blocos como objetos, por meio da classe Proc.

Uma das características mais relevantes ao estudarmos uma nova lin-guagem é o seu modelo de tipagem. Dentro de um modelo de tipagem, podemos considerar pelo menos três aspectos:

refere-se ao rigor com que os tipos são tratados na linguagem. Se a tipagem for forte, então a linguagem se encarregará de fazer verificações de tipos e tomará providências se o programa ferir regras de tipagem, seja sinali-

Page 6: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

45

#um método cliente qualquer que usa um tradutordef fazer_traducao(tradutor) tradutor.traduzir(“bola”)end

# implementação de dois tradutores

class TradutorIngles def traduzir(palavra) puts “Translating the word \”#{palavra}\” to English...” endend

class TradutorEspanhol def traduzir(palavra) puts “Traduciendo la palabra \”#{palavra}\” al Español...” endend

Listagem 4. Ilustrando o Duck Typing.

Metaprogramação, reflexão e DSLs

zando erros em tempo de compilação ou exceções em tempo de execução. Já uma linguagem de tipagem fraca é mais relaxada no que tange à manipulação de tipos, permitindo que o programador misture tipos de dados mais livremente (quem já programou com a linguagem C sabe que coisas bizarras podem acontecer numa lin-guagem de tipagem fraca);

refere-se a quando um modelo de tipagem é definido. Se a tipagem for de tipagem estáti-ca, então os tipos são determinados em tempo de compilação; se a linguagem for de tipagem dinâmica, então os tipos são determina-dos em tempo de execução;

refere-se à maneira como uma linguagem determina o tipo de um objeto. Se a lingua-gem for de tipagem explícita, então devemos declarar cada vari-ável com seu devido tipo. Já uma linguagem de tipagem inferida determina o tipo de um objeto com base nas regras da linguagem e na estrutura do programa.

Considerando essas características, podemos dizer que Java é uma lin-guagem de tipagem forte, estática e explícita, ao passo que Ruby é uma linguagem de tipagem forte, dinâmica e inferida. Agora, vamos analisar mais de perto as características de tipagem de Ruby.

Suponha que queremos montar um sistema de tradução para diversos idiomas. Pensando de forma bem simplificada, em Java, poderíamos defi-nir uma interface Tradutor, com o seguinte método: String traduzir(String palavra). Cada tradutor, então, implementaria esta interface traduzindo a palavra passada como argumento para o idioma correspondente. Numa linguagem estaticamente tipada como Java, um Tipo é definido explici-tamente por meio de uma interface ou de uma classe. Em Ruby, as coisas funcionam de forma um pouco diferente, conforme mostra a Listagem 4.

O método cliente fazer_traducao() recebe como parâmetro um objeto

qualquer e, para este método, tudo o que é necessário é que o objeto

passado como parâmetro possua um método traduzir(). Em outras pala-

vras, o método fazer_traducao() assume que os objetos que serão recebi-

dos como parâmetro sabem responder à mensagem traduzir(). Por isso,

“as interfaces”, ou tipos, dos objetos Ruby são definidos pelos conjuntos

de mensagens que os objetos sabem responder. No exemplo dado, se

um objeto sabe responder à mensagem traduzir(), então, para todos os efeitos, ele é um “tradutor”. Daí veio o termo “Duck Typing” (tipagem de “pato”): “If it walks like a duck, and quacks like a duck, then it is a duck” (se um determinado objeto anda como um pato, e “grasne” como um pato, então ele é um pato”).

Para mais informações sobre tipagem de linguagens de programação, além de uma comparação dos modelos de Java e Ruby, recomendamos o ótimo artigo de Bruce Tate, da série Crossing Borders da IBM: “Typing strategies beyond the java model” (ver referências).

Não poderíamos deixar de concluir esta brevíssima discussão sobre a lin-guagem Ruby sem dar “uma pincelada” no rico tema da metaprograma-ção e reflexão, pontos fortes das linguagens dinâmicas, e, em especial, de Ruby. Mas, afinal de contas, o que é metaprogramação? Nada mais é do que código que gera código, sendo ambos executados dinamicamente. É um poderoso recurso para eliminação de código duplicado. Podemos dinamicamente definir classes, métodos em uma classe etc. Podemos fazer com que o interpretador avalie dinamicamente qualquer trecho de código Ruby fazendo-se uma chamada ao método eval(), por exemplo: eval(“puts ‘Alô Mundo!’) faz com que a string ‘Alô Mundo!’ seja impressa. Ruby é uma linguagem intensamente reflexiva, ou seja, é possível exa-minar informações do ambiente de execução. Por exemplo, podemos saber se um determinado identificador está sendo usado (defined?), se um determinado objeto responde a um método (respond_to?), se um objeto é de determinada classe (is_a?), percorrer a hierarquia de classes de um objeto etc.

Ruby possui alguns “ganchos” (do inglês, hook), que são pontos que podem ser interceptados pelo programador para realizar alguma ação. Um dos ganchos mais interessantes da linguagem é o method_missing (método não encontrado), utilizado (assim como vários outros recursos de metaprogramação) pelo framework Ruby on Rails em seus Active Records. Quando um método que não existe é invocado num objeto, Ruby busca por um método default chamado method_missing(). Se este método tiver sido definido, o interpretador executa esse método, passando para ele o nome do método não encontrado, juntamente com seus parâmetros (se o método não for encontrado, é lançada a exceção NoMethodError).

O dinamismo da linguagem, sintaxe enxuta, características funcionais, aliadas à metaprogramação e reflexão, fazem de Ruby uma excelente lin-guagem hospedeira para criação de linguagens específicas de domínio

de maneira bastante peculiar para lidar com um domínio de aplicação bem definido. Juntamente com Domain-Driven Design, as DSLs estão se consolidando como excelentes ferramentas para reduzir os ruídos tecnológicos nas soluções de software, fazendo com que os sistemas construídos espelhem de maneira mais fidedigna as abstrações existen-tes no mundo real.

Nosso objetivo com esta pequena discussão da linguagem Ruby foi dar ao leitor uma curta perspectiva das características de uma típica lingua-gem dinâmica e o que ela pode nos oferecer. Agora, veremos como unir o útil ao agradável, combinando toda a robustez do Java com a elegância e flexibilidade da linguagem Ruby.

Page 7: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

46 www.mundoj.com.br

A interface de programação (API) para scripting foi adicionada à lin-guagem Java em dezembro de 2006 e é parte integrante da versão 6 do Java Development Kit (JDK). Foi proposta na JSR-223 e seu objetivo é uniformizar o uso de linguagens de scripting na plataforma Java, de maneira a permitir aos desenvolvedores conhecer apenas um pequeno conjunto de classes e interfaces para integrar linguagens dinâmicas às suas aplicações.

Principais classes e interfaces

Verificando engines de script instaladas

Obtendo um ScriptEngine

A API de scripting está contida no pacote “javax.script” e as principais interfaces e classes desta API podem ser visualizadas na figura 1.

ScriptEngineManager <<interface>>

ScriptEngine

<<interface>>

ScriptEngineFactory

javax.script

<<interface>>

Invocable

<<interface>>

ScriptContext

<<interface>>

Bindings

Figura 1. Principais classes e interfaces da API de scripting.

A tabela 4 contém um resumo com o papel das principais classes e in-terfaces da API.

Abstração Representa

javax.script.ScriptEngine Interpretador de linguagem de scripting.

javax.script.ScriptEngine-Factory

Fábrica de interpretadores para uma linguagem de scripting específica.

javax.script.ScriptEngine-Manager

Gerente (ou repositório) de fábricas de interpretadores de linguagens de scripting disponíveis.

javax.script.Invocable Interface opcional que permite a execução de procedimentos de scripts que já tenham sido avaliados.

javax.script.Bindings Mapeamento de vínculos entre no-mes e objetos Java. O mapeamento de vínculos serve para permitir que linguagens de scripting consigam acessar objetos criados por código Java.

javax.script.ScriptContext Contexto de execução de um dado interpretador de linguagem. Agrega os Bindings, o leitor da entrada pa-drão e os escritores da saída padrão e de erro.

Através da classe ScriptEngineManager, podemos obter uma instância de ScriptEngine para uma dada linguagem de scripting. Isso é feito informan-do um dos nomes da engine (motor, código que realiza o trabalho de exe-cutar o script) ou da linguagem de scripting, uma das extensões de arquivo utilizadas nos arquivos de scripting ou um dos mime types associados ao script. Na tabela 5 há um resumo dos métodos de fábrica disponíveis para obter um ScriptEngine a partir de um ScriptEngineManager.

ScriptEngine getEngineByName(String shortNa-

me)

Nome da engine ou um nome relacionado com o

script.

ScriptEngine getEngineByExtension(String

extension)

Uma extensão de arquivo relacionada com o script.

ScriptEngine getEngineByMimeType(String

mimeType)

Um mime type associado ao script.

Outra maneira de obter um ScriptEngine a partir da classe ScriptEngi-neManager é procurando, na lista de ScriptEngineFactory disponíveis, uma fábrica capaz de criar uma instância de interpretador para a lin-guagem de scripting que desejamos.

Para listar os scripts suportados em uma dada instalação de Java, pode-mos utilizar o código da Listagem 5.

public class ListadorFabricasEngineScript {

public void listarFabricasMotorScript() {

ScriptEngineManager gerenteFabricas = new ScriptEngineManager();

for (ScriptEngineFactory fabrica : gerenteFabricas.getEngineFactories()) {

System.out.println(“Nomes: “ + fabrica.getNames());

System.out.println(“Extensoes: “ + fabrica.getExtensions());

System.out.println(“Mime Types: “ + fabrica.getMimeTypes());

}

}

public static void main(String[] args) {

new ListadorFabricasMotorScript().listarFabricasEngineScript();

}

}

Listagem 5. Listando fábricas de motor de script suportados.

Java 6 já vem com suporte para JavaScript, através da engine Rhino. Ao executar o código da Listagem 5 em uma instalação básica de Java 6, deverá ser impresso no console algo como:

Page 8: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

47

Executando scripts

public class ChamadorJRuby {

public void executar() throws MissingRequiredScriptEngineException,

ScriptException {

String linguagem = “jruby”; //poderia ser “ruby” também.

ScriptEngineManager gerente = new ScriptEngineManager();

ScriptEngine motor = gerente.getEngineByName(linguagem);

if (motor == null) {

// Aqui tratamos o caso de não existir um

// ScriptEngine disponível para a linguagem.

throw new MissingRequiredScriptEngineException(linguagem);

}

motor.eval(“puts(&apos;Olá, mundo Ruby!&apos;)”);

}

public static void main(String[] args) {

try {

new ChamadorJRuby().executar();

} catch (MissingRequiredScriptEngineException e) {

e.printStackTrace();

} catch (ScriptException e) {

e.printStackTrace();

}

}

}

Listagem 6. “Olá, mundo Ruby!”

Nomes: [js, rhino, JavaScript, javascript, ECMAScript, ecmascript]

Extensoes: [js]

Mime Types: [application/javascript, ap-plication/ecmascript, text/javascript, text/ecmascript]

Nomes: [jruby, ruby]

Extensoes: [rb]

Mime Types: []

Se as bibliotecas que contêm a engine e o interpretador JRuby também estiverem no classpath (jruby.jar e jruby-engine.jar), também deverão ser impressas as seguintes linhas no console:

Para executar um script a partir da linguagem Java, a interface ScriptEn-gine define os seis métodos de nome eval(), mostrados na tabela 6.

Método Descrição.

Object eval(Reader reader) throws ScriptException

Executa o script lido através de “reader”.

Object eval(Reader reader, Bindings n) throws ScriptEx-ception

Executa o script lido através de “reader”, usando “n” como mapa de objetos vinculados ao escopo do motor (ENGINE_SCOPE).

Object eval(Reader reader, ScriptContext context) thro-ws ScriptException

Executa o script lido através de “reader”, usando “context” como contexto de execução.

Object eval(String script) throws ScriptException

Executa o script definido na string “script”.

Object eval(String script, Bindings n) throws ScriptEx-ception

Executa o script definido na string “script”, usando “n” como mapa de objetos vinculados ao escopo do motor (ENGINE_SCOPE).

Object eval(String script, ScriptContext context) thro-ws ScriptException

Executa o script definido na string “script”, usando “context” como contexto de execução.

A família de métodos eval() serve para executar scripts. Pode-se executar scripts que estejam definidos em strings ou acessíveis por um Reader. Por enquanto, não se preocupe com as abstrações Bindings e ScriptContext, pois estas serão explicadas mais adiante no artigo.

Na Listagem 6, é mostrado um programa que obtém uma instância de engine para a linguagem Ruby. O programa executa um trecho de códi-go Ruby, definido em uma string, o qual imprime “Olá, mundo Ruby!” no console. A exceção MissingRequiredScriptEngineException é uma classe simples criada por nós (herda diretamente de Exception) e é usada para demonstrar a importância de tratar o caso de não existir uma engine de script para a linguagem que desejamos. O código de MissingRequiredS-criptEngineException não será mostrado neste artigo.

O código da Listagem 6 mostra um padrão recorrente quando programa-mos para a API de scripting:1. obter uma instância de ScriptEngineManager;2. obter, do ScriptEngineManager, uma instância de ScriptEngine da

linguagem de scripting desejada;3. utilizar um dos métodos eval() de ScriptEngine para executar o có-

digo do script.

Utilizar um Reader para executar um script também é simples. Para adicionar um tempero ao nosso exemplo, vamos implementar uma cal-culadora de descontos sobre preços para uma loja. Imagine que essa loja divide os descontos em várias categorias e que o valor desses descontos costuma mudar frequentemente. Para capturar a natureza dinâmica dessas mudanças, os descontos serão calculados num script Ruby. A Lis-tagem 7 mostra o código Ruby, e a Listagem 8, o código da calculadora de descontos. As abstrações para moedas e valores monetários podem ser vistas nas Listagens 9, 10 e 11.

Quando executado, o programa da Listagem 8 imprime no console o seguinte:

Page 9: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

48 www.mundoj.com.br

Preço de etiqueta : R$ 100,00

Preço à vista : R$ 95,00

Preço cliente plus: R$ 90,00

Preço 4x : R$ 100,00

Preço 7x : R$ 104,00

Preço 12x : R$ 106,00

... pela especificação da API de scripting, os fornecedores de engines de script não são obrigados a usar o mesmo nome de vínculo fornecido pelo usuário (mas, em geral, usam quando possível). Em JRuby, por exemplo, os nomes vinculados através da API são adicionados de um “$” como prefixo, para denotar o escopo global do nome (variáveis globais em Ruby começam com $). Isso quer dizer que um vínculo registrado como “nome_do_vinculo” a partir de Java estará visível em Ruby como “$nome_do_vinculo”.

Bindings vinculos = engine.createBindings();

vinculos.put("valor_de_pi", 3.141592);

engine.eval("puts($valor_de_pi)", vinculos);

engine.put("valor_de_pi", 3.141592);

engine.eval("puts($valor_de_pi)");

Na classe CalculadoraDeDescontos, assumimos que a engine de execução de scripts mantém estado. Ou seja, assumimos que os métodos definidos em regras_desconto.rb, e interpretadas quando a CalculadoraDeDescon-tos é construída, estão disponíveis em chamadas subsequentes de eval(). Manter estado entre chamadas de eval() não é parte da especificação JSR-223, mas algumas implementações, como a de JavaScript (Rhino), a de Ruby (JRuby), entre outras, possuem essa característica.

No método calcularPrecoParcelamento() da classe CalculadoraDeDes-contos, mostramos uma maneira alternativa de realizar a chamada para uma função em um script: a interface javax.script.Invocable. Novamente, a especificação JSR-223 não obriga que os ScriptEngines implementem esta interface, mas a engine de JRuby e de JavaScript (Rhino) o fazem. Para manter a consistência, mostramos como realizar o mesmo trabalho utilizando o método eval(), caso o ScriptEngine não implemente a inter-face Invocable.

O leitor pode indagar, com razão, que um arquivo de properties poderia representar os valores dos descontos para pagamento à vista e para clien-tes “plus”. A vantagem do script, nesse caso, é que decisões mais com-plicadas, como a do método obter_juros_para_parcelamento(numero_de_parcelas), podem ser expressas mais facilmente.

# arquivo: regras_desconto.rb# neste arquivo são definidas as funções de desconto.

#obtém desconto para pagamento a vistadef obter_desconto_para_pagamento_a_vista() return 0.05 # 5%end

#obtém desconto para clientes especiaisdef obter_desconto_para_cliente_plus() return 0.10 # 10%end

#obtém os juros para pagamento parceladodef obter_juros_para_parcelamento(numero_de_parcelas) if numero_de_parcelas < 5 return 0.0 elsif numero_de_parcelas < 8 return 0.04 else return 0.06 endend

Listagem 7. Código Ruby com métodos de desconto.

Binding

O conceito de binding (ato de vincular), no contexto da JSR-223, consiste em associar um nome a um objeto Java. O processo de binding torna visível o objeto Java para a linguagem de scripting através do nome associado. Vimos, antes, que alguns dos métodos da família de métodos “eval()” aceitam um objeto do tipo Bindings. Essa é uma das maneiras de estabelecer os vínculos disponíveis ao script. Outra maneira é utilizar o método “void put(String key, Object value)” para manipular os bindings de um ScriptEngine diretamente.

A interface Bindings define mapas de vínculos entre nomes e objetos Java (basicamente um mapa cujas chaves só podem ser Strings) e cada biblioteca de script fornece sua implementação da Interface. Obtemos uma instância vazia do objeto correto a partir do ScriptEngine, utilizando o método createBindings(). O código a seguir exemplifica seu uso.

Ao ser executado, esse código imprimirá, na saída padrão, o valor de PI.

O mesmo efeito pode ser obtido através do seguinte código, que usa o método put() do ScriptEngine para alterar os vínculos diretamente.

De maneira inversa, para acessar um objeto vinculado através da linguagem de scripting, podemos utilizar o método eval() e o método “get(String key)” a partir do ScriptEngine ou do objeto Bindings desse ScriptEngine, conforme o código da Listagem 12.

Page 10: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

49

public class CalculadoraDeDescontos {

private ScriptEngine motor;

public CalculadoraDeDescontos(ScriptEngine motor, File arquivoScript)

throws FileNotFoundException, ScriptException {

// Você verificaria a integridade dos argumentos aqui.

this.motor = motor;

Reader leitor = new FileReader(arquivoScript);

motor.eval(leitor); // assume que o motor mantém estado.

}

private Dinheiro executarMetodoScript(Dinheiro precoDeEtiqueta,

String metodo) throws ScriptException {

Object retorno = motor.eval(metodo);

if (retorno instanceof Number) {

Number descontoPercentual = (Number) retorno;

double percentualAPagar = 1.0 - descontoPercentual.doubleValue();

return precoDeEtiqueta.multiplicar(percentualAPagar);

} else {

throw new ScriptException(“Script deveria retornar um numero.”);

}

}

public Dinheiro calcularPrecoAVista(Dinheiro precoDeEtiqueta)

throws ScriptException {

return executarMetodoScript(precoDeEtiqueta,

“obter_desconto_para_pagamento_a_vista()”);

}

public Dinheiro calcularPrecoClientePlus(Dinheiro precoDeEtiqueta)

throws ScriptException {

return executarMetodoScript(precoDeEtiqueta,

“obter_desconto_para_cliente_plus()”);

}

public Dinheiro calcularPrecoParcelamento(Dinheiro precoDeEtiqueta,

int numeroParcelas) throws ScriptException {

Object retorno = null;

// Aqui demonstramos uma maneira alternativa de executar o método

if (motor instanceof Invocable) {

Invocable motorInvocable = (Invocable) motor;

try {

retorno = motorInvocable.invokeFunction(

“obter_juros_para_parcelamento”, numeroParcelas);

} catch (NoSuchMethodException e) {

e.printStackTrace();

}

} else {

retorno = motor.eval(“obter_juros_para_parcelamento(“ +

numeroParcelas + “)”);

}

if (retorno instanceof Number) {

Number juros = (Number) retorno;

double percentualAPagar = 1.0 + juros.doubleValue();

return precoDeEtiqueta.multiplicar(percentualAPagar);

} else {

throw new ScriptException(“Script deveria retornar um numero.”);

}

}

public static void main(String[] args) {

public interface Moeda {

public String getNome();

public String getSimbolo();

}

public class Real implements Moeda {

@Override

public String getNome() {

return “real”;

}

@Override

public String getSimbolo() {

return “R$”;

}

}

try {

ScriptEngine motor = new ScriptEngineManager()

.getEngineByName(“ruby” /* ou “jruby” */);

CalculadoraDeDescontos calculadora =

new CalculadoraDeDescontos(motor,

new File(“scripts/regras_desconto.rb”));

Dinheiro precoDeEtiqueta = new Dinheiro(100, 00, new Real());

Dinheiro precoAVista = calculadora.calcularPrecoAVista(precoDeEtiqueta);

Dinheiro precoClientePlus = calculadora

.calcularPrecoClientePlus(precoDeEtiqueta);

Dinheiro precoQuatroParcelas =

calculadora.calcularPrecoParcelamento(

precoDeEtiqueta, 4);

Dinheiro precoSeteParcelas = calculadora.calcularPrecoParcelamento(

precoDeEtiqueta, 7);

Dinheiro precoDozeParcelas = calculadora.calcularPrecoParcelamento(

precoDeEtiqueta, 12);

System.out.println(“Preço de etiqueta : “ + precoDeEtiqueta);

System.out.println(“Preço à vista : “ + precoAVista);

System.out.println(“Preço cliente plus: “ + precoClientePlus);

System.out.println(“Preço 4x : “ + precoQuatroParcelas);

System.out.println(“Preço 7x : “ + precoSeteParcelas);

System.out.println(“Preço 12x : “ + precoDozeParcelas);

} catch (FileNotFoundException e) {

e.printStackTrace(); // tratar

} catch (ScriptException e) {

e.printStackTrace(); // tratar

}

}

}

Listagem 8. Interpretando um arquivo de script.

Listagem 9. Interface Moeda.

Listagem 10. Classe Real.

Page 11: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

50 www.mundoj.com.br

public class Dinheiro { private long quantidade; private Moeda moeda; public Dinheiro(long principal, long centavos, Moeda moeda) { // verificar parametros de entrada aqui. quantidade = principal * 100 + centavos; this.moeda = moeda; } public Dinheiro multiplicar(double fator) { long total = (long) (quantidade * fator); return new Dinheiro(total / 100, total % 100, moeda); } private String formatar(long q) { long principal = q / 100; long centavos = q % 100; return principal + “,” + centavos/10 + centavos % 10; } @Override public String toString() { return moeda.getSimbolo() + “ “ + formatar(quantidade); }}

ScriptEngineManager gerente = new ScriptEngineManager();Bindings vinculosGlobais = gerente.getBindings();vinculosGlobais.put("ar", "disponível para todos.");ScriptEngine engine = gerente.getEngineByName("jruby");engine.eval("puts($ar)");System.out.println(engine.getBindings(ScriptContext.GLOBAL_SCOPE).get("ar"));

StringWriter saidaErro = new StringWriter();StringWriter saida = new StringWriter(); ScriptEngine engine = new ScriptEngineManager().getEngineByName("jruby");ScriptContext contextoEngine = engine.getContext();contextoEngine.setWriter(saida);contextoEngine.setErrorWriter(saida);

engine.eval("puts('Saida estratégica pela esquerda!')");System.out.println("saida: " + saida);

engine.eval("$legal = ':)'"); // observe o uso de “$”Object engineSmile = engine.get("legal");Bindings vinculos = engine.getBindings(ScriptContext.ENGINE_SCOPE);Object bindingsSmile = vinculos.get("legal");

Listagem 11. Classe Dinheiro. Listagem 13. Como alterar os vínculos globais das linguagens de scripting.

Listagem 14. Procedimentos para mudar a saída padrão e de erro usadas pela linguagem de scripting.

Listagem 12. Código Java para ler o conteúdo de uma variável da linguagem de scripting.

Observe que para obter os Bindings de um ScriptEngine, precisamos fornecer um identificador de escopo como argumento. Todas as modifi-cações de vínculo que fizemos até agora só afetaram o ambiente de uma dada instância de ScriptEngine criada por um ScriptEngineManager. Por isso, dizemos que o escopo desses vínculos é escopo de engine (engine scope). Ou seja, se criássemos um novo ScriptEngine a partir de um Scrip-tEngineManager para a mesma linguagem, não teríamos acesso aos vín-culos adicionados (para o novo engine acessar os vínculos, deveríamos informar, explicitamente, o mapeamento).

Para fazer com que um dado vínculo tenha escopo global, devemos alterar os Bindings do ScriptEngineManager, de maneira semelhante ao código mostrado na Listagem 13.

É possível implementar outros escopos de binding além dos de engine e global, mas isso vai além do propósito deste artigo.

Contexto do Script (ScriptContext)

Compilando um script

A interface ScriptContext representa o contexto de execução de uma

dada engine de script. No ScriptContext estão contidos os vários mapea-

mentos de vínculo (Bindings) disponíveis, o leitor da entrada padrão e os

escritores da saída padrão e de erro. Note que algumas engines (como o

Rhino, de JavaScript) podem não suportar alterar a entrada padrão.

Na Listagem 14, vemos um código que altera a saída padrão e de erro utilizada nos scripts interpretados pela instância de ScriptEngine.

Para acelerar a execução de código do script, algumas engines permitem a execução de uma forma intermediária de código, compilado. Quando isso é possível, a engine implementa a interface javax.script.Compilable, que disponibiliza os métodos “compile(String script)” e “compile(Reader script)”. Estes métodos retornam um objeto do tipo CompiledScript, que define os métodos:

Deve-se notar que um CompiledScript não define um método “eval()” que receba um objeto “String script” ou um “Reader script”, sendo apenas possí-vel reproduzir o script compilado que ele representa. A Listagem 15 ilustra os passos para compilar e executar um script previamente compilado.

Considerações finais

Conhecer diversas linguagens e paradigmas de programação tem se

tornado um requisito essencial para o desenvolvedor moderno. Em

particular, linguagens dinamicamente tipadas, como Ruby, Python e

Groovy têm conquistado um espaço cada vez maior na construção das

Neste artigo, demos uma passada bem rápida pela linguagem Ruby,

mostrando um pouco algumas de suas características e funcionalidades

que a tem colocado como uma linguagem bastante promissora. Mos-

tramos como podemos utilizar o poder de Ruby em nossas aplicações

Java, visando elevar o nível de flexibilidade e facilitar a customização de

nossos sistemas. Em especial, discutimos a Java Scripting API, introduzi-

Page 12: capa Dinamismo e elegância na parceria Java & · PDF filepodemos programar melhor na nossa própria linguagem. Mas essa não é ... framework escrito em Ruby para desenvolvimento

51

try

{

CompiledScript scriptCompilado = null;

if (engine instanceof Compilable) {

Compilable compilador = (Compilable) engine;

Reader leitorScript = new FileReader(new File(“scripts/codigo_ruby.rb”));

scriptCompilado = compilador.compile(leitorScript);

}

if (scriptCompilado != null) {

scriptCompilado.eval();

} else {

// tratar caso.

}

} catch (FileNotFoundException e) {

e.printStackTrace(); //tratar

}

Listagem 15. Código Java para compilar código da linguagem de scripting, se suportado.

da no Java 6 para facilitar a execução de código de outras linguagens na plataforma Java.

É fato que linguagens estaticamente tipadas não são pré-requisitos para construção de grandes sistemas de software. Tanto que técnicas como programação orientada a aspectos, reflexão e geração de byte-code têm se popularizado no mundoj para tentar contornar a “natureza estática” da linguagem. Linguagens dinâmicas têm sido utilizadas com sucesso para construção de grandes aplicações, como, por exemplo: Youtube.com (Python), Basecamp e Twitter (Ruby on Rails), a infinidade de aplicações em PHP, apenas para citar algumas. Passar numa etapa de compilação nunca foi garantia de corretude para qualquer software. Essa segurança pode ser obtida por outras práticas, como a escrita de testes de unidade e de integração, automatizados, num processo de integração contínua.

De qualquer forma, o poder disponível em linguagens como Ruby deve ser explorado de maneira adequada e responsável. De fato, com a liberdade proporcionada por esses tipos de linguagens, torna-se mais fácil também fazer um uso indevido desses recursos e introduzir defeitos difíceis de serem localizados e corrigidos. Alguns erros que são detecta-dos em tempo de compilação em linguagens como Java, são detectados apenas em tempo de execução em linguagens como Ruby. Para serem mais eficientes em seu trabalho, os desenvolvedores devem ter a ma-turidade e a experiência necessárias para tomar as decisões corretas e utilizar as ferramentas adequadas.

“O bom siso te guardará, e a inteligência te conservará.” (Pv 2:11)

Referências

Reader leitorScript = new FileReader(new File(“scripts/codigo_ruby.rb”));

scriptCompilado = compilador.compile(leitorScript);

}

if (scriptCompilado != null) {

scriptCompilado.eval();

} else {

// tratratartar ca caso.

}

} catch (FileNotFoundundundExcExcepteption e) {

e.printStackTrace(); ); ); //t//t//t//tratarar

}

de comEssa segtestes dintegr

De quadeve sa liberdfácil tadifíceisdos emdoapenasmais eturidadeutilizar

“O bom

erêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêncincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincincinciasasasasasasasasasasasasasasasasasasasasasasasasasasasasasasasasRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefRefReferêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerêerê

-

mas

Cornell

index.html