Boa noite pessoal,
Pra quem trabalha com aplicações JSE e infelizmente utilizam a morosa API do javax.swing ou java.awt do Java já sabem 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, as vezes acabamos necessitando de um debug visual destes objetos, mas não querendo usar os stacktraces do Eclipse ou então por meio “sysouts” no meio do código fonte, mas 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, esta é 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, onde a criação de soluções a partir de tecnologias (bibliotecas, frameworks, etc.) recentes pode acabar gerando problemas diversos, como de incompatibilidade.
Vamos a mega 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 dentro da mesma 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 para não conflitar com alguma coisa da aplicação, vou fazer a reflexão a moda antiga, usando a java.lang.reflect pura (não é legal):
[java]
//…
Cliente r = new Cliente();
r.setNome(“Carlos A. Junior”);
r.setId(new Long(123));
List<Field> fields = Arrays.asList(r.getClass().getDeclaredFields());
//…
[/java]
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 como ficaria se a gente fosse utilizar a biblioteca Mirror.
1 2 3 4 5 |
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 para 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:
1 2 |
[MinhaClasseNomeGrande ] - Alguma mensagem interessante... [ClasseNomeMenor ] - Outra mensagem legal... |
Viram como é legal essa marotagem que o Log4j tem?! Então pessoal vamos fazer o mesmo ao nosso log também, só que com o nome dos atributos das ao invés das classes, vai ficar bem mais agradável ao usuário final.
Mas como que fazemos isto?
Simples, vamos usar um fator para calcular a edentação dos espaços para o colchete que fecha o nome da classe. Para resolvermos isto de maneira elegante, vamos criar o seguinte método:
1 2 3 4 5 6 7 8 9 10 |
// 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 + "]", "]"); } return ("[" + field + "]"); } //... |
Só lembrando pessoal que o fator se dá a partir do maior nome de atributo da classe, para descobrirmos este fator podemos simplesmente iterar a lista de atributos e guardar o tamanho (length) da String referente ao nome do atributo (Field.getName()):
1 2 3 4 5 6 |
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 pessoal, agora precisamos apenas montar a mensagem de forma organizada para aparecer na janela. Eu vou deixar um estilo simples (chave = valor), isto 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:
1 2 3 4 5 6 7 |
// 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) + ""); } |
Simples não? Usei a classe StringBuilder pra ajudar no consumo de memória, uma vez porque esse tipo de operação de concatenação em loops é mais aconselhável usá-la.
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 disso:
1 2 3 4 5 6 7 8 9 10 11 |
// 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 este 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 (isto 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. Deste modo eu configurei para ele utilizar a mesma fonte padrão do console do Eclipse IDE, onde todas as letras possuem uma mesma largura.
Na imagem abaixo vocês podem visualizar melhor isso:
Então pessoal, sem muito sofrimento e complicação nós fizemos essa tela apresentada na imagem abaixo:
Então pessoal, 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 que estou trabalhando nós usamos esta solução, 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: Esses 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
http://www.guj.com.br/java/27728-stringbuffer
http://www.java2s.com/Code/Java/Data-Type/RightpadaStringwithspaces.htm
http://projetos.vidageek.net/mirror/mirror/
http://en.wikipedia.org/wiki/Domain-specific_language
http://stackoverflow.com/questions/5828625/if-swing-is-deprecated-what-is-the-alternative