Persistência em JME – Parte I

 

Introdução

A persistência é um importante conceito na programação, afinal de contas é preciso decidir qual a melhor maneira de armazenar a informação depositada pelo usuário no sistema, até mesmo porque nem sempre poderemos ficar utilizando a memória volátil para armazenar as informações utilizadas pela aplicação, inclusive porque a máquina em que está a aplicação será “um dia” desligada.

 

Assim como em aplicações desktop, as aplicações desenvolvidas para dispositivos portáteis podem, em determinadas situações, necessitar armazenar de forma mais “segura” ou menos volátil determinados tipos de dados por elas produzidas. Um porém é que todo esse trabalho deve ser feito levando-se em consideração as limitações existentes nos dispositivos portáteis. A solução oferecida por JME para o armazenamento de informação em memória não volátil é o RMS (Record Management System) o Sistema de Gerenciamento de Gravação.

 

O RMS trabalha persistindo as informações geradas pela aplicação em uma espécie de tabela ou arquivo(RecordStore) e posteriormente recorrendo a ela(s) para recuperar as informações necessárias.

 

Uma forma de se imaginar o RMS é como uma tabela composta de linhas sendo que cada uma dessas linhas irá possuir um identificador, uma chave, através da qual os registros serão recuperados. Cada linha dessa tabela nós chamamos de Record, sendo que cada Record composto por um id (chave), que é um inteiro, e um array de bytes onde são armazenados os dados. Essa tabela composta  por Records chamamos de RecordStore.

 

Uma mesma Midlet pode manter mais de um RecordStore, onde cada um destes é identificado por um nome, onde esses são case sensitive, além disso duas ou mais Midlets podem compartilhar um RecordStore. A esse conjunto de Midlets que podem acessar um mesmo RecordStore chamaremos Midlet suite. Se essa Midlet suite for retirada, apagada todos os seus RecordStore serão removidos juntamente com ela.

 

Metodos fornecidos pelo recordStore

No momento, para que se entenda o exemplo mais adiante, vou comentar alguns métodos da  API RecordStore, na verdade os métodos que comentarei são os mais simples, triviais, entretanto de conhecimento indispensável para quem quiser se aventurar a trabalhar com RMS. Mais adiante apresentarei outros métodos desta API, mas para um “bate bola” inicial esses que eu apresentarei são suficientes.

 

·         openRecordStore(String nome, boolean criarCasoNãoExista): Antes de tudo, a classe RecordStore não possui construtor. A obtenção de um RecordStore se dá através do método openRecordStore(String nome, boolean criarCasoNãoExista). Nesse método o parâmetro nome se refere a identificação do RecordStore e o criar trata da opção de que seja criado um RecordStore com aquele nome, caso ainda não exista nenhum. Preste atenção que o nome do método faz referencia a abertura e não criação de um RecordStore, o que torna bastante interessante a existencia desse parâmetro, já que podem existir situações em que não é interessante esta criação, como por exemplo quando uma vez criado e fechado o RecordStore, não é necessário permitir que seja criado um novo RecordStore quando se for abrir aquele que estava fechado.

·         closeRecordStore(): Já que falamos do método que serve para abrir um RecordStore, falemos agora sobre este, que como o nome já indica serve para fechar um RecordStore aberto.

·         deleteRecordStore(String nomeDoRecordStore): Esse método é utilizado para se apagar um RecordStore, cujo nome de identificação é o parâmetro para que o método saiba qual RecordStore de eliminar.

        

Agora que vimos, ainda que de maneira superficial, os métodos básicos para se trabalhar com um RecordStore, proponho atravessar a fronteira do RecordStore e partirmos para analisar o trabalho com os Record´s que irão compor esse RecordStore.

 

·         int addRecord(byte[] dados, int offset, int numBytes): Esse método é utilizado para adicionar um uma linha de dados ao RecordStore. A utilização desse método faz necessário se passar como parâmetros um array de byte correspondente aos dados que se queira armazenar, um inteiro correspondente ao offset que é o primeiro indice do array de byte que deve ser lido e por fim um inteiro correpondente ao tamanho do array de byte do dado que se quer armazenar. O retorno desse método é um int que corresponde ao id da linha adicionada ao RecordStore.

·         setRecord(int id, byte[] novosDados, int offset, int numBytes):  Esse método é muito parecido com o anterior, na verdade ele serve para fazer a atualização dos dados que fazem parte de uma linha, o RMS não possui  uma espécie de “save and update”. Assim, pode-se notar que a diferença deste método para o anterior, além da assinatura é claro, é  a  existência do parâmetro id , usado para especificar o Record que se deseja alterar.   

