Mostrando las entradas con la etiqueta J2ME. Mostrar todas las entradas
Mostrando las entradas con la etiqueta J2ME. Mostrar todas las entradas

jueves, 2 de abril de 2015

Ejemplo de conexión de JavaME con periférico por puerto serial/USB

Todos los equipos Nextel con Java ME tienen la capacidad de establecer una conexión de datos por el puerto serial o mini USB desde una aplicación Java.
En la terminal receptora, es simplemente necesario poder detectar al equipo conectado como si fuese un modem.
Es muy sencillo. El código adjunto ofrece un ejemplo.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.Connector;
import javax.microedition.io.CommConnection;
import java.io.IOException;
import java.io.DataOutputStream; public class SocketTest extends MIDlet implements CommandListener {
    private boolean midletPaused = false;
    private Command exitCommand;
    private Command okCommand;
    private Form form;
    private StringItem stringItem;
    /**
     * The SocketTest constructor.
     */

    public SocketTest() {
    }
    /**
     * Initilizes the application.
     * It is called only once when the MIDlet is started. The method is called before the startMIDlet method.
     */

    private void initialize() {
    }
    /**
     * Performs an action assigned to the Mobile Device - MIDlet Started point.
     */

    public void startMIDlet() {
        switchDisplayable(null, getForm());
    }
    /**
     * Performs an action assigned to the Mobile Device - MIDlet Resumed point.
     */

    public void resumeMIDlet() {
    }
    /**
     * Switches a current displayable in a display. The display  instance is taken from  getDisplay method. This method is used by all actions in the design for switching displayable.
     * @param alert the Alert which is temporarily set to the display; if null, then nextDisplayable is set immediately
     * @param nextDisplayable the Displayable to be set
     */

    public void switchDisplayable(Alert alert, Displayable nextDisplayable) {
        Display display = getDisplay();
        if (alert == null) {
            display.setCurrent(nextDisplayable);
        } else {
            display.setCurrent(alert, nextDisplayable);
        }
    }
    /**
     * Called by a system to indicated that a command has been invoked on a particular displayable.
     * @param command the Command that was invoked
     * @param displayable the Displayable where the command was invoked
     */

    public void commandAction(Command command, Displayable displayable) {
        if (displayable == form) {
            if (command == exitCommand) {
                exitMIDlet();
            } else if (command == okCommand) {
                openSocketConnection();
            }
        }
    }
    /**
     * Returns an initiliazed instance of exitCommand component.
     * @return the initialized component instance
     */

    public Command getExitCommand() {
        if (exitCommand == null) {
            exitCommand = new Command("Exit", Command.EXIT, 0);
        }
        return exitCommand;
    }
    /**
     * Returns an initiliazed instance of form component.
     * @return the initialized component instance
     */

    public Form getForm() {
        if (form == null) {
            form = new Form("Prueba", new Item[] { getStringItem() });
            form.addCommand(getExitCommand());
            form.addCommand(getOkCommand());
            form.setCommandListener(this);
        }
        return form;
    }
    /**
     * Returns an initiliazed instance of stringItem component.
     * @return the initialized component instance
     */

    public StringItem getStringItem() {
        if (stringItem == null) {
            stringItem = new StringItem("Prueba", "Prueba conexion serial");
        }
        return stringItem;
    }
    /**
     * Returns an initiliazed instance of okCommand component.
     * @return the initialized component instance
     */

    public Command getOkCommand() {
        if (okCommand == null) {
            okCommand = new Command("Ok", Command.OK, 0);
        }
        return okCommand;
    }
    /**
     * Returns a display instance.
     * @return the display instance.
     */

    public Display getDisplay () {
        return Display.getDisplay(this);
    }
    /**
     * Exits MIDlet.
     */

    public void exitMIDlet() {
        switchDisplayable (null, null);
        destroyApp(true);
        notifyDestroyed();
    }
    /**
     * Called when MIDlet is started.
     * Checks whether the MIDlet have been already started and initialize/starts or resumes the MIDlet.
     */

    public void startApp() {
        if (midletPaused) {
            resumeMIDlet ();
        } else {
            initialize ();
            startMIDlet ();
        }
        midletPaused = false;
    }
    /**
     * Called when MIDlet is paused.
     */

    public void pauseApp() {
        midletPaused = true;
    }
    /**
     * Called to signal the MIDlet to terminate.
     * @param unconditional if true, then the MIDlet has to be unconditionally terminated and all resources has to be released.
     */

    public void destroyApp(boolean unconditional) {
    }
    public void openSocketConnection() {
        try {
        CommConnection cc = (CommConnection)Connector.open("comm:com0;baudrate=19200");
        DataOutputStream dos = cc.openDataOutputStream();
        dos.writeChars("Sample output");
        dos.flush();
        cc.close();
        }
        catch (IOException ex){
            Alert alert = new Alert("Exception: ", ex.getMessage() ,null,AlertType.ERROR);
            switchDisplayable(alert,getDisplay().getCurrent());
        }
    }
}

