quarta-feira, 28 de novembro de 2007

Renderizadores e Binding

As JSRs 227, 273, 295 e 303 devem ser as linhas-mestre, mas, infelizmente, elas não estão prontas na totalidade e dificilmente terão um ponto comum. Assim, tenho que correr atrás de uma forma de fazer binding dos controles e dados no Merlin.

Ontem a noite, postei no grupo do merlin uma thread sobre renderizadores e binding customizados, com as idéias preliminares que eu tinha sobre isso. Porém, não sosseguei, e passei o dia hoje trabalhando no assunto. Fiz uma versão essencial, e o demo mostra o resultado.

As questões técnicas eu deixo em segundo plano, mas a essência é importante discutir.

Binding
Ao meu ver, o famoso binding pode ser interpretado como tarefa do controlador no modelo MVC, atuando entre a View e o Modelo (vejam como é difícil optar por uma língua única: português e ingles parecem viver juntos na Informática).

E ele pode ser ativado em momentos diferentes, várias vezes durante a interação do usuário. Essas várias vezes nada mais são que os eventos que a View (o controle da tela) dispara durante a interação com o usuário.

O binder (objeto que está fazendo o binding) pode ser ativado em um (ou vários) desses eventos, tanto para frente quanto para trás.

Para frente e para trás, podem ser interpretados como ações de "carregar o controle em função do valor do modelo" (load) e "atualizar o modelo em função do valor do controle" (update), respectivamente. Ou o contrário, é o freguês que manda :)

Pois bem, cada ação de load ou update, pode incorrer em sucesso ou fracasso. Geralmente, as ações de load (carregar o controle) não vão dar problemas no sistema, uma vez que é esperado que o modelo de dados esteja consistente. Porém, as ações de update podem, e geralmente vão, dar problemas. Isso porque é nesse momento que estamos capturando as CGDAS do usuário.

Quando uma CGDA dessas acontece, é função do binder avisar o sistema sobre o erro ocorrido. Assim, ele dispara uma mensagem com o que aconteceu. O controller captura essa mensagem e atualiza a view e o ciclo está finalizado.

Complexidades
Nota-se que tudo isso é um processo complexo, e que não pode ser fixo. Em outras palavras, por mais que tenhamos uma engenhosa miríade de código (tal como os validadores do Eclipse), precisamos ter uma porta de saída para o desenvolvedor. E essa porta de saída não pode ser a dos fundos.

A porta de saída precisa ser elegante, projetada e de fácil manuseio. É aí que entram as reais capacidades do framework e o porquê as JSRs acima.

A anotação RenderAs e o Binding
Como visto no post anterior, a anotação RenderAs serve para escolhermos um controle de tela diferente do padrão para um determinado campo de um objeto.

Quando ela é usada, é muito provável que estejamos saindo do contexto comum e caindo em casos onde precisamos, manualmente, dizer como o (mal)dito campo será mapeado para o controle da tela e vice-versa. É nesse momento que entra o binding.

Na implementação que fiz hoje (sofrendo com os genéricos do Java, diga-se de passagem), eu coloquei a feature do binding bem pertinho da anotação RenderAs. Tão pertinho, que ela está dentro, até. Vejamos o exemplo:

class UmCliente {

Strin
g nome;
@RenderAs(JTextField.class)
boolean vip = true;


}


Esse código produz algo como essa figura:


Notem que o campo vip parece ter sofrido um toString() para ser exibido no respectivo controle. E foi mais ou menos isso mesmo (pelo menos, nessa versão alpha do Merlin).

Para termos uma coisa mais elegante, podemos fazer algo como:

class UmCliente {

String nome;

@RenderAs(value=JTextField.class,binder=BooleanToTextComponent.class)
boolean vip = true;

}


Agora, além de especificar um controle de tela diferente, eu avisei o Merlin para colocar um binder específico. O resultado disso é algo como a figura:


Mas como esse binder é implementado? Vejam abaixo:

public class BooleanToTextComponentBinder implements IBinder[Boolean,JTextComponent] {

public boolean load(Boolean model, JTextComponent view) {


view.setText(model.booleanValue() == true ? "SIM" : "NÃO");
return true;


}

public boolean update(Boolean model, JTextComponent view) {


model = view.getText().equals("NÃO") ? Boolean.TRUE : Boolean.FALSE;
return true;


}

}

