Back-End

30 nov, 2012

Janela de log like a Log4j

Publicidade

Olá, pessoal.

Para quem trabalha com aplicações JSE e utiliza a morosa API do javax.swing ou java.awt do Java já sabe das complicações que são agregadas diretamente no dia-a-dia…listeners, configuração de laytouts etc.

Quando temos em mãos aplicações grandes ou então com muitas telas com formulários, às vezes acabamos necessitando de um debug visual desses objetos, mas não querendo usar os stacktraces do Eclipse ou então por meio “sysouts” no meio do código fonte, e sim uma solução reutilizável, como por exemplo uma janelinha de diálogo simples que poderia ser aberta por meio de uma tela de atalho ou então um item de menu. Enfim, uma solução para o usuário final ou então para uma equipe de testes.

Bom, essa é a ideia deste artigo.

Antes de começarmos, pessoal, confesso que é um pouco desnecessário, em um primeiro caso, nós poderíamos apenas usar o Log4j e ter um log bem consistente de toda a aplicação, e não de apenas um item em particular, mas vou defender o lado da manutenção e melhorias em aplicações legadas, nas quais a criação de soluções a partir de tecnologias (bibliotecas, frameworks etc.) recentes pode acabar gerando problemas diversos, como de incompatibilidade.

Vamos à solução, pessoal.

Objetivo básico

Vamos marcar como objetivo apenas montar uma janela usando o recurso pronto do javax.swing, no caso o JOptionPane.showMessageDialog e imprimir dela os atributos e seus respectivos valores de um objeto de negócio das telas do nosso sistema.

Reflexão dos atributos

Como a ideia é manter o estado atual de bibliotecas sem precisar adicionar novas ao seus projetos, vou fazer a reflexão à moda antiga, usando a java.lang.reflect pura (não é legal):

//...
Cliente r = new Cliente();
r.setNome("Carlos A. Junior");
r.setId(new Long(123));
List<Field> fields = Arrays.asList(r.getClass().getDeclaredFields());
//...

Eu particularmente prefiro ter uma DSL para abstrair esse trabalho e adicionar alguns controles que não cabe a nós ficar cuidando no dia-a-dia. No exemplo abaixo, veja como ficaria se a gente fosse utilizar a biblioteca Mirror.

//...
Cliente r = new Cliente();
r.setNome("Carlos A. Junior");
r.setId(new Long(123));
List<Field> fields = new Mirror().on(Cliente.class).reflectAll().fields();
//...

Bom, na sequência, quem já utilizou o Log4j está acostumado com aquele log bem organizado onde o nome da classe que está gerando a mensagem aparece formata entre um par de colchetes, mais ou menos assim:

[MinhaClasseNomeGrande   ] - Alguma mensagem interessante...
[ClasseNomeMenor         ] - Outra mensagem legal...

Viram como é legal essa marotagem que o Log4j tem?! Então, pessoal, vamos fazer o mesmo com o nosso log, só que com o nome dos atributos ao invés das classes, o que vai ficar bem mais agradável para o usuário final.

Mas como que fazemos isso?

Simples, vamos usar um fator para calcular a edentação dos espaços para o colchete que fecha o nome da classe. Para resolvermos isso de maneira elegante, vamos criar o seguinte método:

// sequência do código acima.
private static String formatClassName(String field, int max){
   int rest = (max - field.length());
   if(rest > 0){
      field = field + String.format("%" + rest + "s", " ");
   }
   return ("[" + field + "]");
}
//...

Só lembrando, pessoal, que o fator se dá a partir do maior nome de atributo da classe. Para descobrirmos esse fator, podemos simplesmente iterar a lista de atributos e guardar o tamanho (length) da String referente ao nome do atributo (Field.getName()):

int fator = 0;
for (Field field : fields) {
   if(field.getName().length() > fator){
      fator = field.getName().length();
   }
}

Bacana, pessoal, então até este ponto nós temos a nossa lista de atributos do tipo java.lang.reflect.Field e também o fator de escala da edentação  para o fechamento dos colchetes.

Bom, agora precisamos apenas montar a mensagem de forma organizada para aparecer na janela. Eu vou deixar um estilo simples (chave = valor), isso vocês poderão fazer da maneira que vocês julgarem melhor depois. Vamos então iterar a nossa lista de atributos e montar a mensagem  que será apresentada ao usuário:

// sequência de código anterior...
StringBuilder builder = new StringBuilder();
 
for (Field field : fields) {
   field.setAccessible(true);
   String s = formatClassName(field.getName(), fator);
   builder.append(s + " = " + field.get(r) + "\n");
}

Simples não? Usei a classe StringBuilder para ajudar no consumo de memória, porque esse tipo de operação de concatenação em loops é mais aconselhável.

Interface gráfica

Bom, por fim, falta a gente montar a janela para que o usuário veja essa nossa marotagem que fizemos funcionando. Não vou um mestre do swing do Java, mas acho que a solução para construir a janela não ficará muito diferente disto:

// sequencia de código anterior...
final JTextArea textArea = new JTextArea();
textArea.setFont (new Font(Font.MONOSPACED, Font.PLAIN, 12));
textArea.setAlignmentY (JTextArea.RIGHT_ALIGNMENT);
textArea.setEditable (false);
textArea.append (builder.toString());
 
JScrollPane scroll = new JScrollPane(textArea);
scroll.setPreferredSize(new Dimension(500, 250));
 
JOptionPane.showMessageDialog(null, scroll, "Dados do objeto", JOptionPane.INFORMATION_MESSAGE);

Nesse trecho de código acima, nós criamos uma instância do JTextArea para armazenar o conteúdo da nossa StringBuilder resultante da iteração da lista de atributos. Depois definimos que esse conteúdo terá barras de rolagem por meio de um JScrollPane; assim, caso o texto ultrapasse o tamanho pré-definido do objeto JScrollPane, uma barra de rolagem será embutida automaticamente (isso pode ser melhor configurado também).

Pessoal, um detalhe com a fonte que eu configurei. A fonte padrão que o JTextArea utiliza possui a largura diferente para alguma letras, como no caso do “M” e do “I”; assim, o alinhamento do fechamento dos colchetes não bate. Desse modo, eu configurei para ele utilizar a mesma fonte padrão do console do Eclipse IDE, no qual todas as letras possuem uma mesma largura.

Na imagem abaixo vocês podem visualizar melhor isso:

Bug do alinhamento dos colchetes.

Então, pessoal, sem muito sofrimento e complicação, nós fizemos essa tela apresentada na imagem abaixo:

Para obter a implementação que construímos, deixei disponível lá no Github para vocês no endereço abaixo:

https://github.com/carlosjrcabello/debug-window

Conclusão

Esta solução, por mais que não seja algo novo, pode ajudar a resolver problemas comuns quando você trabalha com aplicações JSE que fazem uso do problemático javax.swing.

Particularmente no projeto em que estou trabalhando, nós usamos esta solução, e ela ajuda bastante o pessoal do teste a relatar problemas quanto a lógicas de negócio que não estão consistentes (cálculos financeiros por exemplo). Assim, eles podem facilmente informar no retorno de uma requisição de serviço qual o ID de um registro que está inconsistente.

Obs: Nesses casos, nem um JUnit ajudaria devido ao grande número de regras que são aplicadas.

Encerro por aqui, dúvidas, críticas e sugestões são bem-vindas.

Referências