J2ME, Java Wireless Message API (WMA)

Sin lugar a dudas, la mensajería instantánea a través de mensajes cortos (SMS) es una de las formas de comunicación más extendida y aceptadas por la sociedad.
En este tutorial voy a intentar hacer una introducción de las características más importantes que nos proporciona Java para el envío y recepción de SMS desde aplicaciones para móviles (MIDLets). Se presupone que el lector ya posee conocimientos básicos de programación (J2ME, MIDP, CLDC), compilación e instalación de MIDLets.

Introducción a WMA

WMA, son las siglas de Wireless Message API (curiosamente también lo son de uno de los formatos de audio de Windows, Windows Media Audio), una extensión de la especificaciones del CLDC y MIDP para el envio, la recepción y la gestión de SMS desde MIDLets.
A pesar de ser una extensión opcional, la gran mayoría de los terminales la llevan instalada y lista para ser usuada desde nuestras aplicaciones J2ME.
WMA es una especificación no una implementación. Su implementación dependerá del terminal y del protocolo de comunicación que use (GSM, CDMA, etc). Por supuesto, nosotros como desarrolladores podemos abstraernos de esto último.
Versiones de la especificación WMA:
Versión 1.0: Especificación inicial. Describe las funcionales básicas para el envío y la recepción de SMS. (JSR 120)
Versión 1.1: Una ampliación de la especificación 1.0, para soportar el nuevo modelo de seguridad del MIDP 2.0.
Versión 2.0: Una ampliación a las anteriores para la gestión de mensajes multimedia (MMS). (JSR 205)

Introducción al API WMA

El API está compuesto exclusivamente de interfaces ubicadas bajo el páquete javax.wireless.messaging. Estas interfaces son:
javax.wireless.messaging.Message: Define la funcionalidad genérica de todos los tipos de mensajes. Permite:
  1. Especificar el destinatario del mensaje. public void setAddress(String addr)
  2. Obtener el emisor del mensaje. public String getAddress()
  3. Obtener la fecha de envio del mensaje. java.util.Date getTimestamp()
javax.wireless.messaging.TextMessage: Representa a un mensaje de texto.
Hereda la funcionalidad de javax.wireless.messaging.Message añadiendo los métodos public void setPayloadText(String data) y public String getPayloadText() para especificar u obtener los datos del mensaje.
javax.wireless.messaging.BinaryMessage: Representa a un mensaje binario.
Hereda la funcionalidad de javax.wireless.messaging.Message añadiendo los métodos public void setPayloadData(byte[] data) y public byte[] getPayloadData() para especificar u obtener los datos del mensaje.
javax.wireless.messaging.MessageListener: Oyente de mensajes entrantes.
Esta interfaz es útil en javax.wireless.messaging.MessageConnection que funcionan en modo servidor. (Será explicada más adelante.)
javax.wireless.messaging.MessageConnection: Interfaz a través de la cual se realiza el envío y la recepción de mensajes. (Será explicada más adelante.)

¿Qué pasos tengo que realizar para enviar un mensaje (SMS)?

  1. Obtener un MessageConnection en modo cliente (Se verá más adelante).
  2. Crear el mensaje a través de la interfaz MessageConnection.
  3. Especificar el contenido y el destinatario del mensaje.
  4. Usar el método send de la interfaz MessageConnection para enviar el mensaje.

¿Qué pasos tengo que realizar para recibir un mensaje (SMS) de forma asíncrona?

  1. Obtener un MessageConnection en modo servidor (Se verá más adelante).
  2. Implementar la interfaz MessageListener en una de nuestras clases.
  3. Asociar la clase anterior al MessageConnection.
  4. Cuando el método notifyIncomingMessage() de la interfaz MessageListener sea invocado, significa que hemos recibido un mensaje.
  5. Deberemos invocar el método receive() de la interfaz MessageConnection en un hilo independiente.
  6. Realizar el tratamiento del mensaje.

¿Qué pasos tengo que realizar para recibir un mensaje (SMS) de forma síncrona?

  1. Obtener un MessageConnection en modo servidor (Se verá más adelante).
  2. Invocar el método receive() de la interfaz MessageConnection en un hilo independiente (Es un método bloqueante).
  3. Realizar el tratamiento del mensaje.

En J2ME, todas las comunicaciones que requieren los MIDLets con el exterior (Bluetooth, Socket, Http, etc.) se obtienen a través la clase javax.microedition.io.Connector que forma parte del CLDC. Esta clase devuelve una instancia de una clase que implementa la interfaz javax.microedition.io.Connection para el modo de comunicación deseada. Pues bien, la interface javax.wireless.messaging.MessageConnection no es más que un javax.microedition.io.Connection para comunicación via SMS.
Básicamente a través de está inteface podemos crear, enviar y recibir SMS tanto de forma síncrona como asíncrona.

¿Cómo obtengo javax.wireless.messaging.MessageConnection?

