Persistência em JME – Parte II

 

Funcionalidades mais avançadas

Agora que vimos o básico do trabalho com o RMS podemos ver algumas funcionalidades mais interessantes, com a implementação de filtros e também um método mais elegante para se percorrer o nosso RecordStore. Agora vamos conhecer mais alguns métodos de RMS e então partir para o nosso segundo exemplo.

 

Nós vimos no sistema uma forma de proceder na listagem do RecordStore, entretanto essa forma não parece muito elegante, e também não é muito eficiente para determinadas operações, já que ela exige uma série de pequenas operações e a medida que o RecordStore for crescendo acabará tornando mais dispendiosa a tarefa de lista-lo.

 

Para resolver esses problemas, RMS nos fornece um método para trabalhar de forma mais otimizada com o RecordStore, este método é o public RecordEnumeration enumerateRecords(RecordFilter filtro,

RecordComparator comparador, boolean mantenhaAtualizado) Esse método devolve um RecordEnumeration referente aos elementos do RecordStore. Esse método recebe como parâmetros um RecordFilter que será utilizado para fazer uma filtragem dos elementos e retornar uma pesquisa mais otimisada, um RecordComparator que é utilizado para fazer um comparação e nos retornar uma lista ordenada da nossa pesquisa. Por fim um boolean que quando tiver como valor true manterá o RecordEnumeration sempre atualizado com relação as mudanças do RecordStore, o problema dessa opção é que ao fazer isso estaremos aumentando a carga por sobre a nossa aplicação uma vez que cada modificação que ocorrer no RecordStore deverá ser reportada até o RecordEnumeration, devido a este triste efeito colateral muitas pessoas preferem trabalhar com boolean com valor false e assumir os riscos de usar um RecordEnumeration que pode não estar representando de forma muito fiel o RecordStore. Vamos agora entender cada um desses novos elementos.

        

* RecordEnumeration: é uma interface que possui um conjunto de métodos que podem ser utilizados para iterar por sobre o RecordStore. O RecordEnumeration permite caminhar através do RecordStore em dois sentidos. A forma de se trabalhar com esse ente é similar a que se utiliza para trabalhar com um Enumeration comum, a exceção que o último não permite caminhar para trás. RecordEnumeration nos disponibiliza, entre outros, os métodos hasNextElement() e hasPreviousElement()  para sabermos se existem Record´s posteriores ou precedentes respectivamente e  nextRecordId() e previousRecordId() para obtermos o elemento seguinte ou o anterior. Por fim, é uma prática recomendada o emprego do método destroy() após o uso do RecordEnumeration, já que ele libera os recursos utilizados pelo RecordEnumeration.

 

* RecordFilter: é uma interface que permite a implementação de um filtro através do qual é possível fazer uma pesquisa mais otimizada ao RecordStore. Por exemplo, supondo que o nosso RecordStore armazene valores podemos com o RecordFilter fazer uma busca pelos Record´s com valores acima de 100, por exemplo. Para isso basta implementar o único método desta interface, matches(byte[] dado),  ele recebe como parâmetro um array de byte e deve retornar um boolean sendo true caso verdadeiro false quando falso;

 

* RecordComparator é mais uma interface, assim como a anterior possui apenas um método a ser implementado public int compare(byte[] primeiro, byte[] segundo), esse deve retornar uma das seguintes constantes, RecordComparator.EQUIVALENT, quando o parâmetro primeiro for igual ao segundo, RecordComparator.PRECEDES, indica que o parâmetro primeiro é anterior ao segundo para se fazer uma ordenação e por fim RecordComparator.FOLLOWS, que indica que o parâmetro primeiro é posterior ao segundo.

 

Caso não se queira fazer uso do filtro ou do comparador deve-se apenas passar estes parâmetros como null, na chamada do método que deve nos retornar um RecordEnumeration, dessa forma: enumerateRecords(null, null, false);

 

Agora que já vimos como trabalhar com RecordEnumeration podemos então partir para o segundo exemplo, onde nossa aplicação irá armazenar dados sobre os produtos de uma loja de cd´s, o nome dos álbuns e a quantidade e consequentemente nos retornar listas mais otimizadas para fazer consultas, uma vez que digitamos uma quantidade e apertamos no botão de listar, nesse caso serão listados apenas os cd´s com quantidade maior ou igual a colocada .

 

Nesse exemplo trabalhamos com cinco classes , uma Midlet (Loja.java), uma classe que cuida da persistência (Gravacao.java), uma classe que representa o objeto persistido (Estoque.java), uma classe responsável por listar o estoque (ExibirLista.java) e por fim uma classe que representa a exceção (ParametroInvalidoException.java). Vamos agora falar um pouco sobre cada uma delas.

 

Primeiramente apresento o código da Midlet, Loja.java. Desta vez, esta classe possui elementos cuja importância vão além da questão visual, é nela que estão implementadas, através de classes internas, as interfaces que representam o RecordFilter e o RecordComparator. Estes objetos são obtidos através de métodos privados que devem retorna-los.

 

* private RecordFilter retornaFiltro(final int quant)

* private RecordComparator retornaComparador()

 