·         deleteRecord(int id): Como o próprio nome indica, esse método serve para se apagar um Record, para tanto é necessário apenas passar o id do Record que se deseja eliminar.

·         byte[] getRecord(int id): Esse método deve ser utilizado para recuperar um determinado Record do RecordStore. Para tanto é necessário apenas passar como parâmetro o id  do Record que se deseja pegar do RecordStore e ele devolverá um array de byte correspondente ao conteúdo da linha identificada pelo id.

·         int getNumRecords(): Esse método é utilizado para se obter a quantidade de Records existente em um RecordStore. Para isso ele apresenta como retorno um inteiro que representa as linhas desse RecordStore

 

Agora que vimos o básico do RecordStore podemos analisar o nosso primeiro exemplo, uma pequena e simples aplicação onde é aplicado um pouco do que até então foi falado. Uma coisa bem legal de se ver na API RecordStore é que tanto nos métodos utilizados para se fazer inserção/atualização de dados como no método para a recuperação de um dado, trabalha-se com array de byte, o que abre um leque muito grande de possibilidades para o programador, já que dessa forma, pode-se trabalhar com o armazenamento desde um simples String até mesmo de uma imagem, bastando para isso bazer a conversão do item desejado para byte e o caminho inverso para recuperá-lo.

 

Exemplo 1:

Para escrever este primeiro exemplo usei quatro classes, uma Midlet (MagicBook.java), uma classe responsável pelo trabalho de armazenamento (Gravacao.java), uma pela exibição dos elementos do RecordStore (ExibirLista.java) e uma Exceção (ParametroNuloException.java).

 

O nosso primeiro exemplo trata de uma aplicação muito simples, que grava ‘palavras mágicas’ e que no final pode listá-las para o usuário (veja a Figura 01, 02 e 03). Mais adiante veremos funções mais interessantes dentro do RMS, por hora fiquemos com esse exemplo que demonstra autilização de uma parte dos conceitos básicos.

 

MagicBook - cadastro.bmp

Figura 01. Imagem da aplicação Palavras Mágicas - Cadastrando palavras.

           

Primeiramente o código da Midlet, MagicBook.java.

 

package devmedia.com.br.persistencia;

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;

 

public class MagicBook extends Midlet implements CommandListener {

      private Display display= null;

      private Form form = null;

      private TextField box = null;

      private Command gravar = null;

      private Command listar = null;

      private Command sair = null;

      private Gravacao gravacao = null;

      private ExibirLista el = null;

     

      public MagicBook() {

            display = Display.getDisplay(this);

            gravacao = new Gravacao();

            form = new Form("Palavras Mágicas");

            el = new ExibirLista(display,form);

            box = new TextField("Palavra: ","",20,TextField.ANY);

            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(box);

            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){

                  gravacao.gravarPalavra(box.getString());

            }else if(c == listar){

                  String[] registros = gravacao.retornaPalavras();

                  el.listarRecorStore(registros);

            }else if(c==sair){

                  try {

                        destroyApp(false);

                  } catch (MidletstateChangeException e) {

                        e.printStackTrace();

                  }

                  notifyDestroyed();}    

      }    

}

 

MagicBook - listando.bmp

Figura 02. Imagem da aplicação Palavras Mágicas - Listando palavras.

 

Esta classe está escrita de forma bem simples, possui os elementos básicos que podem compor o display de uma aplicação como Command, Form e TextField, não vou dar explicação sobre esses elementos, porque isso foge do escopo deste artigo. A classe Gravação é instanciada no construtor da Midlet. Sobre a classe Gravacao falaremos agora.

 

package devmedia.com.br.persistencia;

import javax.microedition.rms.InvalidRecordIDException;

import javax.microedition.rms.RecordStore;

import javax.microedition.rms.RecordStoreException;

import javax.microedition.rms.RecordStoreFullException;

import javax.microedition.rms.RecordStoreNotFoundException;

import javax.microedition.rms.RecordStoreNotOpenException;

 

public class Gravacao{

     

      private RecordStore rs;

     

      public Gravacao(){     

            try {

                  rs = RecordStore.openRecordStore("bookOfTrueTales",true);

            } catch (RecordStoreFullException e) {

                  e.printStackTrace();

            } catch (RecordStoreNotFoundException e) {

                  e.printStackTrace();

            } catch (RecordStoreException e) {

                  e.printStackTrace();

            }

      }

     