Los javax.wireless.messaging.MessageConection pueden funcionar de dos modos:
  1. En modo cliente: Sólo sirve para enviar SMS a un destinatario.
  2. En modo servidor: Sirve para recibir y tratar los SMS que son dirigidos hacia el. En esté modo también se pueden enviar SMS.
La forma de especificar el modo de funcionamiento deseado se realiza a través de la cadena de conexión que se le pasa a la clase javax.microedition.io.Connector.
Ejemplos de como especificar el modo cliente:
MessageConnection conn = (MessageConnection) javax.microedition.io.Connector.open("sms://+34666666666");
MessageConnection conn = (MessageConnection) javax.microedition.io.Connector.open("sms://+34666666666:5555"); En ambos casos el SMS sería enviado al teléfono 666666666 de España (por el prefijo +34), pero con una importante diferencia. En el primer caso, el SMS sería tratado por la aplicación que por defecto tiene instalada el teléfono, mientras que, en el segundo caso, el SMS sería tratado por la aplicación que esté escuchando en ese puerto.
Ejemplo de como especificar el modo servidor:
MessageConnection conn = (MessageConnection) javax.microedition.io.Connector.open("sms://:5555");
Los SMS que llegen al puerto 5555 serán atendidos por el MIDLet. En realidad, "5555" NO es un puerto sino un IDENTIFICADOR que le indica a la plataforma java que desea tratar los SMS que llegen al terminal con ese identificador.

Métodos de la clase:

public javax.wireless.messaging.Message newMessage(java.lang.String type, java.lang.String address)
Crea un nuevo mensaje para ser enviado.
En el argumento type especificamos el tipo de mensaje que deseamos enviar:
   MessageConnection.TEXT_MESSAGE para mensajes de texto
   MessageConnection.BINARY_MESSAGE para mensajes binarios.
En el argumento address debemos especificar la dirección del destinatario del mensaje. Normalmente, su número de teléfono.
public int numberOfSegments(javax.wireless.messaging.Message msg)
Devuelve el número de segmentos que son necesarios para enviar la información a través de la red.
Por ejemplo, si queremos enviar el Quijote via SMS pues seguro que son necesarios más de un segmento (un segmento es igual a un SMS como lo conocen los usuarios) y este método nos devolvería o bien un número grande o bien el valor 0 indicando que no se puede enviar la información deseada.
public javax.wireless.messaging.Message receive() throws IOException, InterruptedIOException
Devuelve un mensaje enviado a nuestra aplicación. Hay que tener varias cosas importantes en mente:
  1. Es un método bloqueante, por lo que generalmente deberá ser invocado en un hilo distinto al hilo principal donde está ejecutandose el Midlet.
  2. Nuestra aplicación es responsable de guardar la información recibida a memoria no volátil en caso de ser necesario.
public void send(javax.wireless.messaging.Message msg) throws IOException, InterruptedIOException
Envia el mensaje al destinatario.
Este método debe ser invocado en un hilo distinto al hilo principal donde está ejecutandose el Midlet.
Para especificar el destinatario del mensaje se debe usar el método setAddress(java.lang.String addr) definido en la interfaz javax.wireless.messaging.Message de la que heradan javax.wireless.messaging.TextMessage y javax.wireless.messaging.BinaryMessage.
public void setMessageListener(javax.wireless.messaging.MessageListener l) throws IOException
Registrar una clase que implemante la interfaz javax.wireless.messaging.MessageListener, el plataforma J2ME invocará el método notifyIncomingMessage() cuando reciba un mensaje.
Este método sólo tiene sentido para javax.wireless.messaging.MessageConnection que funcionen en modo servidor

Ejemplo 1. Envío de un SMS en modo texto.