O primeiro filtra com base na quantidade recebida através do que é digitado no campo quantidade e é responsável pelo retorno dos álbuns cuja quantidade seja maior ou igual a fornecida.

 

O segundo é responsável pelo ordenamento, este ordenamento é feito com base na ordem alfabética dos álbuns, assim os álbuns são listados em ordem alfabética.

 

Dessa vez serão vistos estruturas novas em nosso código porque estamos trabalhando com um objeto um pouco mais complexo que um String, daí a diferença no momento da conversão do nosso objeto para um array de byte, necessário nas operações efetuadas pelo RecordStore. Logo abaixo o código de Loja.java.

 

package devmedia.com.br.loja;

import java.io.ByteArrayInputStream;

import java.io.DataInputStream;

import java.io.IOException;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.Form;

import javax.microedition.lcdui.TextField;

import javax.microedition.Midlet.Midlet;

import javax.microedition.Midlet.MidletstateChangeException;

import javax.microedition.rms.RecordComparator;

import javax.microedition.rms.RecordFilter;

 

public class Loja extends Midlet implements CommandListener {

      private Display display= null;

      private Form form = null;

      private TextField album = null;

      private TextField quantidade = null;

      private Command gravar = null;

      private Command listar = null;

      private Command sair = null;

      private Gravacao gravacao = null;

      private ExibirLista el = null;

     

      public Loja() {

            display = Display.getDisplay(this);

            gravacao = new Gravacao();

            form = new Form("Loja de CD´s");

            el = new ExibirLista(display,form);

            album = new TextField("Album: ","",20,TextField.ANY);

            quantidade = new TextField("QUANTIDADE: ","0",6,TextField.NUMERIC);

            gravar = new Command("GRAVAR",Command.SCREEN,1);

            listar = new Command("LISTAR",Command.SCREEN,1);

            sair = new Command("SAIR",Command.EXIT,2);

            form.addCommand(gravar);

            form.addCommand(listar);

            form.addCommand(sair);

            form.append(album);

            form.append(quantidade);

            form.setCommandListener(this);

           

      }

     

      protected void startApp() throws MidletstateChangeException {

            display.setCurrent(form);}

     

      protected void pauseApp() {}

     

     protected void destroyApp(boolean arg0) throws MidletstateChangeException {

            gravacao.fecharRecordStore();}

     

      public void commandAction(Command c, Displayable d) {

            if(c == gravar){

                  if(quantidade.getString().equals(""))

                        {quantidade.setString("0");}

                  gravacao.gravarEstoque(album.getString(),

                                      Integer.parseInt(quantidade.getString()));

            }else if(c == listar){

                  if(quantidade.getString().equals(""))

                       {quantidade.setString("0");}

                  el.listarRecorStore(gravacao.retornaAlbuns(

                      retornaFiltro(Integer.parseInt(quantidade.getString())),
                      retornaComparador()));

            }else if(c==sair){

                  try {

                        destroyApp(false);

                  } catch (MidletstateChangeException e) {

                        e.printStackTrace();

                  }

                  notifyDestroyed();}}

     

      private RecordFilter retornaFiltro(final int quant){

            RecordFilter filter = new RecordFilter(){

                 

                  public boolean matches(byte[] dados) {

                        DataInputStream

                        dis = new

                               DataInputStream(new ByteArrayInputStream(dados));

                        try {

                             dis.readUTF();

                             int quantidade = dis.readInt();

                             return quant<=quantidade;

                        } catch (IOException e) {

                             return false;

                        }}}; return filter;}

 

      private RecordComparator retornaComparador(){

            RecordComparator comparator = new RecordComparator(){

                  public int compare(byte[] dados1, byte[] dados2) {

                        DataInputStream

                        dis1 = new

                             DataInputStream(new ByteArrayInputStream(dados1));

                        DataInputStream

                        dis2 = new

                             DataInputStream(new ByteArrayInputStream(dados2));

                        String nome1 = null;

                        String nome2 = null;

                        try {

                             nome1 = dis1.readUTF();

                             nome2 = dis2.readUTF();

                        } catch (IOException e) {

                             return RecordComparator.EQUIVALENT;

                        }    

                        int comp = nome1.compareTo(nome2);

                        if(comp<0){

                             return RecordComparator.PRECEDES;

                        }else if(comp >0){

                             return RecordComparator.FOLLOWS;

                        }

                  return RecordComparator.EQUIVALENT;

                  }    

            };return comparator;}} 

 

Vamos falar agora da nossa classe Gravacao. O seu funcionamento está muito parecido com o da classe Gravacao do exemplo anterior, a única diferença está na modificação no nome de alguns métodos. O método que faz a gravação dos dados no RecordStore tem essa assinatura:

 

* public void gravarEstoque(String album, int quantidade): ele recebe o nome do álbum, a quantidade e dentro dele executa a instanciação do objeto Estoque. Logo depois disso faz a conversão do objeto em um array de byte para então poder grava-lo da mesma forma como se procedeu no exemplo anterior.

 

A outra diferença esta na assinatura e na forma de implementação do método que antes devolvia um array de String. Sua nova assinatura é:    

 

