quinta-feira, 2 de abril de 2009

CollectionUtils e Filter<T> Manipulando e criando buscas.

A algum tempo atrás eu andava com uma dúvida de como realizar buscas genéricas em uma Collection tipada, ate que um colega Sérgio Taborda me sugeriu o uso de uma interface muito simples.

A ideia é muito simples cria-se uma interface chamada Filter<T> que é um contrato, que relata como deve ser o objeto que queremos encontrar, e então cria-se rotinas simples baseando neste contrato (a interface) para buscas.

interface Filter<T>

  1. /**
  2.  * Filtro de objeto, que testa se um obejto candidato T confere com o filtro.
  3.  * @author Tomaz Lavieri
  4.  * @param <T> o tipo de objetos que o filtro testa.
  5.  * @see CollectionUtils
  6.  */
  7. public interface Filter<T> {
  8.     /**
  9.      * Verifica se o objeto candidato passa pelo filtro.
  10.      * @param candidate Objeto candidato.
  11.      * @return  <tt>true</tt> - caso o candidato passe no filtro.
  12.      *          <br><tt>false</tt> - caso o candidato não pesse pelo filtro.
  13.      */
  14.     public boolean match(T candidate);
  15. }


É com essa ideia do filtro, que se cria um critério para buscas em coleções, por exemplos, vamo supor que agente tem uma classe chamada Pessoa e que queremos buscar todas as pessoas que contenham o nome "João" e que a idade seja maior ou igual a 18 anos.

  1. Filter<Pessoa> joaoMaioresDe18 = new Filter<Pessoa>() {
  2.     public boolean match(Pessoa candidate) {
  3.         boolean nomeOk = candidate.getNome() != null ?
  4.             candidate.getNome().toLowerCase().contains("joão") :
  5.             false;
  6.  
  7.         return nomeOk && candidate.getIdade() >= 18;
  8.     }
  9. };


Agora temos um critério de busca que foi guardado na variável joaoMaioresDe18 e podemos checar facilmente se qualquer pessoa passa pelo criterio utilizando joaoMaioresDe18.match(pessoa); este retorna true caso a pessoa seja um joão maior de 18 anos.

Agora podemos criar uma rotina para buscar em uma lista, todas as pessoas que pertençam ao criterio informado, ficando assim:

  1. List<Pessoa> pessoas = getAllPessoas(); //abstraindo como essa lista é obtida.
  2. List<Pessoa> resultado = new ArrayList<Pessoa>(0); //guarda o resultado
  3. for (Pessoa pessoa : pessoas) //realizando a busca
  4.     if (joaoMaioresDe18.match(pessoa))
  5.         resultado.add(pessoa);


Após isto, temos em resultado, todas as pessoas que contém o nome "joão" e que são maiores de idade.

Ficou bom, mais o processo pode ser automatizado e tornar-se ainda melhor, para isso é criado uma classe utilitária chamada CollectionUtils onde podemos guardar rotinas como:
  1. Buscar todos os resultados que coincidem com o filtro - findAllMatch
  2. Buscar todos os resultados que não coincidem com o filtro - findAllNotMatch
  3. Buscar o primeiro resultado que coincidir com o filtro - findFirstMatch
  4. Buscar o primeiro resultado que não coincir com o filtro - findFirstNotMatch
  5. Reter na lista total apenas os objetos que coincidirem com o filtro - retainAll
  6. Remover da lista total todos os objetos que coincidirem com o filtro - removeAll
  7. Adciona a uma segunda lista todos os itens da primeira lista que coincidirem com o filtro - addAllMatch
  8. Adciona a uma segunda lista todos os itens da primeira lista que não coincidirem com o filtro - addAllNotMatch


Colocarei aqui apenas um trecho de CollectionUtils e o link para o código completo segue aqui http://pastebin.com/f9d80cb7

class CollectionUtils
  1. import java.util.ArrayList;
  2. import java.util.Collection;
  3. import java.util.Iterator;
  4. import java.util.List;
  5.  
  6. /**
  7.  * Classe utilitária para manipular coleções
  8.  * @author Tomaz Lavieri
  9.  */
  10. public class CollectionUtils {
  11.     /**
  12.      * Não é possivel instaciar CollectionUtils.
  13.      */
  14.     private CollectionUtils(){}
  15.  
  16.     /**
  17.      * Busca todos os objetos <b>T</b> na lista de <tt>candidates</tt> que
  18.      * conferem com o <tt>filter</tt>.
  19.      * <BR>
  20.      * <BR>Tem o resultado inverso a {@link #findAllNotMatch(Collection, Filter)}
  21.      * @param   <T> o tipo de objeto a ser buscado.
  22.      * @param   candidates a coleção de condidatos aonde deseja-se realizar a
  23.      *          busca.
  24.      * @param   filter o filtro que será usado para <b><u>aceitar</u></b> um
  25.      *          candidato no resultado da busca.
  26.      * @return  todos os itens que passaram pelo filtro.
  27.      * @see #findAllNotMatch(Collection, Filter)
  28.      */
  29.     public static <T> List<T> findAllMatch(Collection<? extends T> candidates,
  30.             Filter<T> filter) {
  31.         List<T> matchs = new ArrayList<T>(0);
  32.         addAllMatch(candidates, filter, matchs);
  33.         return matchs;
  34.     }
  35.  
  36.     /**
  37.      * Adciona a coleção <tt>recipient</tt> todos os objetos <tt>T</tt> da lista
  38.      * de <tt>candidates</tt> que coincidirem com o <tt>filter</tt>.
  39.      * <BR>
  40.      * <BR>Adciona o inverso dos itens de <tt>candidates</tt> que
  41.      * {@link #addAllNotMatch(Collection, Filter, Collection)} adcionaria.
  42.      * @param   <T> o tipo de objeto a ser adcionado.
  43.      * @param   candidates a coleção de candidatos a serem adcionados ao
  44.      *          <tt>recipient</tt>.
  45.      * @param   filter o filtro que será usado para <b><u>aceitar</u></b> um
  46.      *          candidato a ser adcionado ao <tt>recipient</tt>.
  47.      * @param   recipient coleção onde os objetos serão adicionados.
  48.      * @see #addAllNotMatch(Collection, Filter, Collection)
  49.      */
  50.     public static <T> void addAllMatch(Collection<? extends T> candidates,
  51.             Filter<T> filter, Collection recipient) {
  52.         for (T object : candidates)
  53.             if (filter.match(object))
  54.                 recipient.add(object);
  55.     }
  56.    
  57. //... restante da classe segue no link indicado
  58.  
  59. }


Agora com essas duas classes, basta criar um filtro, e mandar junto com a coleção, para CollectionUtils e receber os resultados.

Desta forma espera-se aumentar a produtividade, e facilitar a manipulação de coleções.



Filter.java
CollectionUtils.java


Nenhum comentário:

Postar um comentário

Seguidores