En este ejemplo, vamos a hacer una aplicación que envie el texto que introduzca el usuario en un área de texto a un destinatario fijo.
Para el desarrollo de aplicaciones para móviles, yo personalmente utilizo el IDE NetBeans con la extensión Mobility

  1. package autentia.tutoriales.wma;  
  2. import javax.microedition.lcdui.*;  
  3. improt javax.microedition.midlet.*;  
  4.   
  5. /** 
  6.  * MIDLet de Ejemplo del uso del API WMA (Wireless Messagin API)  
  7.  * @author Carlos García. Autentia. 
  8.  */   
  9. public class WMAMidlet extends javax.microedition.midlet.MIDlet {  
  10.     private boolean started;  
  11.       
  12.     /** 
  13.      * Constructor 
  14.      */  
  15.     public WMAMidlet(){  
  16.         this.started = false;  
  17.     }  
  18.   
  19.     /*  
  20.      * @see javax.microedition.midlet.MIDlet#startApp() 
  21.      */  
  22.     protected void startApp() throws MIDletStateChangeException {  
  23.            
  24.         // Este método puede ser incovado varias veces mientras la aplicación se esté ejecutando.   
  25.         // Por ejemplo, si se está ejecutando la aplicación y nos llega una llamada   
  26.         // entrante, la aplicación generalmente es pausada y esté método será   
  27.         // invocado cuando la llamada finalize.  
  28.           
  29.         if (! this.started){      
  30.             this.started = true;  
  31.             Display.getDisplay(this).setCurrent(new WMAMainForm(this));  
  32.         }  
  33.     }  
  34.       
  35.     /*  
  36.      * @see javax.microedition.midlet.MIDlet#destroyApp(boolean) 
  37.      */  
  38.     protected void destroyApp(boolean arg0) throws MIDletStateChangeException {  
  39.         this.destroyApp(true);  
  40.     }  
  41.   
  42.     /*  
  43.      * @see javax.microedition.midlet.MIDlet#pauseApp() 
  44.      */  
  45.     protected void pauseApp() {  
  46.         // En este ejemplo no se requiere ninguna tarea cuando el MIDLet es pausado.  
  47.     }     
  48.   
  49. }  

  1. package autentia.tutoriales.wma;  
  2.   
  3. import java.io.*;  
  4. import javax.microedition.io.*;  
  5. import javax.microedition.lcdui.*;  
  6. import javax.wireless.messaging.*;  
  7.   
  8.   
  9.   
  10. /** 
  11.  * Ventana principal de la aplicación 
  12.  * @author Carlos García. Autentia 
  13.  */   
  14. public class WMAMainForm extends javax.microedition.lcdui.TextBox   
  15.             implements javax.microedition.lcdui.CommandListener {  
  16.       
  17.     /** 
  18.      * Referencia al MIDLet 
  19.      */  
  20.     private javax.microedition.midlet.MIDlet midlet;  
  21.       
  22.     /** 
  23.      * Envia el SMS 
  24.      */  
  25.     private javax.microedition.lcdui.Command cmdSend;  
  26.       
  27.     /** 
  28.      * Finaliza la aplicación 
  29.      */  
  30.     private javax.microedition.lcdui.Command cmdExit;  
  31.       
  32.     /** 
  33.      * Constructor 
  34.      * Ventana principal de la aplicación 
  35.      */  
  36.     public WMAMainForm(javax.microedition.midlet.MIDlet midlet) {  
  37.         super("Mensaje a enviar"""166, TextField.ANY);  
  38.         this.midlet = midlet;  
  39.           
  40.         this.createUI();  
  41.     }  
  42.   
  43.     /** 
  44.      * Crea y configura el interfaz gráfico de la ventana. 
  45.      */  
  46.     private void createUI(){  
  47.         this.setTicker(new Ticker("Autentia Real Business Solutions"));  
  48.         this.cmdSend = new Command("Enviar", Command.OK,   1);  
  49.         this.cmdExit   = new Command("Salir",  Command.STOP, 1);  
  50.           
  51.         this.addCommand(cmdSend);         
  52.         this.addCommand(cmdExit);  
  53.         this.setCommandListener(this);        
  54.     }  
  55.       
  56.       
  57.     /**  
  58.       * El usuario desea enviar el SMS  
  59.      */   
  60.     private void sendSMSClick() throws java.io.IOException {   
  61.         MessageConnection conn = null;   
  62.         TextMessage msg = null;   
  63.         try {   
  64.             // Paso 1: Obtenemos una implementación del Connection que se encargará de enviar el SMS   
  65.             conn = (MessageConnection) Connector.open("sms://+34699221570");   
  66.               
  67.             // Paso 2: Creamos el SMS   
  68.             msg = (TextMessage) conn.newMessage(MessageConnection.TEXT_MESSAGE);  
  69.   
  70.             // Paso 3: Establecemos el contenido del SMS   
  71.             msg.setPayloadText(this.getString());   
  72.               
  73.             // Paso 4: Enviamos el SMS   
  74.             conn.send(msg);   
  75.         } finally {   
  76.             // Paso 5: IMPORTANTE Cerramos la conexión   
  77.             this.closeQuietly(conn);   
  78.             conn = null;   
  79.         }   
  80.     }  
  81.   
  82.       /** 
  83.        * Cierra un Connection ignorando todas las posibles excepciones 
  84.        */  
  85.         private void closeQuietly(javax.microedition.io.Connection conn){  
  86.             try {  
  87.                 conn.close();  
  88.             } catch (Exception ex){  
  89.                 // Nada  
  90.             }  
  91.         }    
  92.           
  93.     /*  
  94.      * Receptor de eventos del UI (User Interface) 
  95.      * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable) 
  96.      */  
  97.     public void commandAction(Command arg0, Displayable arg1) {  
  98.         try {  
  99.             if (arg0 == cmdSend){  
  100.                 this.sendSMSClick();  
  101.             } else if (arg0 == cmdExit){  
  102.                 this.midlet.notifyDestroyed();  
  103.             }  
  104.         } catch (Exception ex){  
  105.             // En caso de error modificamos el texto de la ventana con el mensaje  
  106.             this.setString(ex.toString());  
  107.         }  
  108.     }  
  109. }  