* public RecordEnumeration retornaAlbuns(RecordFilter filtro, RecordComparator comparador):  Ele agora apresenta como retorno um RecordEnumeration o que fez com que sua implementação se desse de forma bem simples que no método anterior quando retornava um array de String.

 

      public void gravarEstoque(String album, int quantidade){

            if(album==null || quantidade <0){

                  try {

                        throw new ParametroInvalidoException();

                  } catch (ParametroInvalidoException e) {

                  }}else{

                        Estoque estoque = new Estoque();

                        estoque.setAlbum(album);

                        estoque.setQuantidade(quantidade);

                       

                        ByteArrayOutputStream baos =new ByteArrayOutputStream();

                        DataOutputStream dos = new DataOutputStream(baos);

                        try {

                             dos.writeUTF(estoque.getAlbum());

                             dos.writeInt(estoque.getQuantidade());

                             dos.close();

                        } catch (IOException e) {

                             e.printStackTrace();

                        }

                        byte[] dados = baos.toByteArray();

                        try {

                              rs.addRecord(dados,0,dados.length);

                        } catch (RecordStoreNotOpenException e) {

                             e.printStackTrace();

                        } catch (RecordStoreFullException e) {

                             e.printStackTrace();

                        } catch (RecordStoreException e) {

                             e.printStackTrace();}}}

     

      public RecordEnumeration retornaAlbuns(RecordFilter filtro,

                                             RecordComparator comparador){

            RecordEnumeration re = null;

            try {

                   re = rs.enumerateRecords(filtro,comparador,false);

            } catch (RecordStoreNotOpenException e) {

                  e.printStackTrace();

            }return re;}

 

 

A classe ExibirLista sofreu uma modificação no método responsável pela Geração da Lista de dados Gravados. Este passou a ter uma nova assinatura e consequentemente uma nova implementação.

        

*  public void listarRecorStore(RecordEnumeration re):  Ele passou a receber como parâmetro um RecordEnumeration e a partir dele povoar a lista, como estamos lidando agora com dados um pouco mais complexos, sua implementação a forma de recuperação desses dados passou a ser um pouco diferente, entretanto este método continua ainda focado na sua principal tarefa, que é a de preparar e  exibir a lista com os dados armazenados a apartir do RecordEnumeration que ele recebe. Antes de exibir a lista preparada ele elimina os recursos consumidos pelo RecordEnumeration a apartir do método destroy(). Devemos lembrar que isso é muito importante já que no universo dos dispositivos moveis a cautela no consumo de recursos é essencial.  

     

      public void listarRecorStore(RecordEnumeration re){

            List lista = new List("Listar CD´s",List.IMPLICIT);

            lista.addCommand(voltar);

            lista.setCommandListener(this);

                  while(re.hasNextElement()){

                        byte[] est = null;

                        try {

                              est = re.nextRecord();

                        } catch (InvalidRecordIDException e) {

                             e.printStackTrace();

                        } catch (RecordStoreNotOpenException e) {

                             e.printStackTrace();

                        } catch (RecordStoreException e) {

                             e.printStackTrace();

                        }

                        DataInputStream

dis = new DataInputStream(new ByteArrayInputStream(est));

                        String album = null;

                        try {

                             album = dis.readUTF();

                             album = album + " - "+ dis.readInt();

                        } catch (IOException e) {

                             e.printStackTrace();

                        }

                        lista.append(album,null);

                  }

            re.destroy();

            this.display.setCurrent(lista);}

 

Por fim mostramos o objeto Estoque, trata-se de uma espécie de bean, já que tudo que possui é um construtor vazio e métodos get e set para as suas duas propriedades album(String) e quantidade(int).

 

Conclusão

O RMS é um recurso muito interessante disponibilizado por JME, entretanto seu suo deve ser feito com cautela porque ao administra-lo deve-se observar as limitações de recursos do aparelho, afim de que sejam evitadas situações desagradáveis como o consumo excessivo dos recursos do aparelho por parte de RecordStore e o consumo da sua capacidade de processamento ao se procurar fazer leituras em RecordStore muito extensos. Contudo, apesar da existência desses percalços, a API RecordStore fornece ao programador os recursos necessários para se trabalhar com a persistência dentro da aplicação.

 

Bibliografia 

·         TOPLEY, kim.  J2ME in a nutshell – A Desktop Quick Reference. Editora: O’Reilly, Março 2002

·         MUCHOW, John W. Core J2ME, Technology e MIDP. Editora: Prentice Hall PTR, Dezembro 2001

·         GOSH, soma.J2ME Record Management Store. Disponivel em http://www-128.ibm.com/developerworks/wireless/library/wi-rms/

·         RecordStoreAPI: http://www.j2medev.com/api/midp/javax/microedition/rms/RecordStore.html

·         RecordEnumerationAPI: http://www.j2medev.com/api/midp/javax/microedition/rms/RecordEnumeration.html

·         RecordFilterAPI: http://www.j2medev.com/api/midp/javax/microedition/rms/RecordFilter.html

·         RecordComparatorAPI: http://www.j2medev.com/api/midp/javax/microedition/rms/RecordComparator.html

Leia também