A interface genérica IBinder[M,V] (que o post mostra incorretamente, pois caracteres "maior que" e "menor que" não são suportados aqui) do Merlin, e especifica os métodos load e update, como discutido anteriormente. Graças aos tipos genéricos, não precisamos conversões para acessar tanto o modelo, quanto o controle de tela. E, no miolo dos métodos, código Java simples...

Conclusões
Não está tão simples quanto eu quero, mas para uma versão feita em três horas e meia está louco de bom (sim, o resto do dia foi perdido fazendo o vídeo-demo. Pode?)

Espero que das JSRs converjam (existe essa palavra?) logo, de forma que o primeiro release do Merlin esteja bem fundamentado nelas.

Por hora é isso.

terça-feira, 27 de novembro de 2007

@RenderAs

Hoje o trabalho estava fácil, e então decidi dar uma olhada na implementação do Merlin, para deixar redonda uma versão - básica, diga-se de passagem - para ser mostrada na banca do mestrado, lá por Março do ano que vem.

Pois bem, fiquei feliz, porque muitas coisas que eu estava imaginando, na verdade eu já tinha até implementado. Estou falando do RenderAs.

RenderAs
Essencialmente, como todo Gerador Baseado em Modelos, o Merlin utliza regras heurísticas para mapear um atributo de um objeto em um controle em um formulário, como um campo String mapeado para um TextBox. Muitas heurísticas existem, e é bem verdade que um grande escopo de coisas podem ser feitas automaticamente. Com mais algumas heurísticas (e essas sim, que somente o Merlin utiliza) coisas bem interessantes podem ser feitas (...)

Porém, nem sempre as heurísticas dão conta do recado. Nesses momentos, o programador precisa colocar a mão na massa e dizer, explicitamente, o que ele deseja que seja mostrado na tela. No Merlin, para fazer isso, lançamos mão da anotação @RenderAs, como no exemplo:

class Usuario {
String nome;
@RenderAs(JTextArea.class)
String descricao;
}

Ao ser renderizado um formulário para o objeto Usuario, este conterá um TextBox referente ao campo nome, e um TextArea referente ao campo descricao. Caso a anotação @RenderAs não tivesse sido utilizada, ambos campos seriam renderizados como caixas de texto simples (uma fixa, e outra variável - devido algumas heurísticas).

Neutralidade
O programador experiente logo perceberá que parece estarmos atrelando nosso objeto ao framework Swing, devido a classe JTextArea. Na verdade, isso não ocorre.

O Merlin trabalha tanto em nível físico, quanto em nível abstrato para seus mapeamentos. Em outras palavras, é possível utilizar algo como:

class Usuario {
String nome;
@RenderAs(TEXT_AREA)
String descricao;
}

Nesse exemplo, puxando mais letrinhas do mundo das ferramentas model-based, estamos trabalhando com Abstract Interface Objects (AIOs) ao invés de Concrete Interface Objects (CIOs). Hum, interessante...

Mas, novamente, um leitor atento perguntará? Bom, mas e se já tivermos uma aplicação construída para um framework como Swing ou SWT, e quisermos migrá-la para uma plataforma web, como o JSF, como faríamos ?

A resposta é: não é preciso fazer nada.

Diferente de outras ferramentas, o Merlin suporta o mapeamento bidirecional entre AIOs e CIOs, de forma que, mesmo um objeto anotado com um JTextArea poderá ser renderizado corretamente em uma aplicação para Internet.

Obviamente, essa abordagem tem seus custos. E quais são? Tão simplesmente a dependência de compilação das classes do pacote gráfico original (no caso, o Swing). Em outras palavras, um problema de projeto, e não do framework.

O futuro
Mais coisas incorrem dessas customizações, como binding e outras funcionalidades. Isso ainda não está implementado e também nem pensei no assunto, visto que, como disse no início do post, minha preocupação para o momento é deixar o que existe mais ou menos redondo, para não dar "tela azul" na frente dos homi.

Amanhã quero comentar o @Dependence.