Ejemplo 2. Envío de un SMS en modo binario.

Los pasos para enviar información en modo binario son los mismos que para el envio de SMS en modo texto.
Los mensajes binarios son representados bajo la clase javax.wireless.messaging.BinaryMessage. La principal diferencia entre esta clase y javax.wireless.messaging.TextMessage es el método para especificar el contenido del mensaje a enviar. En este último caso, el contenido es un array de bytes y es especificado a través del método setPayloadData.
  1. /** 
  2. * El usuario desea enviar el SMS en modo binario 
  3. */  
  4.   
  5. private void sendBinarySMSClick() throws java.io.IOException {  
  6.     MessageConnection conn = null;  
  7.     BinaryMessage msg = null;  
  8.     ByteArrayOutputStream bout = null;  
  9.       
  10.     try {  
  11.         // Paso 1: Obtenemos una implementación del Connection que se encargará de enviar el SMS   
  12.         conn = (javax.wireless.messaging.MessageConnection) Connector.open("sms://+34699221570");   
  13.           
  14.         // Paso 2: Creamos el SMS   
  15.         msg = (BinaryMessage) conn.newMessage(MessageConnection.BINARY_MESSAGE);  
  16.           
  17.         // Paso 3: Establecemos el contenido del SMS con algunos datos de prueba. Por ejemplo, datos de una persona.   
  18.         bout = new ByteArrayOutputStream();   
  19.         dout = new DataOutputStream(bout);   
  20.         dout.writeBoolean(true);  // ¿Soltero?   
  21.         dout.writeByte(55); // Edad   
  22.         dout.writeUTF("Madrid"); // Provincia de nacimiento   
  23.         dout.writeUTF("España"); // País.   
  24.         dout.writeLong(888883311L); // DNI   
  25.         msg.setPayloadData(bout.toByteArray());   
  26.           
  27.         // Paso 4: Enviamos el SMS   
  28.         conn.send(msg);   
  29.     } finally {   
  30.         // Paso 5: IMPORTANTE. Cerramos las objetos, liberando recursos   
  31.         this.closeQuietly(bout);   
  32.         this.closeQuietly(dout);   
  33.         this.closeQuietly(conn);   
  34.         dout = null;   
  35.         dout = null;   
  36.         conn = null;   
  37.     }   
  38. }  
  39.   
  40.    /** 
  41.     * Cierra un OutputStream ignorando todas las posibles excepciones 
  42.     */          
  43. private void closeQuietly(java.io.OutputStream out){  
  44.        try {  
  45.            out.close();  
  46.        } catch (Exception ex){  
  47.            // Nada  
  48.        }  
  49. }  
  50.          
  51.    /** 
  52.     * Cierra un Connection ignorando todas las posibles excepciones 
  53.     */  
  54. private void closeQuietly(javax.microedition.io.Connection conn){  
  55.        try {  
  56.            conn.close();  
  57.        } catch (Exception ex){  
  58.            // Nada  
  59.        }  
  60. }     

Introducción a Push Registry.

A partir de la especificación MIDP 2.0, se añadió una potente característica a la plataforma J2ME que consiste en que nuestras aplicaciones puedan ser iniciadas por eventos externos o temporizadores.
Por ejemplo, nuestra aplicación puede ser iniciada cuando reciba un SMS en un determinado puerto (identificador).

Información interesante

A continuación os presento una tabla con información relacionada con el juego número de SMS necesarios para enviar información.

Referencia:  http://java.sun.com/products/wma/index.jsp Por lo general los mensajes de texto se envian con el juego de caracteres del GSM-7 bit, y sólo cuando el mensaje tiene caracteres que no pueden ser codificados con ese juego de caracteres se usará el UCS-2.

Conclusiones y reflexiones

En comparación con otros, este API es bastante simple y fácil de utilizar.
Sabiendo utilizar esta y otras tecnologías como las que os presentamos en Autentia a través de nuestros tutoriales, se pueden hacer sistemas interesantes y útiles.
No olvideis que esto es sólo una introducción, asi que si necesitais más información debeis dirigiros a las páginas oficiales de la especificación.
Espero que os haya parecido interesante este tutorial.

viernes, 6 de marzo de 2015

Programación en red en MIDP

OBJETIVOS
En la clase de hoy vamos a ver el soporte que da MIDP a la programación en red. Como hemos visto en la clase teórica, en el CLDC de J2ME se rediseñaron las clases para entrada/salida y para el soporte a  conexiones de red de J2SE, definiendo lo que se denomina CLDC Generic Connection Framework.

CLDC Generic Connection Framework define una serie de interfaces para dar soporte a la variedad de tipos de conexiones que nos podemos encontrar en dispositivos móviles, pero no implementa ninguna de ellas, es en los perfiles donde se debe realizar esta implementación. En el perfil con el que estamos trabajando, MIDP, se debe dar soporte obligatoriamente a conexiones HTTP, a través de la implementación del interfaz HttpConnection.