      public void fecharRecordStore(){

            try {

                  rs.closeRecordStore();

            } catch (RecordStoreNotOpenException e) {

                  e.printStackTrace();

            } catch (RecordStoreException e) {

                  e.printStackTrace();

            }

      }

     

      public void gravarPalavra(String palavra){

            if(palavra==null){

                  try {

                        throw new ParametroNuloException();

                  } catch (ParametroNuloException e) {

                        e.printStackTrace();

                  }}else{

                        byte[] dados = palavra.getBytes();

                        try {

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

                        } catch (RecordStoreNotOpenException e) {

                             e.printStackTrace();

                        } catch (RecordStoreFullException e) {

                             e.printStackTrace();

                        } catch (RecordStoreException e) {

                             e.printStackTrace();

                        }

                  }

      }

     

      public String[] retornaPalavras(){

            int total = 0;

            try {

                  total = rs.getNumRecords();

            } catch (RecordStoreNotOpenException e) {

            }

            String[] registros = new String[total];

            for(int i=1;i

                  String palavra = null;

                  try {

                        palavra = new String(rs.getRecord(i));

                       

                  } catch (RecordStoreNotOpenException e) {

                        e.printStackTrace();

                  } catch (InvalidRecordIDException e) {

                        e.printStackTrace();

                  } catch (RecordStoreException e) {

                        e.printStackTrace();

                  }

                  registros[i-1] = palavra;

 

            }

            return registros;}

}

 

Essa classe sim merece uma atenção especial porque ela é o coração do nosso artigo, agora vou passar a comentar os seus métodos, para que seja visto como foram utilizados.

 

O construtor desta classe não recebe parâmetros, sua única função é abrir o RecordStore, através da linha: RecordStore.openRecordStore("bookOfTrueTales",true);  que será utilizado pelos demais métodos da  classe.

 

O método gravarPalavra(String palavra)  recebe como parâmetro a palavra que será gravada, após isso ele a transforma em um array de byte e encaminha ela para o método addRecord de RecordStore, onde são passados respectivamente como parâmetros o array de byte, o indice do array e o tamanho do array.

 

O método fecharRecordStore é utilizado para fechar o RecordStore, ele é chamado dentro do destrutor da de MagicBook, que é chamado quando se escolhe a opção sair.

Por fim, vemos o método retornaPalavras(), este retorna um array de String, onde estão o conteúdo de cada registro do RecordStore. Ele verifica o tamanho do RecordStore (total = rs.getNumRecords();) e o percorre através de um for pegando os seus elementos com rs.getRecord(i), convertendo-os em String e então passando estes para um array, que é o retorno desse método. Uma coisa importante é que o id dos registros  do RecordStore começam de 1 e não de 0.

 

MagicBook - palavrasMágicas.bmp

Figura 03. Imagem da aplicação Palavras Mágicas.

 

Agora uma breve olhada no código da classe ExibirLista, no momento sua única função é listar os registros do RecordStore através do método listarRecordStore() que recebe como parâmetro um array de String. Esse método recebe o conteudo do array e os coloca em uma lista que será exibida. O seu construtor recebe um Display e um Form, provenientes de MagicBook.

 

package devmedia.com.br.persistencia;

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.List;

 

public class ExibirLista implements CommandListener {

     

      private Display display;

      private Form form;

      private Command voltar;

     

      public ExibirLista(Display display, Form form) {

            this.display = display;

            this.form = form;

            voltar = new Command("VOLTAR",Command.BACK,1);

      }

     

      public void listarRecorStore(String[] linhas){

            List lista = new List("Listar Palavras",List.IMPLICIT);

            lista.addCommand(voltar);

            lista.setCommandListener(this);

            for(int i=0; i

                  String palavra = linhas[i];

                  lista.append(palavra,null);

            }

            this.display.setCurrent(lista);

      }

 

      public void commandAction(Command c, Displayable d) {

f(c==voltar){display.setCurrent(form);}}}

 

Por fim temos a nossa classe Exceção, utilizada apenas para personalizarmos um problema que podemos vir a enfrentar no sistema e consequentemente tornar mais fácil o entendimento do código. Abaixo está o código dela.

 

package devmedia.com.br.persistencia;

public class ParametroNuloException extends Exception {   

public ParametroNuloException() {super();}

     

public ParametroNuloException(String frase) {super(frase);}      }

Leia também