Para la práctica final no necesitais hacer uso de conexiones de red, por lo que es muy recomendable realizar
esta práctica para que completeis vuestros conocimientos sobre MIDP.



 CLDC Generic Connection Framework
En el CLDC Generic Connection Framework, todas las conexiones se crean utilizando el método estático open de la clase Connector.
Si no se produce ningún error, este método devuelve un objeto que implementa una de las interfaces definidas en el CLDC Generic Connection Framework.

Estas interfaces están contenidas en el paquete javax.microedition.io y son:
  • El interfaz Connection es el tipo básico de conexión. Esta conexión sólo puede abrirse y cerrarse.

  • El interfaz InputConnection
    representa un dispositivo desde el que se pueden leer datos. Proporciona
    el método openInputStream
    que devuelve un stream de entrada para la conexión.


  • El interfaz OuputConnection representa un dispositivo en el que se pueden escribir datos.
    Proporciona el método openOutputStream que devuelve un stream de salida para la conexión.

  • El interfaz StreamConnection  que combina la conexiones de entrada y de salida anteriores.

  • El interfaz ContectConnection  que es una subinterfaz de StreamConnection. Proporciona acceso
    a información de algunos meta datos proporcionados en un conexión HTTP.

  • El interfaz StreamConnectionNotified que espera a que se establezca una conexión. Devuelve un
    StreamConnection a través del cual puede establecerse un enlace de comunicación bidireccional.

  • El interfaz DatagramConnection que representa el destino de un datagrama.
El método open de la clase Connector tiene la siguiente sintaxis, Connector.open(String);

donde el parámtero String tiene el formato "protocol:address;parameters". Por ejemplo:
  • Conexión HTTP:
    Connector.open("http://www.it.uc3m.es/pervasive");
    
  • Conexión Datagrama:
    Connector.open("datagram://address:port#");
    
  • Conmunicación con puerto serie:
    Connector.open("comm:0;baudrate=9600");
    
  • Acceso a ficheros:
    Connector.open("file:/miFichero.txt");
    

El objetivo de tener esta sintaxis, es abstraer al programador de las diferencias que existen entre los diferentes protocolos, de manera que la mayoría del código de una aplicación no se modifica cuando se cambia el protocolo que se está usando.

IMPORTANTE: Los ejemplos de conexiones anteriores son sólo ilustrativos. CLDC en sí mismo no proporciona ninguna implementación de ningún protocolo, éstas deben de proporcionarse a nivel de perfil. Además un determinado perfil no tiene porque implementar todos los protocolos, así MIDP proporciona una implementación del protocolo HTTP, pero en general no implementa ni sockets, ni datagramas (aunque algunos fabricantes pueden incorporarlas).

En el siguiente apartado veremos el interfaz HttpConnection propocionada en MIDP.



 HttpConnection
HTTP puede implementarse utilizando protocolos IP (como TCP/IP) o protocolos no-IP (como WAP o i-mode), por este motivo se seleccionó como protocolo para comunicaciones de red en MIDP. Todas las implementaciones de MIDP deben soportarlo, garantizando de esta manera la portabilidad de las aplicaciones, que utilizan este protocolo, entre diferentes dispositivos.

En MIDP se define un nuevo interfaz dentro de la jerarquía del CLDC Generic Connection Framework, el interfaz HttpConnection para el soporte a conexiones HTTP. Este interfaz extiende del interfaz ContentConnection.

El protocolo HTTP es un protocolo a nivel de aplicación del tipo petición/respuesta, en el que los parámetros de una petición deben establecerse antes de que se envíe la petición. La conexión puede estar en uno de los siguientes tres posibles estados:
  • "Setup": No se ha establecido todavía la conexión.
  • "Connected": Se ha establecido la conexión, la petición se ha enviado y se está esperando por una respuesta.
  • "Closed": La conexión se ha cerrado.
En el interfaz HttpConnection se proporcionan una serie de métodos que pueden invocarse en cada uno de los estados anteriores:

En el estado "setup" se pueden invocar los siguientes métodos:
  • setRequestMethod(String method) que establece el método de la petición, que puede ser POST,
    GET o HEAD.
    El método por defecto es GET y si el método no es HTTP o la implementación de HTTP está recortada y no lo soporta, se produce una IOException.
  • setRequestProperty(String key, String value) que permite indicar el valor de algunas propiedades HTTP antes de enviar la petición, la propiedad que se quiere establecer se indica en el parámetro key y su valor se establece en el parámetro value.
Por ejemplo, en el código siguiente se crea una conexión HTTP a la URL http://www.it.uc3m.es/pervasive y se indica que el método de la petición es POST, y que la propiedad HTTP User-Agent tiene el valor Profile/MIDP-1.0 Configuration/CLDC-1.0:
HttpConnection c = (HttpConnection)Connector.open("http://www.it.uc3m.es/pervasive");
c.setRequestMethod(HttpConnection.POST);
c.setRequestProperty("User-Agent, "Profile/MIDP-1.0 Configuration/CLDC-1.0");

La transición entre el estado "setup" y el estado "connected" se produce cuando se invoca algún método que precisa enviar o recibir datos al servidor con el que se establece la conexión. Algunos de los métodos que proporciona la implementación del interfaz HttpConnection y que provocan esta transición, son:
Cuando la conexión está abierta (se ha pasado al estado de "connected"), se pueden invocar los siguientes métodos:
A continuación se muestra un ejemplo de código en el que se utiliza la implementación de HttpConnection para leer el contenido de la página http://www.it.uc3m.es/celeste/docencia/cr/hola.txt y mostrarselo al usuario (MIDletHTTPExample.java):

import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
 
/**
 * Un ejemplo de MIDlet para visualizar el contenido de una URL
 * utilizando la implementación del interfaz HttpConnection.
 */
 
public class MIDletHTTPExample extends MIDlet {
 
  private Display display;
  private String url = "http://www.it.uc3m.es/celeste/docencia/cr/hola.txt";
 
  public MIDletHTTPExample() {
    display = Display.getDisplay(this);
  }
 
  /**
   * Método startApp()
   */
  public void startApp() {
    // Llama al método download para descargar el contenido de la URL
    try {
      download(url);
    } catch(IOException e) {
      System.out.println("IOException: " + e);
    }
  }
 
  private void download (String url) throws IOException {
    StringBuffer b = new StringBuffer();
    InputStream is = null;
    HttpConnection c = null;
    TextBox t = null;
 
    try {
      long len = 0 ;
      int ch = 0;
 
      // Abre una conexión del tipo HttpConnection
      c = (HttpConnection)Connector.open(url);
      
      // Obtiene un stream de entrada, para leer el contenido de la url
      // con la que establece la conexión
      is = c.openInputStream();
      
      // Lee hasta que se cierra la conexión
      while ((ch = is.read()) != -1) {
        b.append((char)ch);
      }
      
      // Se contruye un TextBox con el contenido de la URL
      t = new TextBox("Hola...", b.toString(), 1024, 0);
 
    } finally {
      // Se cierra tanto el stream de entrada
      if (is != null)
        is.close();
      // Se cierra la conexión
      if (c != null) 
        c.close();
    }  
    
    // Se visualiza el TextBox en el Display
    display.setCurrent(t);
  }
  
   /**
    * pauseApp
    */
   public void pauseApp() {
   }
 
   /**
    * destroyApp
    */
   public void destroyApp(boolean unconditional) {
   }
}
 
Ejercicio 1:Partiendo del código del ejemplo anterior realizad un MIDlet que solicite al usuario una URL y una etiqueta HTML, y le muestre como resultado el texto modificado por esa etiqueta.

Ejercicio 2:Repetid el ejercicio anterior, pero además de visualizar el texto modificado por la etiqueta HTML, almacenad su contenido en un Record Store, de forma que el usuario de forma "off-line" pueda visualizar el resultado de sus consultas (por lo tanto, se debe de almacenar: URL solicitada, etiqueta HTML indicada y texto modificado por la etiqueta HTML en la URL indicada).

RMS: Almacenamiento en MIDP



 OBJETIVOS
MIDP define una sencilla base de datos orientada a registros que permite almacenar a las aplicaciones datos de forma persistente. Esta base se denomina Record Management System (RMS).
La práctica de hoy tiene como objetivo aprender los conceptos básicos para el manejo de esta base datos, a través del API que nos ofrece MIDP. Todas las clases relacionadas con RMS están contenidas en el paquete javax.microedition.rms.
Esta práctica os debe servir para enfrentaros con dos partes de la práctica final: almacenar el estado de una partida, para que sea posible parar y reanudar un partida (opciones del menú Stop y Restart) y para mantener el registro de mejores puntuaciones (opción Records).


 RecordStore
El mecanismo básico de almacenamiento de RMS es denominado record store. Un record store es un  conjunto de registros, y un registro es un byte array de datos de tamaño variable. Un record store está  representado por un objeto de la clase RecordStore.
Existen reglas importantes sobre los record store:
  1. El nombre de un record store consiste en una combinación de hasta 32 caracteres (sensible a las mayúsculas).

  2. Los record stores creados por MIDlets de un mismo MIDlet suite están almacenados en el mismo espacio de nombres, y por lo tanto, pueden compartir y ver sus contenidos.

  3. Los record stores creados por MIDlets en un MIDlet suite, no son accesibles para los MIDltes de otros MIDlets suite.

  4. El nombre de un record store debe ser único en un MIDlet suite.

Operaciones con un Record Store

La clase RecordStore proporciona los siguientes métodos para crear, abrir, cerrar y borrar un record store:
Cada record store mantiene al menos un campo de cabecera. Si no existe espacio suficente para almacenar la cabecera, el record store no se creará y se producirá una RecordStoreFullException. Si ocurre otro tipo de problema, como que el nombre del record store es demasiado largo, o el record store está corrupto, se produce una RecordStoreException.

A continuación, se puede ver un sencillo ejemplo de creación de un RecordStore (RecordStoreTest1.java).

import javax.microedition.midlet.*;
import javax.microedition.rms.*;
 
public class RecordStoreTest1 extends MIDlet {
    public RecordStoreTest1() { 
    }
    public void startApp() throws MIDletStateChangeException {
        RecordStore rs=null;
        try {
     rs = RecordStore.openRecordStore("file1",true);
            System.out.println("record store file1 is opened.");
        }catch(Exception e){
            System.out.println("Error: "+e.getMessage());
        }
        finally{
            //close the record store
            try {
         rs.closeRecordStore();
                System.out.println("record store file1 is closed");
            }catch (Exception e){
                System.out.println("Error: "+e.getMessage());
            }
        }
        destroyApp(true);
        notifyDestroyed();
    }
 
    /**
     * Pause the MIDlet
     */
    public void pauseApp() {
    }
 
    /**
     * Called by the framework before the application is unloaded
     */
    public void destroyApp(boolean unconditional) {
    }
}

Records


Los elementos de un record store se denominan records. Los records se representan por arrays de bytes y se identifican univocamente mediante recordIDs (valores enteros que comienzan en 1). Los records pueden se añadidos, borrados, leidos y modificados a través de los siguientes métodos, proporcionados por el API:


A continuación podeis ver un ejemplo sencillo, que partiendo del primer ejemplo, añade dos records nuevos y borra uno de ellos (RecordStoreTest3.java).

import javax.microedition.midlet.*;
import javax.microedition.rms.*;
 
public class RecordStoreTest3 extends MIDlet {
    public RecordStoreTest3() { 
    }
    public void startApp() throws MIDletStateChangeException {
        RecordStore rs=null;
        try {
     rs = RecordStore.openRecordStore("file1",true);
            byte data[]= new byte[4];
            for(int j=0; j<2; j++) {
                int i=rs.getNextRecordID();
                data[0] = (byte)((i >> 24) & 0xff);
                data[1] = (byte)((i >> 16) & 0xff);
                data[2] = (byte)((i >> 8) & 0xff);
                data[3] = (byte)(i & 0xff);
                System.out.println("record "+ rs.addRecord(data,0,4)+" is added.");
            }
  try {
             rs.deleteRecord(2);
             System.out.println("record 2 is deleted.");
  }catch(InvalidRecordIDException e) {
   System.out.println("record 2 does not exist");
  }
        }catch(Exception e){}
        finally{
            //close the record store
            try {
         rs.closeRecordStore();
            }catch (Exception e){}
        }
        destroyApp(true);
        notifyDestroyed();
    }
 
    /**
     * Pause the MIDlet
     */
    public void pauseApp() {
    }
 
    /**
     * Called by the framework before the application is unloaded
     */
    public void destroyApp(boolean unconditional) {
    }
}

La cabecera de un Record Store

Los record stores al igual que los ficheros mantienen una cabecera con información propia:

El API nos proporciona además la siguiente información adicional sobre los records store, aunque no está almacenada en la cabecera:

El interfaz RecordListener

A través del interfaz RecordListener se pueden monitorizar cambios en record stores. Se pueden gestionar tres tipos de eventos, a través de los métodos:
La clase RecordStore proporciona los siguientes métodos para añadir o borrar un RecordListener:
NOTA:Un objeto RecordStore puede tener varios RecordListeners registrados.


 RecordEnumeration
Después de que los records se borren, los recordIds de los records que están almacenados en un record store no son consecutivos. Por lo tanto, realizar un recorrido secuencial para obtener todos los records almacenados no es la manera más eficiente de hacerlo.

Para ello se proporciona la clase RecordEnumeration, que aunque en el API de MIDP se presenta como un interfaz, toda implementación de MIDP debe proporcionar una implementación de ella.

Un RecordEnumeration es parecido a un lista doblemente enlazada, en la que cada nodo representa un recordId. Un RecordEnumeration mantiene una secuencia lógica de recordIds de los records almacenados en un record store.

Ejercicio:Analizad el interfaz y realizad un pequeño programa en el que obteneis secuencialmente el contenido de los records en un record store. Cread después un nuevo programa que utilice la implementacion de RecordEnumeration y comprobad la diferencia.


 RecordFilter y RecordComparator
El interfaz RecordFilter define un único método public boolean matches(byte candidate[]) que nos permite definir un criterio para seleccionar un record en un record store que cumpla una determinada condición, de tal forma que si la cumple, se devuelve true y si no false.

El interfaz RecordComparator nos proporciona una forma sencilla de clasificar records. Define una única
función int compare(byte[] rec1, byte[] rec2), que puede devolver únicamente los siguientes constantes:
Por ejemplo, una manera de clasificarlos sería por fecha de creación. Así al implentar esta función se devolvería PRECEDES si rec1 se ha creado antes de rec2, FOLLOWS si se ha creado después y EQUIVALENT si se han creado simultaneamente.