mailxmail - Cursos para compartir lo que sabes Desarrollo de aplicaciones para dispositivos inalámbricos (J2ME) Autor: Gilberto Nájera 1 mailxmail - Cursos para compartir lo que sabes Presentación del curso Curso que abarca el nivel de principiante a intermedio de la programación J2ME para móviles y PDAs, así como la elaboración de páginas web para los mismos. Incluye la elaboración de una página y una aplicación de hoja de cálculo al final. 2 mailxmail - Cursos para compartir lo que sabes 1. Introducción 1. INTRODUCCIÓN Durante los últimos años hemos experimentado, más que en ninguna otra época de la humanidad, un sorprendente avance en la tecnología; tanto en la de las grandes organizaciones: comerciales, militares y educativas, como en la que usamos diariamente. Indudablemente algunos de los más grandes avances se han dado en el área de las comunicaciones: televisión, radio, satélites, teléfonos celulares; y en la electrónica y computación: computadoras con capacidades que antes eran inimaginadas, dispositivos de tamaños minimizados sorprendentemente, sin sacrificar por esto gran poder de procesamiento. Hasta hace pocos años estas dos ramas de la tecnología se desarrollaban independiente una de la otra, pero con el surgimiento de Internet, revolucionando la forma de comunicarse, los teléfonos celulares o móviles fueron creciendo en cuanto a capacidad, de forma que han llegado a convertirse -algunos modelos- en computadoras de bolsillo, con las limitantes de poder que sus características suponen, obviamente. Quién habría de imaginarse hace treinta años que un teléfono podría trasladarse de un lugar a otro, no requerir de cable y tener mayor funcionalidad que las supercomputadoras de aquella época o que no sería necesario sino un bolsillo de 8 x 13 cm para cargar una computadora completa, seguramente nadie. Las nuevas tecnologías utilizadas por los fabricantes de estos aparatos tienden a reducir el tamaño sin sacrificar funcionalidad e incluso aumentarla. Dentro de las nuevas tecnologías que incorporan los pequeños dispositivos portátiles -PDA (Portable Digital Assistant), teléfonos celulares, etc.- se encuentran primordialmente el acceso a Internet, con todas las ventajas de movilidad e información que proporcionan ambas tecnologías (telefonía móvil e Internet), por un lado y la capacidad para el usuario de crear sus propias aplicaciones o descargarlas de Internet e instalarlas en su equipo. En cuanto al acceso y navegación en Internet, se utiliza un protocolo llamado WAP (Wireless Application Protocol) que es el encargado de la comunicación y la interpretación de los contenidos; también se utiliza, para la creación de las páginas 3 mailxmail - Cursos para compartir lo que sabes interpretación de los contenidos; también se utiliza, para la creación de las páginas que se verán en estos dispositivos, el lenguaje WML (Wireless Markup Language), el cual es similar al HTML de las computadoras de escritorio, aunque diseñado especialmente para las características de los dispositivos portátiles. En este documento se tratarán brevemente los fundamentos del diseño de páginas WML. Por el lado de desarrollo de aplicaciones se tienen lenguajes como C++ y Java en versiones especiales para los equipos en cuestión, el presente reporte de investigación se centrará en el lenguaje Java, ya que ocupa una abrumadora mayoría en el mercado y está demostrando ser la mejor opción disponible para todos los programadores interesados en el tema, ademas de las características de seguridad y estabilidad inherentes a cualquier producto Java. 1.1.Un vistazo a las comunicaciones inalámbricas Este amplísimo campo abarca desde las señales de radio y televisión hasta teléfonos celulares y comunicación por satélite. Conceptualmente, puede ser dividido en dos tipos: -LAN (Local Area Network): Operan regularmente en distancias cortas -unos cuantos metros. Pueden ser desde el control de la alarma de un auto, un juguete de radio control o una red BlueTooth. -WAN (Wide Area Network): Sobrepasan por mucho las distancias de los dispositivos LAN, aunque basan su funcionamiento en una red terrestre excelentemente elaborada: Una red de antenas de radio cuidadosamente colocadas, los aparatos pueden funcionar mientras se encuentren en el rango de al menos una de las antenas. Como los teléfonos celulares y los receptores de radio. 1.2.Introducción a la plataforma Java Java fue creado por Sun Microsystems en un intento por desarrollar un lenguaje que facilitara la programación de aparatos electrodomésticos conectados a la red de una casa inteligente. Después de un tiempo de desarrollo, la directiva del proyecto vio que las capacidades del lenguaje iban mucho mas allá de una red casera y decidieron enfocarlo hacia las aplicaciones basadas en Internet o con cierto nivel de desempeño requerido en el trabajo de red, además de satisfacer a las empresas que demandaban un lenguaje que se pudiese implementar en todas sus máquinas, sin importar el sistema operativo sobre el que trabajaran. 4 mailxmail - Cursos para compartir lo que sabes Aunque el lenguaje Java es sintácticamente similar a C++, difiere enormemente en su fundamento: -Java es un lenguaje puramente orientado a objetos, interpretado, esto significa que los programas realizados con Java no son ejecutables por si solos, existe otro programa que los interpreta cada vez que son requeridos. Esto puede causar disminución en la velocidad de ejecución de las aplicaciones, pero, aunque aun no se alcanza la rapidez de ejecución de los programas hechos en C++ o Visual BASIC, se ha mejorado enormemente con respecto a las primeras versiones del lenguaje, lo que significa que tal vez en un futuro cercano la rapidez deje de ser un impedimento para usar Java. -C++ utiliza, como característica principal, apuntadores a memoria y delega a los programadores, simples seres humanos, la tarea de localizarla, controlarla y liberarla. Mientras tanto, Java utiliza objetos de tipo seguro, no permite la asignación de memoria dinámica y gracias a su recolector de basura (garbage collector) la memoria sin usar se recicla automáticamente. La base de la plataforma Java es una máquina virtual, la cual puede ser implementada en los más populares sistemas operativos y en gran variedad de hardware. Por lo que se puede tener aplicaciones binarias Java operando consistentemente a través de diferentes implementaciones. Las APIs (Application Programming Interface) Java son el conjunto de clases y objetos que permiten, mediante la utilización del lenguaje Java, la interacción entre el programador y la computadora y entre esta y el usuario final. Juntos, el lenguaje de programación Java, la máquina virtual y las apis, forman la plataforma Java (Fig. 1.1) , la cual, en su versión 2, se puede encontrar en tres ediciones: -Java 2 Standard Edition (J2SE). Diseñada para computadoras de escritorio, puede trabajar en sistemas operativos como: Windows, Linux, MacOS, Solaris, OS x. -Java 2 Enterprise Edition (J2EE). Plataforma para aplicaciones multiusuario o empresariales. Se basa en J2SE y agrega apis para trabajo en el servidor. -Java 2 Micro Edition (J2ME). Conjunto de tecnologías y especificaciones desarrolladas para dispositivos pequeños como los teléfonos celulares y PDA (palm, agendas electrónicas). Utiliza derivados de componentes J2SE, como son una 5 mailxmail - Cursos para compartir lo que sabes máquina virtual más pequeña y un conjunto de apis menos potentes. Figura 1.1. La plataforma Java 1.2.1.Introducción a J2ME A diferencia de J2SE, la micro edición de Java no es sólo una pieza de software ni una simple especificación, J2ME es una plataforma, una colección de tecnologías y especificaciones diseñadas para diferentes partes del mercado de los dispositivos pequeños. J2ME se divide en configuraciones, perfiles y paquetes opcionales. Las configuraciones son especificaciones que detallan una máquina virtual y un conjunto base de apis que pueden ser usadas en cierta clase de dispositivos. La maquina virtual puede ser completa, como la describe la especificación o algún derivado de ella. Un perfil trabaja sobre una configuración pero agrega apis específicas para hacer un entorno completo de construcción de aplicaciones. Usualmente incluyen apis para el ciclo de vida de las aplicaciones, interfaz de usuario y almacenamiento persistente. Un paquete opcional provee una funcionalidad que puede no estar relacionada con alguna configuración o perfil, por ejemplo la API Bluetooh (JSR182). Actualmente J2ME tiene disponibles dos configuraciones: 6 mailxmail - Cursos para compartir lo que sabes Actualmente J2ME tiene disponibles dos configuraciones: -Connected Limited Device Configuration (CLDC). Para dispositivos pequeños con conexiones de red intermitentes, como teléfonos celulares y PDA. -Connected Device Configuration (CDC). Diseñada para aparatos más grandes (en términos de memoria y poder de procesamiento) con conexiones de red robustas. MIDP (Mobile Information Device Profile) es el perfil con mayor desarrollo en la plataforma Java, aunque se está investigando el PDA Profile. MIDP incluye apis de interfaz de usuario, de ciclo de vida de aplicaciones y algunas de almacenamiento persistente. Los dispositivos implementan una "pila" de software completa, la cual consiste usualmente en una configuración, un perfil y apis opcionales. Figura 1.2. Pila de software para JSR185 JSR185, Java Technnology for Wireless Industyry (JWTI), es la especificación vigente para el desarrollo de aplicaciones inalámbricas en Java, con esta especificación se intenta dejar de lado algunos problemas de compatibilidad entre dispositivos que las anteriores especificaciones dejaron pendientes, así que JSR185 exige que todos los fabricantes que deseen adoptar esta especificación deben incluir en sus dispositivos CLDC 1.0 o 1.1, MIDP 2.0 y WMA (Wireless Messaging API), dejando opcional MMAPI (MultiMedia API), de esta forma el desarrollador puede saber que esperar de una clase de dispositivos y con esto crear aplicaciones altamente portables. En la figura 1.2 se presenta gráficamente esta pila de software. 1.3.Tecnología WAP y WML 1.3.1.WAP (Wireless Application Protocol) Este protocolo pretende ofrecer a los usuarios de equipos con recursos de 7 mailxmail - Cursos para compartir lo que sabes memoria, resolución de pantalla y capacidad de procesamiento limitados las mismas funcionalidades que se ofrecen de Internet a través de computadoras personales, pero con las ventajas y limitantes propias de estos dispositivos. 1.3.2.WML (Wireless Markup Language) WML es un lenguaje de aumento o ganancia (markup) de texto basado en el lenguaje de aumento extensible (XML, Extensible Markup Language) y fue desarrollado para especificar contenidos e interfaces de usuario para terminales de banda estrecha tales como teléfonos móviles e intérpretes de páginas. WML est` diseñado para trabajar con dispositivos inalámbricos pequeños que poseen cuatro características: -Pantalla pequeña de baja resolución. Al nacimiento de este lenguaje, la resolución de los teléfonos celulares se limitaba a unas cuantas lineas de texto y de 8 a 12 columnas de caracteres, debido al rápido aumento en ese ramo, las nuevas versiones del lenguaje se pueden implementar en equipos con resoluciones de hasta 256 x 256 pixeles.. -Los aparatos de entrada tienen una capacidad limitada o están diseñados para un propósito determinado. Un teléfono móvil tiene comúnmente teclas numéricas y un reducido número de teclas adicionales con funciones especificas. Algunos dispositivos más sofisticados pueden poseer teclas programables de software, pero no un ratón ni otros dispositivos de selección. -Los recursos computacionales están limitados por una CPU de baja potencia, una memoria reducida y una potencia restringida. -La red ofrece un reducido ancho de banda y una alta latencia. No son infrecuentes los aparatos con conexiones de red de 300 bit/s a 10 Kb/s y latencia "r ound-trip" de entre 5 y 10 segundos. Las características del lenguaje WML pueden agruparse en cuatro áreas principales: -Ofrece un soporte de texto e imagen y tiene una amplia variedad de formatos y comandos. -Las cartas (fragmentos de página o pantallas) WML se agrupan en barajas. Una baraja WML es similar a una página HTML identificada por una URL (Universal Resource Location, localización universal de recursos) y es la unidad básica de 8 mailxmail - Cursos para compartir lo que sabes transmisión de contenidos. -Ofrece soporte para gestión de navegación entre cartas y barajas, e incluye comandos para su manejo. Estos pueden usarse para navegar o ejecutar scripts, WML también provee de conexiones de anclaje similares a las usadas en el HTML versión 4. -Se pueden establecer parámetros para todas las barajas de WML usando un modelo establecido. Se pueden usar variables en lugar de cadenas y sustituirse en el tiempo de ejecución. Esta forma de establecer parámetros permite que los recursos de la red sean usados de forma eficiente. Toda la información de WML se transmite en formato codificado por la red inalámbrica. 9 mailxmail - Cursos para compartir lo que sabes 2. Diseño de páginas web para dispositivos pequeños 2. DISEÑO DE PÁGINAS WEB PARA DISPOSITIVOS PEQUEÑOS Como todos los interesados en el diseño y programación web saben, las páginas web se hacen con los llamados lenguajes de aumento de texto, dependiendo del tipo de página se utiliza un lenguaje determinado, aunque todos tienen características similares. 2.1.Lenguajes de aumento o ganancia (markup) de texto Los lenguajes de aumento de texto, como HTML (Hipertext Markup Language), DHTML (Dynamic HTML), XML (eXtensible Markup Language) y WML (Wireless Markup Language), son lenguajes que, basándose en la utilización de etiquetas (tags), dan al texto simple características especiales, que pueden ir desde mostrarlo con tamaño, alineación o tipo distinto de fuente hasta la creación de enlaces a otros documentos o aplicaciones o bien la generación de formularios gráficos para introducir datos a una base de datos, auxiliándose de otros lenguajes. 2.2.Lenguaje WML Debido a que el tema que ocupa a este documento es la programación de dispositivos inalámbricos, este capítulo se dedicará a la explicación del lenguaje de diseño web dedicado a ellos, WML. WML es un lenguaje basado en XML y creado especialmente para el diseño de páginas web para equipos con capacidades de procesamiento, memoria e interfaz limitadas, es decir, para las llamadas PDA, teléfonos celulares y paginadores (pagers). 2.2.1.Cartas y Barajas Todo el código WML se organiza dentro de una colección de cartas y barajas. Las cartas especifican una o más unidades de interacción con el usuario, por ejemplo un menú de opciones, una pantalla de texto o un campo de entrada de texto. Lógicamente, un usuario navega a través de una serie de cartas de WML, revisando el contenido de cada una de ellas, introduciendo la información requerida, realizando elecciones y moviéndose a otra carta. Las cartas están agrupadas dentro de barajas. Una baraja es la unidad más pequeña WML que un servidor puede enviar al terminal del usuario. 10 mailxmail - Cursos para compartir lo que sabes al terminal del usuario. 2.2.2.Etiquetas Al igual que HTML, WML basa su codificación en etiquetas, éstas definen el formato y otras propiedades que tendrá el texto o los objetos que se encuentren dentro de ellas. Las etiquetas WML se señalan escribiéndose dentro de los signos <>, por ejemplo <etiqueta>, aunque la mayoría de las etiquetas cuentan con apertura y cierre, este último se hace agregando un "/" antes del nombre de la etiqueta y después del signo "<", ejemplo: </cierre_de_etiqueta> . Todo texto u objeto ubicado entre una apertura y un cierre de etiqueta se verá afectado por ella, por ejemplo, <center>Texto</center>, hará que "Texto" aparezca en el centro de la pantalla, así como cualquier imagen u objeto que se encuentre dentro de los límites de la etiqueta <center></center>. 2.2.3.Etiquetas básicas 2.2.3.1.Obligatorias del documento -<?xml version="1.0"?>. Con esta etiqueta se debe iniciar cualquier documento WML, indica al navegador que se abrirá un archivo codificado en XML, hay que recordar que es la base de WML. -<!DOCTYPE WML PUBLIC"-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/WML_1.1.xml">. Se especifica el tipo de XML que se abrirá, WML en este caso y además se indica al navegador la URL que seguirá para la descodificación del lenguaje. -<WML>... </WML>. Se escribe después de los dos anteriores, determina el cuerpo principal del documento, cumple la misma función que la etiqueta <HTML> en ese lenguaje. -<card id="cartauno">...</card>. Recordemos que una página WML se divide en cartas, esta etiqueta marca los límites de una carta llamada "cartauno". Una página WML puede tener tantas cartas como sean necesarias. 2.2.3.2.De formato de texto -<p>...</p>. Limita un párrafo, en WML todo texto debe estar incluido en un párrafo. 11 mailxmail - Cursos para compartir lo que sabes párrafo. -<br/>. Salto de linea, como en HTML. -<a href="#cartados">a la cartados</a>. Enlace de hipertexto a una carta llamada cartados, en lugar de "#cartados" puede ir cualquier URL válida. -<b> </b>. Pone el texto contenido en negritas. -<i> </i>. Todo texto dentro de esta etiqueta se mostrará en itálicas. -<em> </em>. Similar a la itálica. -<strong> </strong>. Otra opción para letra negrita. -<u> </u>. Texto subrayado. -<big> </big>. Letra grande, se debe recordar que se está ante dispositivos con limitantes gráficas, así que no se puede ser muy exigente con los tamaños de letra. -<small> </small>. Letra pequeña 2.2.3.3.Caracteres especiales Al igual que en HTML, hay algunos caracteres utilizados por el lenguaje que presentan ciertas dificultades a la hora de mostrarlos en pantalla (", $ #, etc.). En caso de que sea necesario hacerlo se proporciona una lista, en la tabla 2.1, de códigos por los que se deben sustituir los caracteres para que aparezcan sin ningún problema. Tabla 2.1. Códigos de caracteres especiales. Signo Código Signo Código < &lt; %3c > &gt;%3e ' &apos; " &quot; & &amp;%26 $ $$%24 [ %5b ] %5d / %2f \ %55c ? %3f ; %3b : %3a ^ %5e @ %40 | %7c 12 mailxmail - Cursos para compartir lo que sabes = %3d + %2b } %7d { %7b , %2c espacio %20 # %23 ` %27 2.2.3.4.Tildes y eñes En WML se deben sustituir las letras acentuadas y eñes por los siguientes caracteres: &#225; á &#193; ` &#233; &#201; É &#237; i &#243; ó &#211; Ó &#250; &#218; Ú &#241; ñ &#209; é &#205; Í ú Ñ 2.2.4.Variables Una de las grandes diferencias entre el WML y el HTML es que WML permite la definición variables en las cartas, asignarles valores y presentarlos en la pantalla, incluso utilizar las variables en expresiones. Con esto se puede conservar información en el paso de una carta a otra y así dividir el contenido en varios pasos (cosa bastante útil para pantallas tan pequeñas). Las variables son cadenas de texto case-sensitive (distingue mayúsculas y minúsculas) a la que se le asigna un valor (secuencia de caracteres) o ningún valor. El nombre de la variable puede empezar por el guión bajo: "_" o una letra, seguida de una o más letras, números o el guión bajo. Ejemplos de nombres de variables válidos: NOMBRE_Usuario, _mivar1, X700II 2.2.4.1.Crear variables y asignarles valores <setvar name="mivar1" value="Juan"/>. Juan es el valor de la variable mivar1. Con setvar se crea la variable y a la vez se le asigna un valor. Input. Con input se puede crear (declarar) la variable, asignarla un valor y 13 mailxmail - Cursos para compartir lo que sabes Input. Con input se puede crear (declarar) la variable, asignarla un valor y también devolverle el valor original. Input se utiliza para la entrada de datos y esos datos (valores) se asignarán a una variable. Se presenta como un cuadro de texto. Select. Con select se puede crear (declarar) la variable, asignarle un valor y también devolverle el valor original. Select permite seleccionar al usuario uno o más valores entre una lista de opciones que se asignarán a una variable. Postfield. Con postfield se puede crear (declarar) la variable, asignarle un valor y también devolverle el valor original. 2.2.4.2.Hacer referencia a variables Es posible incluir el valor de una variable dentro de un documento WML, tanto para que aparezca en la pantalla como para que se envíe a un programa. Hay tres formas de hacer esto: 1 .$nombrevariable. Se utiliza cuando no hay ambigüedad con el nombre de la variable dentro del contexto. 2 .$(nombrevariable). Cuando puede existir ambigüedad con el nombre de la variable dentro del contexto. 3 .$(nombrevariable:conversión). Cuando se introduce el valor de una variable dentro de una baraja, se puede definir el formato (escape, unescape o no escape) así: 1 .$(var:e), $(var:E), $(var:escape): cualquiera de los tres traduce al formato escape[1]. 2 .$(var:u), $(var:U), $(var:unesc): cualquiera de los tres traduce del formato escape a texto US-ASCII. 3 .$(var:n), $(var:N), $(var:noesc): cualquiera de los tres hace que el valor de la variable no se traduzca al formato escape. WML siempre aplica el formato escape cuando se trata de atributos que trabajan con URLs. Por eso muchas veces se puede asumir que el WML convertirá al formato escape cuando lo tiene que hacer. De todas formas, conviene incluirlo si tenemos mínimas dudas de lo que ocurrirá. 2.2.4.3.Las variables y las tareas <Noop>, <Prev>, <Refresh> y <Go> - <noop/>. No hace nada. Se utiliza para desactivar eventos en el nivel de la 14 mailxmail - Cursos para compartir lo que sabes baraja. -<prev/>. Prev vuelve al anterior URL. Si dentro de prev hay un elemento setvar, como en el ejemplo, se procesa antes de que se ejecute el prev. <prev> <setvar name="mivar" value="contenido"/> </prev> -<refresh/>. Refresh refresca los contenidos visibles del navegador. Si refresh contiene un setvar, como en el ejemplo, se procesa el setvar y después se procede al refresh. <refresh> <setvar name="mivar" value="contenido"/> </refresh> -<go>. Go lleva a otra URL u otra carta. Si dentro de Go hay un elemento setvar, se procesa primero éste y después se ejecuta la tarea "Go". <go> href= "http://www.WMLclub.com/cgi-bin/programa.pl ?x=$(mivar1)&y=$(mivar2)" method="post"> <setvar name="mivar1" value="50"/> <setvar name="mivar2" value="80"/> </go> 2.2.5.Creación de tablas El manejo de tablas en WML es similar al de HTML, se define el elemento <table> y se indican las columnas ("columns"), después se especifican, dentro de cada columna, las filas y los datos que han de llevar. Aunque el tag de <table> está recogido en las especificaciones del WML 1.1, existen algunos móviles que no lo implementan, como es el caso del Nokia 7110, en su lugar los datos son mostrados en una tabla de una columna. A continuación vemos un ejemplo de código para creación de tablas: <p><table columns="3"> 15 mailxmail - Cursos para compartir lo que sabes <tr> <td><p>fila 1, columna 1</p></td> <td><p>fila 1, columna 2</p></td> <td><p>fila 1, columna 3</p></td> </tr> <tr> <td><p>fila 2, columna 1</p></td> <td><p>fila 2, columna 2</p></td> <td><p>fila 2, columna 3</p></td> </tr> </table></p> 2.2.6.Manejo de imágenes En los dispositivos móviles se permite la visualización de imágenes, pero éstas deben estar en formato WBMP, en lugar de las utilizadas en HTML que pueden ser gif, jpg o bmp; aunque algunos de los más modernos equipos ya cuentan con soporte para jpg. Una imagen WBMP puede ser creada por el diseñador en un programa especial, o bien convertida de otro formato en el mismo programa. Uno de estos programas puede ser Image Magic o bien el incluido en el kit de desarrollo WAP de Nokia. La forma de incluir una imagen en una página WML es la siguiente: <p><img src="movil1.wbmp" alt="Movil1"/></p> 2.3.Scripts en WML (WMLS) WMLS son las siglas de Wireless Markup Language Script y es un lenguaje de programación que junto con el WML nos permite el diseño de las páginas que se visualizan en los terminales móviles. El navegador, además de poseer un intérprete para WML, también tiene uno 16 mailxmail - Cursos para compartir lo que sabes para los scripts. Además del propio lenguaje en sí, este intérprete implementa un conjunto de librerías que permiten a nuestros programas acceder a ciertos servicios del terminal. El código WMLS debe incluirse en un archivo distinto al WML asociado. 2.3.1.Utilización de WMLScript Al utilizar WMLS se pretende cubrir los huecos de funcionalidad que deja WML, es decir, se trata hacer con las páginas cosas que resultarían imposibles con el simple WML. Entre las utilidades del WMLScript están las siguientes: -Validación de los datos de entrada por parte del usuario. -Manejar mediante código funciones propias del terminal, como realizar llamadas desde el teléfono, enviar mensajes, agregar número de teléfono a la libreta de direcciones y acceder a la tarjeta SIM. -Realizar alertas, mensajes de error, confirmaciones, etc. 2.3.1.1.Identificadores Se utilizan identificadores para nombrar las variables, las funciones y los pragmas (información para la unidad de compilación). Estos identificadores tienen las mismas reglas de definición que los nombres de variables en WML, además no pueden ser iguales a una palabra reservada. 2.3.1.2.Comentarios Los comentarios en el WMLS, son como en el C o en el C++ (una sola línea, empieza por // y más de una línea empieza por /* y termina por */): //comentario /*Comentarios de más de una línea */ 2.3.1.3.Caracteres especiales Al estar basado en WML, el manejo de caracteres especiales se realiza de la misma forma. NOTA: Los WMLscripts se pueden escribir en cualquier editor de texto normal y corriente. No pueden ser ejecutados sin que sean llamados desde una carta WML. 17 mailxmail - Cursos para compartir lo que sabes y corriente. No pueden ser ejecutados sin que sean llamados desde una carta WML. 2.3.2.Primeros pasos A continuación se muestra un sencillo WMLS que convierte pesos a dólares (suponiendo el tipo de cambio a 11.05 pesos por dólar). Primero es necesario hacer el WML que recogerá la cifra que se desee pasar a dólares. <?xml version="1.0"?> <!DOCTYPE WML PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/WML_1.1.xml"> <WML> <card id="monedas" title="Cambio de divisas" newcontext="true"> <p>Conversor de pesos a dólares</p> <p><input type="text" name="cantidad" title="Cantidad: " format="*N"/></p> <p><do type="accept"> <go href="peso-dolar.WMLs#calcular('$(cantidad)')"/> </do> </p> <p>Resultado: $(resultado)</p> </card> </WML> En esta carta se ha creado una variable que se llama cantidad y lleva la cifra a convertir en dólares. Además se incluye una variable, que no tiene nada, llamada resultado. En esta variable se almacenará el resultado en el WMLs siguiente: extern function calcular(cantidad) { var dolar = (cantidad/11.05); 18 mailxmail - Cursos para compartir lo que sabes var dolarString = String.toString(dolar); dolarString = String.format("%.2f", dolarString); WMLBrowser.setVar("resultado", dolarString); WMLBrowser.refresh(); } Primero se declara la función, en este caso llamada calcular. La función es extern, esto significa que puede ser llamada desde fuera del archivo WMLS. Esta función recibirá como parámetro la variable cantidad. Al igual que C++ y Java, la función comienza con { y termina con }. Se declara una variable llamada dólar y se le asigna un valor, en este caso, lo que haya introducido el usuario dividido entre 11.05 (el supuesto valor actual de la moneda estadounidense). Como el resultado puede tener una cantidad enorme de cifras decimales, primero se convierte a una cadena de texto, con String.toString. El resultado irá dentro de la variable dolarString, después con "%.2f" se indica que el formato será un float (de punto flotante o decimal) con dos cifras después del punto. Se crea una variable de nombre resultado y cuyo valor es el contenido de dolarString y además se envía a la carta desde la cual fue llamada esta función. Actualiza el navegador (browser) para que en la pantalla aparezcan los cambios (ahora la variable "resultado" tiene un valor y tiene que aparecer en pantalla) 2.3.2.1.Variables en WMLScript Las variables deben ser declaradas antes de poder utilizarlas en una expresión. Esta es la forma de declarar una variable: var NombreVariable; El nombre de la variable puede llevar caracteres alfabéticos, números y el guión bajo ("_"), pero nunca puede empezar con un número. En la declaración de una variable puede asignársele un valor inicial. Hay dos formas de hacerlo, con igual resultado: var a; a = 1; 19 mailxmail - Cursos para compartir lo que sabes O esta otra forma: var a = 1; También se pueden declarar varias variables a la vez, separadas por comas en la misma instrucción var. El valor de las variables en el WMLScript sólo duran lo que dura la función en la que son declaradas. Por eso sólo se puede acceder a su valor dentro de la función donde han sido declaradas. Las variables pueden contener cualquier tipo de datos: -Boolean (true o false).Ejemplo: var respuesta = true; -Integer (número entero positivo o negativo, sólo desde -2147483648 hasta el 2147483647). Ejemplo: var num = 7890; -Float (número decimal, el mínimo es: +-1.17549435E-38 y el máximo es: +-3.402823476E+38).Ejemplo: var num = 6.78; -String (caracteres alfabéticos, se puede utilizar comillas dobles o simples).Ejemplo: var nombre = "Juan"; -Invalid (para distinguirlo de los otros tipos de datos o para saber por ejemplo cuándo el resultado de una operación se sale de los máximos o los mínimos, un float menor a +-1.17549435E-38, por ejemplo). Ejemplo: var ex = invalid; Hay una forma de crear variables globales, esto es, variables que se almacenan en el contexto del navegador y que pueden ser accesadas tanto por las cartas WML, como por las funciones del WMLScript. Estas variables hay que crearlas con la función setVar de la librería WMLBrowser, así: WMLBrowser.setVar ("nombreVariable", valor); La variable siempre tiene que tener un valor inicial: WMLBrowser.setVar ("nombre", 0); 2.3.3.Librerías WMLScript cuenta con seis librerías, éstas incluyen gran variedad de funciones 20 mailxmail - Cursos para compartir lo que sabes con las que podemos trabajar, dichas librerías son: lang, float, string, URL, dialogue y WMLbrowser. 2.3.3.1.Lang Todas las funciones se utilizan como i se hiciera en un entorno orientado a objetos: lang.abs(-9), por ejemplo. Todos los nombres de funciones comienzan con minúscula. 1.Funciones aritméticas: - abs (número). Calcula y devuelve el valor absoluto. - m a x (número1, número2), min (número1, número2). Regresan el mayor y el menor, respectivamente, entre dos números comparados. 2.Funciones de tipos de datos: - isFloat (valor). Devuelve true si valor puede ser convertido en número con decimales, false si no puede ser convertido e invalid si el sistema no soporta decimales. - isInt (valor). Es true si el valor puede ser convertido a entero. False si ni puede ser. Invalid si excede los limites antes mencionados. - parseFloat (cadena de texto). Devuelve un número con decimales, sacado de la cadena de texto. Analiza la cadena de texto y termina en el primer carácter que no sea parte de un número con decimales. Devuelve "invalid" cuando hay un error en el análisis -no encuentra un número con decimales- o el dispositivo WAP no acepta números con decimales. - parseInt (cadena de texto). Devuelve el número entero, sacado de la cadena de texto. El análisis de la cadena de texto se detiene en el primer carácter que no sea parte de un número con decimales. Devuelve "invalid" cuando hay un error en el análisis -no encuentra un número con decimales-. 3.Funciones de entorno. Con estas funciones puedes conocer las capacidades del dispositivo WAP en el que se ejecuta el WMLscript. - CharacterSet(). Devuelve un número entero que es el valor asignado por el IANA (Internet Assigned Numbers Authority) identificando el set de caracteres que 21 mailxmail - Cursos para compartir lo que sabes soporta el intérprete del WMLScript. - Float(). Devuelve true si el dispositivo soporta los números con decimales; false, en caso contrario. - MaxInt(). Devuelve el número entero máximo que acepta el dispositivo (actualmente es 2.147.483.647). - minInt(). Devuelve el número entero mínimo que acepta el dispositivo (actualmente es -2.147.483.648) 4.Funciones de control de flujo. Te permite terminar la ejecución del script y devolver el control al lugar desde donde fue llamado (por ejemplo, otra función del mismo WMLscript). - abort (cadena de texto). Termina "inesperadamente" la interpretación del bytecode del WMLscript y devuelve una cadena de texto describiendo el error. A esta cadena de texto no se puede acceder desde un documento WML. - exit (valor). Termina "normalmente" la interpretación del bytecode del script y devuelve un valor a la función desde la que fue llamado. A esta cadena de texto no se puede acceder desde un documento WML. 5.Funciones de números aleatorios. - random(número entero). Devuelve un entero positivo aleatorio entre 0 y el número indicado. - seed (número entero). Inicia en generador de números aleatorios en el número indicado. 2.3.3.2.Float 1.Funciones de entorno: - maxFloat(). Devuelve el número positivo decimal máximo. Actualmente es 3.40282347E+38, para todos los dispositivos que disponen de números decimales. Si el dispositivo no los soporta, el resultado será "invalid". - MinFloat(). Devuelve el número positivo decimal mínimo. Actualmente es 1.17549435E-38 para todos los dispositivos que disponen de números decimales. Si el dispositivo no los soporta, devolverá "invalid". 22 mailxmail - Cursos para compartir lo que sabes 2.Funciones aritméticas. - ceil(número). Devuelve el entero menor más próximo a número. Si el número ya es entero, devuelve el mismo número dado. - floor(número). Es lo contrario de ceil. Devuelve el entero más próximo a número, pero que no sea mayor que él. - i n t(número). De un número dado devuelve la parte entera. - pow (número1, número2). Devuelve el número1 elevado a la potencia número2. Si número1 es negativo, número2 debe ser un número entero. Si número1 es cero y número2 es menor que cero, pow devuelve "invalid". - round(número). Devuelve el número entero más cercano al número dado. Si dos enteros están igualmente cerca del número, round devuelve el mayor de ellos. - sqrt(número). Devuelve la raíz cuadrada del número. Si el resultado es menor que cero sqrt devuelve "invalid". 2.3.3.3.String 1.Funciones básicas. - charAt (cadenadetexto, número). Devuelve el carácter en la posición número de cadenadetexto. - compare (cadenadetexto1, cadenadetexto2). Compara dos cadenas de texto. - lenght (cadenadetexto). Regresa la cantidad de caracteres que tiene una cadena. - isEmpty (cadenadetexto). Devuelve true si la cadena está vacía, false si no lo está. 2.Funciones de subcadena (Substring). - subString(cadena,Inicio,Longitud). Extrae una subcadena de determinada longitud de caracteres, comenzando en la posición Inicio. - find (cadena, subcadena). Determina si una subcadena pertenece a una cadena, devuelve True si existe. - replace (cadena, cadena1, cadena2). Reemplaza en cadena todas las apariciones de cadena1 por cadena2. 23 mailxmail - Cursos para compartir lo que sabes apariciones de cadena1 por cadena2. 3.Funciones de elementos. - elementAt (cadena, numElemento, separador). - elements (cadena, separador). - insertAt (cadena, cadena2, número, separador). - removeAt (cadena, número, separador). - replaceAt (cadena, elemento, número, separador). 4.Funciones de conversión: - format (formatString, valor). Da un formato especifico a un determinado valor. - toString (valor). Devuelve el valor expresado como cadena de caracteres. Por ejemplo: a=String.toString(3.5); //a="3.5" 2.3.3.4.URL La librería URL tiene funciones para manipular y verificar URLs relativos y absolutos. La sintaxis de un URL (definido en detalle en [Request For Comments (RFC) 2396]) es: <scheme>://<host><:port>/<path>;<parameters>?<query>#<fragment> Ejemplo: http://www.WMLclub.com:80/docs/faqs/index.htm;3;2?id=juan#nombre Para las funciones que devuelven una cadena que contiene un componente de un URL, los delimitadores de adelante y de atrás no están incluidos en el resultado devuelto. La única excepción es en la ruta (donde se incluyen las diagonales (/)). Hay tres tipos de funciones de la librería URL: 1.Funciones para manejar URLs Permiten validar, recuperar, crear y convertir a formato escape las URLs: - escapeString(cadenadetexto). 24 mailxmail - Cursos para compartir lo que sabes - getBase(). - GetReferer(). - isValid(url). - resolve(baseString,incluidoString). - unescapeString(cadenadetexto). 2.Funciones para extraer componentes. Se utilizan para recuperar componentes individuales de URLs relativos y absolutos. Estas funciones primero comprueban que el URL es válido, utilizando isValid(), antes de extraer los componentes. Si isValid es "false", estas funciones devolverán "invalid". - getFragment(cadenadetexto). - getHost(cadenadetexto). - getPort(cadenadetexto). - getParameters(cadenadetexto). - getPath(cadenadetexto). - getScheme(cadenadetexto). - getQuery(cadenadetexto). 3.Funciones para extraer contenido. Sólo hay una función para extraer contenido de un URL y es la que permite asignar el contenido de un archivo de texto a una variable WMLScript. loadString(URLString,contentTypeString) 2.3.3.5.Dialogue Los diálogos son mensajes en pantalla que esperan una respuesta del usuario. La forma en la que aparece en pantalla dependerá de cada micronavegador. Esta librería sólo tiene tres funciones: - prompt(cadena,cadenaPorDefecto). El texto que sale en pantalla es "cadena" y espera una respuesta por parte del usuario (un input). El valor por defecto del 25 mailxmail - Cursos para compartir lo que sabes y espera una respuesta por parte del usuario (un input). El valor por defecto del input es cadenaPorDefecto. Devuelve la cadena que el usuario ha introducido. - confirm(cadena,cadenaOK,cadenaCancelar). El texto que sale en pantalla es "cadena" y despliega dos opciones a elegir ("cadenaOK" y "cadenaCancelar") y espera una respuesta del usuario. La función confirm devuelve "true" si el usuario ha seleccionado OK y "false" si ha seleccionado Cancel. - alert(cadena). Presenta una cadena de texto y espera la confirmación del usuario. Devuelve una cadena vacía. 2.3.3.6.WMLbrowser Las funciones de esta librería permiten acceder a las variables de contexto del micronavegador del dispositivo. También permiten ejecutar una tarea cuando el intérprete WMLscript termina su ejecución. Todas estas funciones devuelven invalid si el micronavegador no soporta WML o si el intérprete de WMLscript no puede ser invocado desde el micronavegador. 1.Funciones de variables. Permiten leer y escribir variables en el contexto del micronavegador. - getVar(nombre). Devuelve la cadena que contiene la variable nombre, devolverá una cadena vacía si la variable no existe, "invalid" si nombre no es un nombre de variable apropiado. Ejemplo: var a = "Hola"; var b = WMLBrowser.getVar(a); // b = "Hola" - setVar(nombre, valor). Inicia la variable nombre con el contenido de valor, si la operación se realiza con éxito devuelve "True", de lo contrario "False" e "Invalid" si nombre no es un nombre de variable válido. Por ejemplo: var a = WMLBrowser.setVar(color,azul); // a = true 2.Funciones de tareas. - g o(cadenaURL). Carga el URL dado. Esto sucede cuando el script devuelve el control al navegador. Ej.: var carta="http://www.WMLclub.com/demos/a.WML#carta2"; 26 mailxmail - Cursos para compartir lo que sabes WMLBrowser.go(carta); - prev(). Carga la carta vista anteriormente. Al igual que go(), su efecto se da sólo cuando el WMLscript devuelve el control al micronavegador. Ej.: WMLBrowser.prev(); - refresh(). Actualiza la carta visualizada. Ej.: WMLBrowser.refresh(); - newContext(). Vacía el contexto del navegador y devuelve una cadena vacía. Ej.: WMLBrowser.newContext(); 3.Funciones de consulta. - GetCurrentCard(). Devuelve la carta actual (la que llamó al script) junto con la ruta más corta con respecto a la ubicación del archivo .WMLs. Ej.: Si www.server.com/a.WML llama a www.server.com/pro.WMLs: var a = getCurrentCard(); // a = "a.WML" O si www.serv0.com/a.WML llama a www.serv1.com/test/test.WMLs: var a = getCurrentCard(); // a = "http://www.serv0.com/a.WML" [1]Formato adoptado por Internet Society para identificar los caracteres utilizados en las URL, donde se escribe el "%" y el número de carácter, %20 para el espacio, por ejemplo. 27 mailxmail - Cursos para compartir lo que sabes 3. El lenguaje de programación Java 3. EL LENGUAJE DE PROGRAMACIÓN JAVA Debido a que el presente documento centra su atención en la programación de dispositivos móviles, solamente se dará un descripción general del funcionamiento y características del lenguaje Java, ya que después se profundizará en su variante J2ME. Cuando se programa en Java, se coloca todo el código en métodos, de la misma forma que se escriben funciones en lenguajes como C. En el presente capítulo se describen algunas de las principales características del lenguaje de programación Java. 3.1. Acerca de la máquina virtual Como ya se ha mencionado, Java es un lenguaje interpretado, esto es que los programas no se compilan a archivos ejecutables, sino a archivos que habrán de ser interpretados por otros que si lo sean. Los archivos compilados de Java se llaman archivos de códigos de bytes o bytecodes. Cuando se hace un programa en Java, el o los archivos que contengan el código fuente deben ser nombrados con extensión ".java" y al momento de ser compilados se creará un archivo de bytecodes con extensión ".class" del mismo nombre que el archivo fuente. Este archivo será la aplicación y, por lo tanto el que se habrá de ejecutar. El trabajo de la máquina virtual es interpretar estos códigos de bytes según el sistema operativo en que se esté trabajando. He aquí el secreto de la portabilidad de Java: existe una máquina virtual para cada sistema operativo y los bytecodes se verifican cada vez que han de ser ejecutados, es decir, no corren directamente sobre el sistema operativo, sino que la maquina virtual los ejecuta convirtiendo los bytecodes a instrucciones propias de la plataforma anfitrión. 3.2.Características básicas del lenguaje 3.2.1. Comentarios Los comentarios se introducen en los programas como ayuda para quienes habrán de leerlos, ya sea los propios programadores o aquellos quienes les han de 28 mailxmail - Cursos para compartir lo que sabes dar mantenimiento. Un comentario no se ejecuta ni compila junto con el resto del programa, es únicamente para información. En Java hay tres tipos de comentarios: A) // comentarios para una sola línea. Todo el texto de una línea que se encuentre después de "//" será omitido por el compilador. B) /* comentarios de una o más líneas*/ Se omitirá en la compilación y ejecución todo el texto contenido a partir de la apertura del comentario "/*" y hasta su cierre "*/". C) /** comentario de documentación, de una o más líneas*/ Los comentarios de documentación, colocados inmediatamente antes de una declaración (de variable o función), indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código. En este tipo de comentario para documentación, se permite la introducción de algunos tokens o palabras clave, que harán que la información que les sigue aparezca de forma diferente al resto en la documentación. 3.2.2. Identificadores Los identificadores se utilizan para nombrar variables, funciones, clases y objetos; cualquier cosa que el programador necesite identificar o usar. En Java, un identificador debe comenzar con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las minúsculas (case sensitive) y no hay longitud máxima en el identificador. Serían identificadores válidos: identificador 29 mailxmail - Cursos para compartir lo que sabes nombre_usuario Nombre_Usuario _variable_del_sistema $transaccion Y su uso sería, por ejemplo: int contador_principal; char _lista_de_archivos; float $cantidad_en_Ptas; 3.2.3. Palabras clave Las palabras claves en un lenguaje de programación son aquellas que tienen alguna función especifica para dicho lenguaje, por ejemplo una palabra que indique la impresión de una cadena de texto en pantalla. Las siguientes son las palabras clave que están definidas en Java y que no se pueden utilizar como identificadores: abstract continue for new boolean default goto null do if package switch synchronized break this byte double implements private threadsafe byvalue else import protected throw case extends instanceof public return transient catch false int char final interface class finally long static void const float native super while short true try Seguramente no serán estas las únicas palabras que no podrán ser utilizadas 30 mailxmail - Cursos para compartir lo que sabes como identificadores, debido a que existen también las palabras utilizadas por las clases que utilicemos (extendamos) en nuestros programas y las palabras reservadas de Java. 3.2.4. Palabras Reservadas Además de las anteriores, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora no tienen un cometido específico. Esas palabras son: cast future operator generic outer rest inner var 3.2.5. Literales y tipos de datos Java utiliza cinco tipos de datos básicos: A) Enteros. Números sin punto decimal. byte 8 bits complemento a dos short 16 bits complemento a dos int 32 bits complemento a dos long 64 bits complemento a dos Por ejemplo: 21, 077, 0xDC00 B) Reales en coma flotante. Números con punto decimal. float 32 bits IEEE 754 double 64 bits IEEE 754 Por ejemplo: 3.14, 2e12, 3.1E12 C) Lógicos o booleanos. almacenan sólo valores de falso o verdadero. true (verdadero). false (falso). D) Caracteres. Únicamente almacenan un carácter, una letra, símbolo o número (no se toma en cuenta su valor matemático). Por ejemplo: "a", \t, \u???? [????] es un número unicode. 31 mailxmail - Cursos para compartir lo que sabes E) Cadenas. Son una agrupación de caracteres. Por ejemplo: "Esto es una cadena literal" 3.2.5.1.Arreglos Son asociaciones de varios datos del mismo tipo, por ejemplo un arreglo de cinco datos enteros o una lista de diez nombres, también son llamados arrays. En Java se pueden declarar arreglos de cualquier tipo: char s[]; int iArray[]; Incluso se pueden construir arreglos de arreglos: int tabla[][] = new int[4][5]; Los límites de los arreglos se comprueban en tiempo de ejecución para evitar desbordamientos y la corrupción de memoria, es decir, se verifica que no se intenten ocupar más espacios de los especificados en la declaración del arreglo. En Java un arreglo es realmente un objeto, porque tiene definido el operador "[ ]". Tiene una función miembro: length. Se puede utilizar este método para conocer su longitud. int a[][] = new int[10][3]; a.length; /* 10 */ a[0].length; /* 3 */ Para crear un arreglo en Java hay dos métodos básicos. Crearlo vacío: int lista[] = new int[50]; o se puede crear el arreglo con sus valores iniciales: String nombres[] = { "Juan","Pepe","Pedro","María" }; Esto que es equivalente a: 32 mailxmail - Cursos para compartir lo que sabes String nombres[]; nombres = new String[4]; nombres[0] = new String( "Juan" ); nombres[1] = new String( "Pepe" ); nombres[2] = new String( "Pedro" ); nombres[3] = new String( "María" ); No se pueden crear arreglos estáticos en tiempo de compilación: int lista[50]; // generará un error en tiempo de compilación Tampoco se puede llenar un array sin declarar el tamaño con el operador new: int lista[]; for( int i=0; i < 9; i++ ) lista[i] = i; Es decir, todos los arrays en Java son estáticos, esto mejora la seguridad en el manejo de memoria. Para convertir un arreglo en el equivalente a los arreglos dinámicos de C++, se usa la clase vector, que permite operaciones de inserción, borrado, etc. 3.2.6. Operadores Los operadores de Java son muy parecidos en estilo y funcionamiento a los de C. En la siguiente tabla aparecen los operadores que se utilizan en Java, por orden de precedencia: . [] ~ instanceof - () * ++ / << >> -% ! + >>> < > <= >= == != & ^ | && || ?: = op= *= /= %= += 33 -= mailxmail - Cursos para compartir lo que sabes Los operadores numéricos se comportan como esperamos: int + int = int Los operadores relacionales devuelven un valor booleano. Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones además de + y += para la concatenación: String nombre = "nombre" + "Apellido"; El operador = siempre hace copias de objetos, marcando los antiguos para borrarlos, y ya se encargará el garbage collector de devolver al sistema la memoria ocupada por el objeto eliminado. 3.2.7. Separadores Los separadores en Java definen la forma y función del código. Agrupan bloques de código e identificadores, definen la prioridad en operaciones. Los separadores admitidos en Java son los siguientes: -() - paréntesis. Para contener listas de parámetros en la definición y llamada a métodos. También se utiliza para definir precedencia en expresiones. -{} - llaves. Para contener los valores de matrices iniciadas automáticamente. También se utiliza para definir un bloque de código, para clases, métodos y ámbitos locales. -[] - corchetes. Para declarar tipos matriz. También se utiliza cuando se referencian valores de matriz. -; - punto y coma. Separa sentencias. -, - coma. Separa identificadores consecutivos en una declaración de variables. También se utiliza para encadenar sentencias dentro de una sentencia f o r. -. - punto. Para separar nombres de paquete de subpaquetes y clases. También se utiliza para separar una variable o método de una variable de referencia. 3.2.8. Control de flujo Las instrucciones de control de flujo son aquellas que, basándose en criterios dados por el programador, alteran el flujo natural (lineal) del programa. 34 mailxmail - Cursos para compartir lo que sabes En Java tenemos casi las mismas instrucciones de control de flujo (selección, decisión, salto y repetición) que en C. 3.2.8.1.Estructuras de decisión y selección Son fragmentos de código que se ejecutan solamente si una condición contenida en ellos se cumple, como se muestra en los siguientes ejemplos: A) if/else: if( condición_Boolean ) { Instrucciones_si_se_cumple_la_condición_Boolean; } else { Instrucciones_si:no_se_cumple; } B) switch: switch( expr1 ) { case expr2: instrucciones_si_expr1_=_expr2; break; case expr3: instrucciones_si_expr1_=_expr3; break; default: instrucciones_si_expr1_<>_expr2_y_a_expr3; break; } 3.2.8.2.Ciclos de repetición 35 mailxmail - Cursos para compartir lo que sabes Estas estructuras repiten su ejecución tantas veces como se haya indicado o bien cuando se cumpla una determinada condición. A) for for( expr1 inicio; expr2 test; expr3 incremento ) { sentencias; } También se soporta el operador coma (,) en los ciclos for for( a=0,b=0; a < 7; a++,b+=2){ } B) while while( Boolean ) { sentencias; } C) do/while do { sentencias; }while( Boolean ); 3.2.8.3.Excepciones Algunas veces los programas producen algún error en tiempo de ejecución y quisiéramos tener la capacidad de decirle qué hacer cuando esto suceda, principalmente para evitar que el programa se colapse cada vez que el error se presenta. para esto nos sirven las excepciones de Java. A) try-catch-throw try { sentencias; } catch( Exception ) { sentencias; 36 mailxmail - Cursos para compartir lo que sabes } Java implementa excepciones para facilitar la construcción de código robusto. Cuando ocurre un error en un programa, el código que encuentra el error lanza una excepción, que se puede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas. 3.2.8.4.Control general del flujo Rompen el flujo natural del programa incondicionalmente, ya sea para terminar un módulo o todo el programa o simplemente para pasar a otro lugar del código. break [etiqueta] continue [etiqueta] return expr; etiqueta: sentencia; Por ejemplo, en caso de que nos encontremos con ciclos anidados, se permite el uso de etiquetas para poder salirse de ellos, por ejemplo: uno: for( ) { dos: for( ) { continue; /* seguiría en el ciclo interno, pero omitiría las instrucciones siguientes*/ continue uno; break uno; // seguiría en el ciclo uno principal // se saldría del ciclo principal } } En el código de una función siempre hay que ser consecuentes con la declaración que se haya hecho de ella. Por ejemplo, si se declara una función para que devuelva un entero, es imprescindible que se coloque un return final para salir de esa función, independientemente de que haya otros en medio del código que también provoquen la salida de la función. En caso de no hacerlo se generará un a 37 mailxmail - Cursos para compartir lo que sabes advertencia (Warning), y el código Java no se puede compilar con "Warnings". int func() { if( a == 0 ) return 1; return 0; // es imprescindible porque se retorna un entero } 3.2.9. Clases Las clases son la parte fundamental de Java. Todo en Java forma parte de una clase, es una clase o describe como funciona una clase. por lo tanto, el conocimiento de las clases es fundamental para poder entender los programas Java. Todas las acciones de los programas Java se colocan dentro del bloque de una clase o un objeto, todos los métodos se definen dentro del bloque de la clase. Java no soporta funciones o variables globales, todo debe pertenecer a una clase. Así pues, el esqueleto de cualquier aplicación Java se basa en la definición de una clase. Todos los datos básicos, como los enteros, se deben declarar en las clases antes de hacer uso de ellos. En C la unidad fundamental son los archivos con código fuente, en Java son las clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de una clase. La palabra claveimport (equivalente al #include) puede colocarse al principio de un archivo, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia con el contenido del archivo que se indique, que consistirá, como es de suponer, en más clases. 3.2.9.1.Tipos de clases Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clases que se han visto, pero hay más modificadores. Los tipos de clases que se pueden definir son: -abstract Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se instancia, sino que se utiliza como clase base para la herencia. -final. 38 mailxmail - Cursos para compartir lo que sabes Una clase final se declara como la clase que termina una cadena de herencia. No se puede heredar de una clase final. Por ejemplo, la clase Math es una clase final. -public. Las clases public son accesibles desde otras clases, bien sea directamente o por herencia. Son accesibles dentro del mismo paquete en el que se han declarado. Para acceder desde otros paquetes, primero tienen que ser importadas. -synchronizable. Este modificador especifica que todos los métodos definidos en la clase son sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde distintos hilos (t hreads) de programación; el sistema se encarga de colocar las banderas (flags) necesarias para evitarlo. Este mecanismo hace que desde hilos diferentes se puedan modificar las mismas variables sin que haya problemas de que se sobrescriban. -protected. Una clase definida como protected puede ser utilizadas desde cualquier clase del mismo paquete, sin posibilidad, a diferencia de las clases public, de ser accedidas desde otros paquetes, aun importandolas. 3.2.9.2.Paquetes de clases Al crear una clase se puede incluir en un paquete que se compilará como tal, es decir, la aplicación no funcionará si falta alguna de las clases integrantes del paquete. La agrupación en paquetes es bastante útil para motivos de organización y portabilidad, ya que nos obliga a tener todas las clases relacionadas a un proyecto en la misma carpeta o directorio. Para incluir una clase en un paquete se hace de la siguiente forma: package Clases; //esta línea indica que esta clase forma parte del paquete //clases public MiClase { int i; .... 3.2.10. Variables y métodos de instancia 39 mailxmail - Cursos para compartir lo que sabes Una clase en Java puede contener variables y métodos. Las variables pueden ser tipos primitivos como int, char, etc. Los métodos son funciones. Por ejemplo, en el siguiente segmento de código podemos observarlo: public MiClase { int i; public MiClase() { i = 10; } public void Suma_a_i( int j ) { i = i + j; } } La clase MiClase contiene una variable (i) y dos métodos, MiClase que es el constructor de la clase y Suma_a_i( int j ). 3.2.10.1.Ámbito de una variable Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las variables de Java sólo son válidas desde el punto donde están declaradas hasta el final de la sentencia compuesta que la engloba. Se pueden anidar estas sentencias compuestas, y cada una puede contener su propio conjunto de declaraciones de variables locales. Sin embargo, no se puede declarar una variable con el mismo nombre que una de ámbito exterior. El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C y C++ son distintas, porque están declaradas dentro de ámbitos diferentes. En Java, esto es ilegal. Class ambito { int i = 1; { // ámbito exterior // crea un nuevo ámbito 40 mailxmail - Cursos para compartir lo que sabes int i = 2; // error de compilación } } 3.2.10.2.Métodos y Constructores Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases. El constructor es un tipo específico de método que siempre tiene el mismo nombre que la clase. Cuando se declara una clase en Java, se pueden declarar uno o más constructores opcionales que realizan la inicialización cuando se instancia (se crea) un objeto de dicha clase. Utilizando el código de ejemplo anterior, cuando se crea una nueva instancia de MiClase, se crean (instancian) todos los métodos y variables, y se llama al constructor de la clase: MiClase mc; mc = new MiClase(); La palabra clave new se usa para crear una instancia de la clase. Antes de ser instanciada con new no consume memoria, simplemente es una declaración de tipo. Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puede referenciar la variable (de instancia) i con el nombre del objeto: mc.i++; // incrementa la instancia de i de mc Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc: mc.Suma_a_i( 10 ); y ahora la variable mc.i vale 21. 3.2.10.3.Finalizadores Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recoger automáticamente todos los objetos que se salen del alcance. No obstante proporciona un método que, cuando se especifique en el código de la clase, el 41 mailxmail - Cursos para compartir lo que sabes reciclador de memoria (garbage collector) llamará: // Cierra el canal cuando este objeto es reciclado protected void finalize() { close(); } 3.3. Programación multihilo (multithread) Se le llama programación multihilo o multithreaded (en inglés) al método de programación donde el flujo del programa se diseña como caminos o hilos independientes que pueden ejecutarse al mismo tiempo, Lo anterior significa que con Java se pueden realizar aplicaciones que realicen varias tareas a la vez y que se puede controlar cada una de estas tareas independientemente del resto. La programación multihilo en Java se logra realizando una agrupación en hilos como si de procedimientos se tratase, aunque se usa la clase thread. En el siguiente ejemplo veremos como crear un hilo en lenguaje Java y como ordenar su ejecución: //La clase principal public class MiClase{ //código de la clase //Se deben instanciar las clases hilo. Hilo1 H1; Hilo2 H2; H1 = new Hilo1 ("1"); H2 = new Hilo2 (); //Llamadas a los hilos H1.start H2.start 42 mailxmail - Cursos para compartir lo que sabes H2.start } //Hilos class Hilo1 extends Thread { //Extiende (hereda) la clase Thread //Constructor de la clase Hilo1 public Hilo1(String nom){ //este constructor utiliza un nombre de hilo } pulic void run(){ //run contiene el código que se ejecutará } } class Hilo2 extends Thread { public Hilo2(){ /*Variante del constructor que no pide nombre de hilo (define los nombres de hilo como Thread1, Thread2... Treadn.*/ } pulic void run(){ } } "Un programa inicia la ejecución de un hilo invocando el método start de ese hilo, a su vez start, invoca el método r u n. Una vez que start echa a andar el hilo, regresa de inmediato el control a su invocador. De ahí en adelante el invocador se ejecutará en paralelo con el hilo iniciado. El método start lanza una excepción de estado de hilo ilegal (IllegalThreadStateException) si el hilo que está tratando de iniciar ya se está ejecutando."[1] Desafortunadamente nuestro espacio para la explicación del lenguaje Java es reducido puesto que no es ese nuestro tema principal, aunque se incluye por considerarse necesario, y no podemos dedicarle lo merecido al interesante tema de la programación multihilo. Recomendamos al lector el libro citado al pie de esta 43 mailxmail - Cursos para compartir lo que sabes la programación multihilo. Recomendamos al lector el libro citado al pie de esta página, dedica un capítulo (que tal vez resulte también insuficiente pero es mejor que dos cuartillas) completo a este tema. [1]Deitel y Deitel, Cómo programar en Java, pág. 673. 44 mailxmail - Cursos para compartir lo que sabes 4. ¿Qué es J2ME? 4. ¿QUÉ ES J2ME? J2ME (Java 2 Micro Edition) es la plataforma basada en el lenguaje Java que Sun Microsystems ha creado para la programación de dispositivos inalámbricos pequeños como teléfonos celulares, paginadores y PDA. La figura 4.1 muestra como está compuesta la plataforma J2ME. Figura 4.1. Componentes de J2ME 4.1.Perfiles, configuraciones y máquinas virtuales La edición micro de Java se compone, además del lenguaje, de una máquina virtual, configuraciones, perfiles y paquetes adicionales (Fig. 4.1). La máquina virtual es la base de la plataforma, es el interprete del lenguaje y sobre la cual se han de ejecutar las aplicaciones, también sobre esta máquina virtual corren las configuraciones (CDC y CLDC), las cuales incorporan apis básicas para la creación de aplicaciones y sirven de soporte a los perfiles. Los perfiles incluyen la mayor parte de las clases y apis que se van a utilizar en la programación, como 45 mailxmail - Cursos para compartir lo que sabes pueden ser instrucciones de entrada y salida o de inicio y terminación de la aplicación. Los paquetes adicionales son aquellos que la especificación de la tecnología inalámbrica Java (JSR185) no establece como obligatorios para incorporar en los dispositivos. Ejemplos de esto pueden ser las apis de mensajes inalámbricos (WMAPI) y de multimedia (WMAPI). 4.2. MIDP 2.0 Hasta este momento es el único perfil aplicado a los dispositivos en el mercado, aunque se están investigando algunos otros, como el especializado en PDA. MIDP 2.0 incorpora apis de interfaz de usuario, de ciclo de vida del programa, almacenamiento persistente, juegos, trabajo en red y multimedia. Según la especificación de la tecnología inalámbrica de Java todo dispositivo que soporte MIDP 2.0 debe incluir mínimamente las siguientes características: -Debe permitir archivos Java (JAR) de más de 64 KB. y archivos descriptores de aplicaciones (JAD) mayores a 5 KB. -Se debe permitir a cada MIDlet la utilización de 30 KB de almacenamiento persistente y se recomienda que las MIDlets incluyan información acerca de el almacenamiento mínimo con el que trabajan correctamente. -El espacio de memoria libre para una aplicación ejecutándose (Heap o del montón) debe ser por lo menos de 256 KB. -Soporte para pantallas de 125 x 125 pixeles, con una profundidad de color de 12 bits. -Se deben incluir la capacidad de que el dispositivo reaccione a eventos de tiempo (una alarma a determinada hora, los llamados ticklers o despertadores). -Mecanismos para tomar un número telefónico del directorio del equipo. -Soporte para imágenes en formato JPEG y PNG. -Acceso a contenidos multimedia por el protocolo HTTP 1.1. 4.3. CLDC 1.1 Además de la Configuración para Dispositivos Conectados (CDC), CLDC 46 mailxmail - Cursos para compartir lo que sabes Además de la Configuración para Dispositivos Conectados (CDC), CLDC (Connected Limited Device Configuration) es la única opción en configuraciones en la tecnología inalámbrica Java, aunque esta última está dedicada a la clase de aparatos que nos ocupan. CLDC es la base para que los perfiles (como MIDP o PDAP) funcionen, proveyendo las apis básicas y la máquina virtual (KVM). CLDC está diseñada para equipos microprocesadores RISC o CISC de 16 a 32 bits y con una memoria mínima de 160 KB para la pila de la tecnología Java. La JSR185 pide como requisitos mínimos para todo equipo que implemente CLDC 1.0 o 1.1 -Soporte mínimo para diez hilos relacionados con aplicaciones (MIDlets). -Usar zonas de tiempo personalizables, con referencia en el formato de zonas de tiempo GMT (GMT \7:00, por ejemplo). -Soporte para propiedades de carácter y conversiones mayúsculas-minúsculas en los bloques suplementales Unicode para Basic Latin y Latin-1 (nuestros caracteres). 4.4. KVM Como ya se explicó anteriormente la máquina virtual es la base de la plataforma Java. En el caso de la plataforma J2ME, debido a las capacidades limitadas de almacenamiento, memoria, procesamiento y pantalla de los dispositivos; no se puede integrar una máquina virtual Java (JVM) del las dimensiones de J2SE o J2EE. Por esto se ha creado una nueva máquina virtual: KVM (Kilobyte Virtual Machine) KVM es una máquina virtual Java compacta y portable específicamente diseñada para ser la base de desarrollo en dispositivos pequeños y de recursos limitados. Actualmente CLDC trabaja sobre KVM. Además KVM está diseñada para mantener los aspectos centrales del lenguaje Java ejecutándose en unos cuantos kilobytes de memoria (de ahí su nombre). Aunque KVM deriva de la máquina virtual J2SE (JVM), algunas características de esta última han sido eliminadas para soportar CLDC, debido a que resultan demasiado costosas de implementar o su presencia supone problemas de seguridad, 47 mailxmail - Cursos para compartir lo que sabes resultando una KVM con las siguientes limitantes: -Soporte de punto flotante.- KVM no soporta números de punto flotante, esto debido a que la mayoría de los dispositivos en los que se implementa no lo soportan tampoco. -Finalización.- Las apis CLDC no incluyen el método object.finalize, así que no se pueden hacer operaciones de limpieza final antes de que el recolector de basura tome los objetos. -Manejo de errores.- CLDC sólo define tres clases error: java.lang.Error, java.lang.OutOfMemoryError y java.lang.VirtualMachineError. Todo tipo de errores que no sean en tiempo de ejecución se manejan de modo dependiente del dispositivo, esto incluye la finalización de una aplicación o reinicio del dispositivo. -Interfaz Nativa Java (JNI).-No se implementa JNI (posibilidad de incluir código en C dentro de clases Java), primeramente por motivos de seguridad, aunque también es considerado excesivo dadas las limitantes de memoria del dispositivo al que se dirige. -Cargadores de clases definidas por el usuario.- KVM debe tener un cargador de clases que no pueda ser manipulado o remplazado por el usuario, principalmente por razones de seguridad. -No hay soporte para la API reflection. La API reflection es más bien usada para aplicaciones de bajo nivel (depuradores, constructores GUI, etc). La falta de esta API impide también la serialización de objetos. -Grupos de hilos o hilos "demonio".- Aunque CLDC soporta programación multihilo, no soporta grupos de hilos o hilos "demonio". Si requiere usar operaciones para grupos de hilos use objetos de colección para almacenar los objetos hilo a nivel de aplicación. -Referencias débiles.- Ninguna aplicación construida con CLDC puede requerir referencias débiles. 4.4.1.Verificador de clases En la máquina virtual de J2SE, el verificador de clases es el responsable de rechazar archivos de clase no válidos. Una máquina virtual que soporte CLDC (ahora KVM) también debe ser capaz de rechazar estos archivos. Sin embargo, el proceso 48 mailxmail - Cursos para compartir lo que sabes de verificación de clases es tardado y costoso y, por lo tanto, no recomendable para equipos con recursos limitados. Los diseñadores de KVM decidieron mover la mayor parte del trabajo de verificación de clases fuera del dispositivo, es decir, hacia la computadora de escritorio donde las clases son compiladas o el servidor de donde se descargan. A este proceso de verificación se le llama preverificación. Los dispositivos son únicamente responsables de ejecutar algunas pruebas en la clase preverificada para asegurarse de que aún es válida. 4.5. Apis en J2ME-CLDC Las apis para CLDC tienen el objetivo de proveer un conjunto de bibliotecas mínimo y útil para el desarrollo de aplicaciones y definición de perfiles para una gran variedad de aparatos. Las apis CLDC se pueden dividir en dos categorías: -Clases derivadas de apis J2SE. Estas se localizan en los paquetes java.lang, java.io y java.util y se derivan de apis del Java Developement Kit (JDK) 1.3. En las tablas 4.1 y 4.2 se muestra una lista detallada de las clases incluidas. -Clases específicas para CLDC. Se localizan en el paquete javax.microedition y sus subpaquetes y se explican con detalle abajo en la tabla 4.4. 4.5.1.Clases heredadas (derivadas) CLDC hereda algunas clases de sistema, entrada y salida (E/S) y utilidades de la plataforma J2SE, las tablas 4.1 y 4.2 muestran los paquetes y sus clases que se heredaron de la edición estándar. Tabla 4.1. Clases de NO excepción heredadas de J2SE Paquete Clases Boolean, Byte, Character, Class, Integer, Long, Math, Object, java.lang Runnable, Runtime, Short, String, StringBuffer, System, Thread, Throwable java.io ByteArrayInputStream, ByteArrayOutputStream, DataInput, DataOutput, DataInputStream, DataOutputStream, InputStream, OutputStream, InputStreamReader, OutputStreamWriter, PrintStream, Reader, Writer 49 mailxmail - Cursos para compartir lo que sabes java.util Calendar, Date, Enumeration, Hashtable, Random, Stack, TimeZone, Vector Tabla 4.2. Clases de excepción heredadas de J2SE Paquete Clases ArithmeticException, ArrayIndexOutOfBoundException, ArrayStoreException, ClassCastException, ClassNotFoundException, Error, Exception, IllegalAccessException, IllegalArgumentException, IllegalMonitorStateException, java.lang IllegalThreadStateException, IndexOutOfBoundException, InstantiationException, InterruptedException, OutOfMemoryError, NegativeArraySizeException, NumberFormatException, NullPointerException, RuntimeException, SecurityException, StringIndexOutOfBoundException, VirtualMachineError java.io EOFException, IOException, InterruptedException, UnsupportedEncodingException, UTFDataFormatException java.util EmptyStackException, NoSuchElementException 4.5.1.1.Soporte de propiedades En CLDC no hay implementación para la clase java.util.Properties, sin embargo las propiedades mostradas en la tabla 4.3 están disponibles y se pueden obtener llamando al método System.getProperty(clave), donde clave puede ser cualquiera de las siguientes: Tabla 4.3. Claves de las propiedades CLDC. Valor predeterminado Clave Explicación microedition.platform La plataforma o dispositivo null huésped. microedition.encoding Codificación predeterminada de caracteres. ISO8859_1 microedition.configurations Configuración y versión J2ME actual. CLDC-1.0 microedition.profiles Nombre de los perfiles soportados. null 50 mailxmail - Cursos para compartir lo que sabes 4.5.2.Clases específicas de CLDC Las siguientes clases (Tabla 4.4) son específicas de CLDC y están contenidas en el paquete javax.microedition.io: Tabla 4.4. Clases específicas de CLDC. Paquete Clases Connection, ConnectionNotFoundException, Connector, ContentConnector, Datagram, javax.microedition.io DatagramConnection, InputConnection, OutputConnection, StreamConnection, StreamConnectionNotifier 4.5.2.1.Clases específicas de MIDP Además de las clases específicas de MIDP contenidas en javax.microedition.rms, javax.microedition.midlet y javax.microedition.lcdui; están disponibles las siguientes clases, interfaces y clases de excepción: - IllegalStateException. Clase en el paquete java.lang. - Timer y TimerTask. Clases en el paquete java.util. HttpConnection. Interfaz para acceso a una red por el protocolo HTTP contenida en el paquete javax.microedition.io. 51 mailxmail - Cursos para compartir lo que sabes 5. Programación MIDP 5. PROGRAMACIÓN MIDP En el presente capítulo se tratarán las características básicas de la programación de aplicaciones MIDP, NO se trata de un tutorial de programación, únicamente se revisarán los aspectos generales que hacen a la programación MIDP diferente de el resto de las aplicaciones Java. En el siguiente capítulo se desarrollará una aplicación y se explicará el funcionamiento de cada instrucción o bloque de instrucciones. Pero primero se deben conocer los pasos a seguir en el proceso de desarrollo para facilitar su comprensión y dar una idea previa de lo que se deberá hacer. 5.1.Herramientas necesarias A continuación se proporciona una lista con las herramientas necesarias para programar MIDlets. -Plataforma de desarrollo Java (J2SDK, por ejemplo) disponible en java.sun.com. -Editor de textos (block de notas, edit, vi, emacs, etc.). -Compilador CLDC (1.0, 1.1) y al menos un perfil (MIDP, PDAP), ambos disponibles en java.sun.com. Hay algunos paquetes de desarrollo Java que incluyen los dos últimos requisitos, además de proveer de otras herramientas que pueden facilitar la programación. -Java Wireless Toolkit: Este paquete se puede descargar del sitio Java de Sun Microsystems, provee de un compilador (incluye CLDC y MIDP) y organizador de proyectos. Se crea en este programa un proyecto y automáticamente se generan las carpetas donde se han de guardar los códigos fuente, las clases, imágenes y otros accesorios que la aplicación utilice. Disponible para Windows y Linux. En la figura 5.1 se muestra la barra de herramientas de JWT (Java Wireless Toolkit). 52 mailxmail - Cursos para compartir lo que sabes Figura 5.1. Java Wireless Toolkit. -Nokia Developer Suite for J2ME: En la sección de desarrolladores de Nokia (www.nokia.com) se puede descargar este entorno de desarrollo, incluye todo lo que se necesita para la programación de MIDlets (a excepción del SDK). Se puede instalar en plataformas Windows 2000, XP y Linux. Este paquete puede resultar poco conveniente para computadoras poco actuales, ya que entre sus requerimientos se mencionan 256 MB de memoria RAM y procesador de 300 Mhz. -Motorola SDK for J2ME. Sólo existe para Windows, es otro kit de desarrollo completo al que sólo hay que agregar el SDK de Java. -Code Warrior. Quizá uno de los mejores entornos integrados de desarrollo (IDE) para la edición micro de la plataforma Java. Se puede descargar gratis después de suscribirse en el sitio de programadores de Motorola, aunque en versión de evaluación por treinta días. -Sun ONE Studio: Otro entorno completo, bastante útil, incluye editor de texto, compilador, preverificador y simuladores para probar las aplicaciones. En el sitio oficial de Sun se puede descargar una evaluación de 60 días o comprar la versión completa. En el presente documento se utilizará el Wireless Toolkit de Java, debido a que es más pequeño (en espacio de disco) y, por lo tanto, más fácil de obtener con una conexión telefónica a Internet, además de que está disponible para Linux que será la plataforma empleada para la programación. 53 mailxmail - Cursos para compartir lo que sabes 5.2. Ciclo de desarrollo de una aplicación MIDP Las aplicaciones MIDP (MIDlets) siguen un proceso desde que son ideadas hasta que están instaladas y ejecutándose en los dispositivos. Dicho proceso es el siguiente: -Escritura. -Compilación y preverificación. -Prueba y corrección. -Empaquetado. -Prueba del paquete. 5.2.1. Escritura de la aplicación Hay algunas reglas para el desarrollo de MIDlets, además de las establecidas por el lenguaje, que deben ser consideradas: -Toda MIDlet debe importar los paquetes javax.microedition.midlet.* y javax.microedition.lcdui.*. import javax.microedition.midlet.*; import javax.microedition.lcdui.*; - javax.microedition.midlet.*. Define que se utilizará MIDP. - javax.microedition.lcdui.*. Proporciona las apis de interfaz de usuario. -Cada MIDlet debe extender la clase MIDlet. public class FirstMIDlet extends MIDlet { -Una MIDlet no debe tener ningún método public static void main(), contrario a lo que sucede en las aplicaciones Java de escritorio. Constructor de la clase public FirstMIDlet() { } Inicia la MIDlet, en lugar de main(). public void startApp() { } 5.2.2. Compilar y preverificar 54 mailxmail - Cursos para compartir lo que sabes Cuando se utiliza Java Wireless Toolkit, o algún otro entorno de desarrollo, el proceso de compilación y preverificación es sumamente sencillo: Una vez escrito el código de las clases que compongan la aplicación se ejecuta JWT y se abre la aplicación a compilar. En JWT se puede también depurar el código, probar y empaquetar la aplicación, etc. Para compilar la MIDlet, una vez abierta, hay que seleccionar la opción "Build", esto creará el archivo de clase o bien indicará los errores que se tengan. El compilado con JWT incluye la preverificación. También se puede compilar y preverificar el código fuente desde la linea de comando del sistema operativo. Teniendo correctamente instalado el SDK de Java y CLDC se utiliza: javac -d .| bootclasspath [ruta] [archivo.java] para crear el archivo de clase en el mismo directorio (-d .\) que el de código y para preverificar el resultado de la compilación se utiliza el comando: preverify -classpath [ruta_clases] este comando preverifica todos los archivos de clases en el directorio ruta_clases y guarda el resultado en un subdirectorio, dentro de esa ruta, llamado "output", para preverificar una sola clase hay que especificar su nombre. 5.2.3. Probando la MIDlet Si no se utiliza JWT la aplicación puede ser probada, desde linea de comandos, con la siguiente instrucción: m i d p [nombre_MIDlet] Aunque si su aplicación consta de varias MIDlets o archivos de clase y requiere dejar que el usuario escoja cuales ejecutar, entonces es necesario empaquetar la aplicación. Utilizando JWT este trabajo se simplifica bastante, sólo se abre un proyecto y, si ya está compilado y preverificado, se selecciona "Run" o "Ejecutar" para ver el resultado en un emulador (Fig. 5.2). 55 mailxmail - Cursos para compartir lo que sabes Figura 5.2. Una de las vistas del emulador de JWT 5.2.4. Empaquetado Si una aplicación consta de múltiples clases, una archivo .jar es usado para agruparlas, así es más fácil de distribuir y desplegar. esto se puede hacer desde linea de comandos de la siguiente manera: jar cf [archivo.jar] [clases] El siguiente paso es crear el archivo descriptor de la aplicación (archivo .jad), 56 mailxmail - Cursos para compartir lo que sabes el cual provee información sobre el contenido del archivo .jar. Un ejemplo de archivo descriptor de aplicación es el siguiente: MIDlet-Name: MID1 MIDlet-Version:1.0.0 MIDlet-Vendor:Sun Microsystems, Inc. MIDlet-Description: Primera MIDlet MIDlet-Info-URL: http://java.sun.com/j2me. MIDlet-Jar-URL: MID1.jar MIDlet-Jar-Size: 1063 MicroEdition-Profile: MIDP-2.0 MicroEdition-Configuration: CLDC-1.0 MIDlet-1: MID,icono.png, MIDlet1 MIDlet-2: MID1,MIDlet2 Las primeras lineas resultarán, seguramente, más que obvias así que se explicará únicamente el último elemento (MIDlet-1), este contiene los datos de la primera MIDlet integrante del paquete (hay que recordar que un paquete se puede componer de muchas MIDlets) y consta de tres partes: nombre de la aplicación, ícono (opcional) y clase. El nombre aparecerá en la pantalla del dispositivo, acompañado del ícono (si existe), para que pueda ser seleccionado y la clase es el archivo compilado que se ejecuta al seleccionar un nombre. 5.2.5. Probar la aplicación empaquetada Una vez que se ha empaquetado la aplicación, se puede probar para asegurar su funcionamiento agregando la opción -descriptor al comando m i d p. midp -descriptor MID1.jad Utilizando en kit de Java es el mismo proceso que probar la aplicación sin empaquetar, ya que al realizar el paquete se genera automáticamente el archivo descriptor. 5.3. Mapeo de teclas Se le llama mapeo de teclas a la distribución que tienen las funciones del 57 mailxmail - Cursos para compartir lo que sabes dispositivo (navegación, comandos, selección, etc.) sobre las teclas exteriores, es decir, la función desplazarse hacia la derecha, puede estar colocada en la tecla con el número 6 de un teléfono y esa misma función puede operar sobre la tecla "é" de otro dispositivo. Cuando el programador requiere de aplicaciones altamente portables debe darle a este aspecto una gran importancia, no se puede permitir que un comando o función de su MIDlet quede inhabilitada a causa de un descuido de ese tipo. 5.4. Manejo de eventos Cuando no se utilizan cajas de texto, listas o formularios en una pantalla, se puede tomar el control de las acciones a realizar cuando se presione, libere o mantenga presionada cierta tecla del dispositivo. A la presión (rápida o detenida) y liberación de teclas se les conoce como eventos, aunque no son el único tipo de eventos, también pueden serlo el cierre repentino y deliberado de la aplicación, la llegada de un mensaje o una llamada telefónica, el encendido del teléfono, en fin, un evento es toda acción llevada a cabo por un agente exterior a la MIDlet y que influya sobre su operación. Un evento es, por tanto, una acción sobre la MIDlet, tomada por el usuario en tiempo de ejecución. 5.4.1.Llamadas Se conoce como Llamada (Callback en inglés) a las invocaciones a métodos definidos por el programador que son ejecutados por la aplicación en respuesta a eventos. Además de J2ME, los conceptos de llamadas y eventos son utilizados en muchos entornos de programación, principalmente aquellos con desarrollo de interfaces gráficas (Visual Basic, Delphi, Visual J, etc.). En lenguajes como C++, las llamadas se implementan mediante apuntadores, una función desvía su apuntador a otra al ocurrir un evento. Como Java no utiliza apuntadores, las llamadas se implementan mediante interfaces, las interfaces son clases que únicamente tienen métodos únicamente definidos, las acciones a tomar en cada método dependen del programador, es decir, una interfaz es como el esqueleto de una clase que debe ser llenado por el programador. En MIDP hay cuatro clases de llamadas de interfaces de usuario. -Comandos abstractos que son parte de la API de alto nivel. 58 mailxmail - Cursos para compartir lo que sabes -Eventos de bajo nivel que representan presión y liberación de teclas. -Llamadas al método paint() de una clase Canvas. -Llamadas al método Run de objetos ejecutables (de la interfaz Runnable) solicitados por una llamada al método CallSerially de la clase Dispaly. 5.4.2.Navegación en pantalla Un aplicación MIDP, a excepción de las demasiado sencillas, siempre utilizará más de una pantalla, por lo que resulta necesaria una herramienta que permita a los usuarios desplazarse de una pantalla a otra según lo requiera la propia MIDlet. El paquete javax.microedition.lcdui proporciona la clase Command, la cual captura la información de un evento. Un comando creado con la clase Command solamente contiene información sobre él mismo, no de la acción que realiza cuando está activado, la acción está definida en un objeto de la interfaz CommandListener asociado con la pantalla. A continuación se muestra como declarar un comando: Command miComando=new Command("Mio", Command.SCREEN,2); Los tres parámetros que el constructor de la clase Command debe incluir son los siguientes: -Etiqueta: Una cadena de caracteres usada para la presentación visual del comando. -Tipo: Especifica su intención, es decir, la acción que realizará el comando. Algunos tipos pueden ser: - Command.SCREEN. - Command.EXIT. - Command.BACK. - Command.ITEM -Prioridad: Describe la importancia del comando con respecto a otros en pantalla. Una prioridad de 1 indica al comando más importante. Cuando una MIDlet se ejecuta, el dispositivo escoge el lugar de un comando basándose en su tipo (abajo y a la izquierda para salir, por ejemplo) y coloca los comandos similares con base en su valor de prioridad. Considérese una aplicación con los siguientes comandos: 59 mailxmail - Cursos para compartir lo que sabes Command Salir=new Command("Exit", Command.EXIT,1); Command miCom=new Command("Mio1", Command.SCREEN,2); Command miCom2=new Command("Mio2", Command.SCREEN,2); En este ejemplo, el manejador de la aplicación coloca el comando Exit en la pantalla y crea un menú para mantener Info y Buy. Presionando la tecla correspondiente al menú, el usuario, es llevado a otra pantalla donde puede seleccionar alguna de las dos opciones. (Fig. 5.3) La clase Command provee de tres métodos para conocer tipo, etiqueta y prioridad del comando. -int getCommandType(). -String getLabel(). -int getPriority(). Figura 5.3. Ejemplo de la clase Command. 5.4.3. Eventos de alto nivel El manejo de eventos en la api de alto nivel se basa en un modelo de escucha o escuchante (listener, en inglés). Los objetos Screen y Canvas pueden tener "escuchantes" para comandos. Para que un objeto pueda ser un escuchante, debe implementar la interfaz CommandListener. Los comandos se agregan a un objeto con el método addCommand y se registra un escuchante con el método CommandListener. Ambos métodos pertenecen a la clase Displayable y son heredados por Screen y Canvas. 5.4.3.1.La interfaz CommandListener Esta interfaz es para MIDlets que necesitan recibir eventos de alto nivel. Esta interfaz tiene un método que todo escuchante debe implementar, el método CommanAction 60 mailxmail - Cursos para compartir lo que sabes CommanAction. public void CommanAction(Command c, Displayable d) { } El parámetro c es un objeto Command que identifica al comando que fue agregado al d (objeto Displayable) con el método addCommand. El parámetro d es el objeto visible (Form, Canvas, Screen) donde el evento ocurre. Dentro de las llaves ({ }) se introduce el código que se habrá de ejecutar al ocurrir un evento. public void commandAction(Command c, Displayable s) { if (c == CmdSalir) { destroyApp(false); notifyDestroyed(); } En el trozo de código mostrado arriba se emplea una decisión i f para identificar qué comando es el que ha sido activado, si se trata del comando CmdSalir se destruye la aplicación, es decir, se cierra y se notifica su destrucción. 5.4.3.2.La interfaz ItemStateListener Las aplicaciones usan esta interfaz para recibir eventos que indican cambios en el estado interno de los elementos en una pantalla, formulario. Esto pasa cuando el usuario hace cualquiera de las siguientes cosas. -Ajustar el valor de una barra de avance interactiva. -Ingresar o modificar el valor de un TextField. -Ingresar una nueva fecha u hora en una DateField. -Cambiar los valores seleccionados en un ChoiceGroup. Esta interfaz tiene un único método que un escuchante debe implementar. public void itemStateChanged(Item i){ } Para registrar un escuchante de esta interfaz se utiliza el método setItemStateListener setItemStateListener. ItemStateListener listener = new ItemStateListener(); Form.setItemStateListener(listener); 61 mailxmail - Cursos para compartir lo que sabes public void itemStateChanged(Item item) { } 5.4.4.Eventos de bajo nivel Si se usa la clase Canvas para tener acceso a eventos de entrada o para dibujar gráficos en la pantalla, es necesario manejar los eventos de bajo nivel. Aplicaciones como los juegos, pueden ser realizadas utilizando la clase Canvas, gracias a que esta posee métodos para controlar eventos de teclas y los llamados "eventos de juego". Los eventos de teclas se registran como códigos específicos que están directamente relacionados a las teclas del aparato. Los eventos de juego producen un código relacionado con la acción asignada a una tecla del dispositivo. 5.4.4.1.Eventos de teclas Como ya se mencionó, los eventos de teclas están relacionados directamente con las teclas del dispositivo. Los eventos de teclas definidos en la clase Canvas son: -KEY_NUM0. -KEY_NUM1. -KEY_NUM2. -KEY_NUM3. -KEY_NUM4. -KEY_NUM5. -KEY_NUM6. -KEY_NUM7. -KEY_NUM8. -KEY_NUM9. -KEY_STAR. -KEY_POUND. Los números del 0 al 9, el asterisco "*" y la almohadilla "#" son las teclas genéricas en todos los dispositivos, quizá existan otros eventos en diferentes dispositivos, pero por motivos de portabilidad, se recomienda solamente usar estos. 5.4.4.2.Eventos de juego 62 mailxmail - Cursos para compartir lo que sabes 5.4.4.2.Eventos de juego Debido a que en los equipos actuales varía mucho la distribución de las teclas, si se requiere una localización específica de estas, flechas de dirección, por ejemplo; es necesario utilizar los eventos de juego, estos eventos son los siguientes: -DOWN. -LEFT. -RIGHT. -UP. -FIRE. -GAME_A. -GAME_B. -GAME_C. -GAME_D. Anteriormente se explicó que cada aparato mapea sus teclas de forma diferente al resto. Con los eventos de juego se puede lograr que la aplicación reaccione a la presión de una tecla que indique movimiento hacia arriba en el dispositivo anfitrión, independientemente del código de esta tecla. 5.4.5.Métodos de manejo de eventos Para el manejo de eventos de bajo nivel, J2ME provee al programador de los siguientes métodos: - protected void keyPressed (int keyCode). - protected void keyReleased (int keyCode) - protected void keyRepeated (int keyCode) - protected void pointerPressed (int x, int y) - protected void pointerDragged (int x, int y) - protected void pointerReleased (int x, int y) - protected void showNotify () - protected void hideNotify () 63 mailxmail - Cursos para compartir lo que sabes - protected void hideNotify () - protected abstract void paint (Graphics g) - commandAction(). De la interfaz CommandListener. Cabe aclarar que los eventos de apuntador (pointer) no son aplicables a todos los dispositivos, sólo para aquellos que cuenten con un indicador de ese tipo (apuntador de ratón, por ejemplo), se puede comprobar si el equipo lo soporta utilizado los métodos hasPointerEvents y hasPointerMotionEvents. Otro método que podría no estar disponible en algún dispositivo es keyRepeated, se puede comprobar su operabilidad con el método hasRepeatEvents. 5.5. Principales componentes Son muchos y muy variados los componentes que incluyen las apis de MIDP y CLDC, además cada programador puede crear componentes propios tomando como base los ya existentes. Por lo anterior, se explicarán sólo aquellos que son considerados cómo básicos para la realización de aplicaciones en general. En la tabla 5.1 se muestra cada uno de los componentes y una breve descripción de estos. Tabla 5.1. Clases del paquete lcdui. Clase Descripción Alert Pantalla que muestra un mensaje al usuario y espera un cierto periodo de tiempo antes de pasar a la siguiente pantalla. AlertType Indica la naturaleza de las alertas (exclamación, error, etc.). Canvas Es la base para la escritura de aplicaciones que utilicen eventos de bajo nivel y para dibujar objetos gráficos en la pantalla. ChoiceGroup Un grupo de elementos seleccionables, colocados dentro de una forma (Form). Command Una estructura que encapsula la información semántica de una acción (p. ej. "Salir"). DateField Componente modificable para presentar en pantalla la información de hora y fecha. Display Representa el manejador de pantalla y dispositivos de entrada (teclas) del sistema. Displayable Característica de un objeto que tiene la capacidad de ser mostrado en la pantalla. 64 mailxmail - Cursos para compartir lo que sabes Font Representa las propiedades de la fuente (letra). Form Una pantalla que contiene una mezcla arbitraria de elementos (imágenes, cajas de texto, etc.) Gauge Una barra que puede ser utilizada como indicador de avance en un proceso. Graphics Provee la capacidad de presentar gráficos 2D simples. Image Muestra una imagen. ImageItem Proporciona el control de trazado (colocación) de una imagen en una forma o alerta Item Superclase para elementos que van a ser adheridos a una forma o alerta. List Muestra una lista de opciones. Screen La superclase de todas las clases de interfaz de usuario de alto nivel. StringItem Un elemento que puede contener una cadena de caracteres. TextBox Pantalla que permite al usuario insertar y modificar texto. TextField Un componente de texto modificable que puede ser colocado en una forma. Ticker Un texto que cruza continuamente la pantalla, puede ser agregado en todos los tipos de pantalla a excepción de canvas. En los siguientes apartados se verá como utilizar algunas de las clases listadas en la tabla anterior, esto mediante la explicación de programas sencillos que, aunque pudiesen carecer de utilidad, ilustran de forma clara la forma de implementar una clase determinada. 5.5.1.¡Hola mundo! Se iniciará esta serie de programas de ejemplo con el clásico código fuente de una aplicación que lo único que hace es mostrar un mensaje en la pantalla, en este caso el mensaje es "¡Hola mundo!". El código fuente es el siguiente: import javax.microedition.lcdui.*; import javax.microedition.midlet.*; Se comienza por importar los paquetes que son obligatorios para cualquier MIDlet. Esto puede variar dependiendo de la finalidad de la clase a crear, pero para una clase que será la principal de su paquete, es forzoso el importar las clases del paquete javax.microedition.midlet y lcdui se requiere para las operaciones de entrada y salida de información. 65 mailxmail - Cursos para compartir lo que sabes public class Holamundo extends MIDlet implements CommandListener { Se inicia el código de la clase, la cual extiende (o hereda) la clase MIDlet, necesaria, como ya se dijo, para la creación de clases ejecutables, además se implementa la interfaz CommandListener, esta interfaz define una estructura de clase en la que se deben declarar unos objetos llamados comandos, los cuales responderán a alguna acción del usuario. private Form Forma1; Se ha instanciado un objeto de la clase Form, que, como ya se vio, pertenece al paquete lcdui. Un objeto Form define una pantalla donde se agregarán objetos de entrada y salida de datos por teclado y pantalla, respectivamente. public Holamundo() { Forma1 = new Form("¡Hola mundo!"); Forma1.append(new StringItem(null, "¡Hola mundo!")); Forma1.addCommand(new Command("Salir", Command.EXIT, 1)); Forma1.setCommandListener(this); } En la sección de arriba se describe el constructor de la clase, el cual debe llevar el mismo nombre que la clase, sin especificar tipo y de acceso público. En un constructor de clase se establecen los valores que han de tomar las variables, se crean las instancias de las clases, en fin, se define como habrá de presentarse, al arrancar, la aplicación al usuario. Al iniciar el constructor se instancia un objetoForm con título "¡Hola mundo!" y de nombre Forma1, enseguida, a esa misma forma se le agrega un elemento SringItem sin etiqueta (título o descripción) y con un contenido igual a "¡Hola mundo!", se agrega también, un comando, los comandos son las opciones que, en un teléfono celular, aparecen en la parte inferior de la pantalla y que el usuario puede seleccionar presionando las teclas que se encuentran debajo del texto; ese comando tiene por texto o etiqueta la palabra "Salir", es del tipo EXIT y de prioridad 0. En un comando, la prioridad indica la importancia de ese comando con respecto a otros en la pantalla, dicha prioridad puede ir de 1 a 10, siendo 1 el valor de prioridad más alto. public void startApp() { Display.getDisplay(this).setCurrent(Forma1); 66 mailxmail - Cursos para compartir lo que sabes Display.getDisplay(this).setCurrent(Forma1); } El método startApp es el que se ejecuta después del constructor, en el se encuentran las instrucciones que serán ejecutadas por la aplicación (es como el procedimiento main de las aplicaciones en C o Java). En el ejemplo, se utiliza un objeto Display que se encarga del control de la pantalla, un método getDisplay(this), el cual arroja como resultado la pantalla del dispositivo (como salida de datos para la MIDlet this, la actual) y otro método setCurrent(Forma1), que indica que lo que se ha de mostrar en la pantalla será lo contenido en Forma1. public void pauseApp() {} public void destroyApp(boolean unconditional) {} public void commandAction(Command c, Displayable s) { notifyDestroyed(); } } Las funciones pauseApp y destroyApp, también son obligatorias de cualquier clase MIDP (sea la principal del proyecto o no), en pauseApp se pueden definir las acciones a tomar cuando la aplicación sea pausada por el usuario, por la misma aplicación o por otra; destroyApp indica lo que se realizará antes de cerrar la aplicación. El método commandAction define lo que se hará con los comandos de la MIDlet, como en el ejemplo sólo se tiene un comando basta con notificar la destrucción de la aplicación, 5.5.2.TextBox La clase TextBox, genera objetos de pantalla completa, como Form,la diferencia es que TextBox únicamente permite la introducción de texto y el manejo 67 mailxmail - Cursos para compartir lo que sabes de comandos. A continuación se presenta un ejemplo sencillo: import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class TextBoxDemo extends MIDlet implements CommandListener { private Command CmdSalir; private Display display; private TextBox t = null; De esta primera parte sólo hay que resaltar la creación de un objeto TextBox de nombre t y valor nulo. public TextBoxDemo() { display = Display.getDisplay(this); CmdSalir = new Command("Salir", Command.EXIT, 2); t = new TextBox("Hola", "Esto es una prueba", 256, 0); t.addCommand(CmdSalir); t.setCommandListener(this); } En el constructor de la MIDlet se observa la designación del contenido de la pantalla (esta MIDlet), la creación de un comando de salida de prioridad 2, la instanciación de t con título "Hola", texto inicial "Esto es una prueba", longitud máxima de texto de 256 caracteres y tipo 0, los tipos son valores constantes que la clase TextBox interpreta para permitir la entrada de algunos caracteres e impedir la de otros, en el ejemplo el tipo 0 es la constante Textfield.ANY, que indica que se puede introducir cualquier carácter (también existen Textfield.NUMERIC, 68 mailxmail - Cursos para compartir lo que sabes TextField.DECIMAL y TextField.PHONENUMBER, entre otras). Después de creado el objeto t, se le agrega el comando CmdSalir. public void startApp() { display.setCurrent(t); } En el método startApp se indica que el elemento en pantalla será t. public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable s) { if (c == CmdSalir) { destroyApp(false); notifyDestroyed(); } } } Cuando una MIDlet tiene más de un comando (aunque no es el caso de esta) se puede realizar una decisión con el parámetro c para definir las acciones a tomar en cada comando. 5.5.3.TextField 69 mailxmail - Cursos para compartir lo que sabes El elemento Textfield trabaja sobre una forma (Form), por lo que ésta debe ser declarada e instanciada antes de utilizar el Textfield. private Form mainForm; mainForm = new Form("Text Field"); private TextField txt1; txt1=newTextField("Cualquier carácter", "", 15, TextField.ANY) Una vez instanciada la forma, se utiliza el método append para crear en ella los campos de texto que consideremos necesarios: mainForm.append(txt1); mainForm.append(new TextField("E-Mail", "", 15, TextField.EMAILADDR)); mainForm.append(new TextField("Entero", "", 15, TextField.NUMERIC)); mainForm.append(new TextField("Decimal", "10.5", 15, TextField.DECIMAL)); mainForm.append(new TextField("Teléfono", "", 15, TextField.PHONENUMBER)); mainForm.append(new TextField("Password", "", 15, TextField.PASSWORD)); mainForm.append(new TextField("URL", "", 15, TextField.URL)); Como se puede observar, el constructor de la clase Textfield lleva cuatro parámetros, el primero de ellos indica la etiqueta que habrá de llevar el campo de texto, en el segundo se debe especificar el valor inicial, el siguiente es la longitud (en caracteres) que habrá de tolerar el campo y el último indica el filtro a utilizar para dicho campo, es decir, que caracteres admitirá. Quizá el lector observe que, el utilizar la palabra clave new dentro de los 70 mailxmail - Cursos para compartir lo que sabes paréntesis del método append podría limitar el control que se tenga sobre los objetos de la forma, por lo tanto es recomendable declarar e instanciar antes los objetos y sólo nombrarlos en append, como se hizo con t x t 1. Hay varios filtros además de los mostrados en el ejemplo, se recomienda al lector interesado revisar la especificación de la clase Textfield. 5.5.4.Datefield Hasta ahora no se ha descrito, en las clases TextField y TextBox, un método relativamente fácil para introducir una fecha, los signos "/" en un teléfono son un poco difíciles de localizar, sin contar con que los números son la última o una de las últimas opciones en el orden de las teclas de la mayoría de los equipos, estos problemas no lo son tanto si se utiliza la clase DateField. Enseguida se explica cómo. public class DateFieldDemo extends MIDlet implements CommandListener{ private final static Command CmdSalir = new Command("Salir", Command.EXIT,1); private boolean primera; private Form mainForm; public DateFieldDemo() { Primera = true; Forma = new Form("Ejemplo de fecha"); } En esta clase de nombre DateFieldDemo, se utilizan por primera vez los cambios de pantalla, implícitos en la clase DateField, es por esto que se crea una variable lógica Primera que servirá para no instanciar los objetos dos veces (al iniciar la MIDlet y al volver del cambio de pantalla). 71 mailxmail - Cursos para compartir lo que sabes protected void startApp() { if(Primera) { Forma.append(new DateField("Fecha", DateField.DATE)); Forma.append(new DateField("Hora", DateField.TIME)); Forma.append(new DateField("Fecha y hora", DateField.DATE_TIME)); Forma.addCommand(CmdSalir); Forma.setCommandListener(this); Primera = false; } Display.getDisplay(this).setCurrent(mainForm); } Además de los ya conocido, en startApp se agregan a la forma tres campos de fecha, el primero pedirá, según la forma que tenga dispositivo de hacerlo, la fecha; el segundo pedirá la hora y el tercero ambos datos. protected void destroyApp(boolean unconditional) { } protected void pauseApp() { } public void commandAction(Command c, Displayable d) { if (c == CmdSalir) { destroyApp(false); notifyDestroyed(); } } 72 mailxmail - Cursos para compartir lo que sabes } } 5.5.5.StringItem Cuando lo que se desea no es pedir al usuario un dato, sino mostrarle información, seguramente no es recomendable utilizar clases cuyo contenido pueda ser alterado. Para esto se utiliza la claseStringItem. public class StringItemDemo extends MIDlet implements CommandListener, ItemCommandListener { private Display display; private Form mainForm; private final static Command IR = new Command("Ir", Command.ITEM, 1); private final static Command PRES = new Command("Pres", Command.ITEM, 1); private final static Command Salir = new Command("Salir", Command.EXIT, 1); En esta primera parte del código de la clase, se puede ver que se ha implementado una nueva interfaz, ItemCommmandListener, la cual se utiliza para agregar comandos, no sólo a los objetos de pantalla completa (como TextBox o Form), sino a los agrupados en estos objetos. Ahora podemos utilizar los comandos de tipo ITEM. protected void startApp() { 73 mailxmail - Cursos para compartir lo que sabes protected void startApp() { display = Display.getDisplay(this); mainForm = new Form("String Item Demo"); mainForm.append("Esto es sólo una etiqueta"); StringItem item = new StringItem("Etiqueta de String Item: ", "Texto de String Item"); mainForm.append(item); item = new StringItem("Etiqueta: ", "texto"); mainForm.append(item); item = new StringItem("Hipervínculo ", "Ir a", Item.HYPERLINK); item.setDefaultCommand(IR); item.setItemCommandListener(this); mainForm.append(item); item = new StringItem("Botón ", "Presionar", Item.BUTTON); item.setDefaultCommand(PRES); item.setItemCommandListener(this); mainForm.append(item); mainForm.addCommand(Salir); mainForm.setCommandListener(this); display.setCurrent(mainForm); } En startApp sólo se muestran las posibilidades que se tienen para escribir un texto que el usuario no pueda modificar y que además pueda ejecutar alguna acción. Desde simplemente escribir "Esto es sólo una etiqueta" hasta un botón que, al ser presionado, puede realizar una tarea. En el ejemplo, se distinguen dos tipos 74 mailxmail - Cursos para compartir lo que sabes al ser presionado, puede realizar una tarea. En el ejemplo, se distinguen dos tipos de constructores de StringItem, el primero, que tiene como parámetros dos cadenas de texto (etiqueta o título y contenido) y el segundo, que además permite la definición de un tipo de texto (hipervínculo o botón). public void commandAction(Command c, Item item) { if (c == IR) { String text = "Ir a la URL..."; Alert a = new Alert("URL", text, null, AlertType.INFO); display.setCurrent(a); } else if (c == PRES) { String text = "Do an action..."; Alert a = new Alert("Action", text, null, AlertType.INFO); display.setCurrent(a); } } public void commandAction(Command c, Displayable d) { destroyApp(false); notifyDestroyed(); } Tal vez llame la atención el hecho de tener dos funciones con el mismo nombre (CommandAction), pero hay que observar los parámetros que toman al activarse, ambas capturan un evento en el teclado, pero la primera captura los eventos ocasionados en el componente de la forma que esté seleccionado, mientras el otro se ocupa de los eventos generales y funcionan solamente sobre los eventos 75 mailxmail - Cursos para compartir lo que sabes protected void destroyApp(boolean unconditional) { protected void pauseApp() { } } } 5.5.6.ChoiceGroup Los grupos de selección o ChoiceGroup, por su nombre en inglés, son listados de elementos en los que se puede seleccionar uno o varios de ellos y permiten realizar operaciones (principalmente comparaciones lógicas) con dicha selección. import javax.microedition.lcdui.*; import javax.microedition.midlet.MIDlet; public class Choice1 extends MIDlet implements CommandListener { private final static Command CMD_Salir = new Command("Salir", Command.EXIT,1); private Display display; private boolean primera; private Form Forma; public ChoiceGroupDemo() { primera = true; } Hasta este punto todo se ve como en los ejemplos anteriores, se importan las clases, se define la clase con extensiones e implementaciones, se crea un comando 76 mailxmail - Cursos para compartir lo que sabes clases, se define la clase con extensiones e implementaciones, se crea un comando que se utilizará para salir de la MIDlet, se declaran los objetos y variables a utilizar y se determina lo que ha de hacer el constructor. protected void startApp() { if(primera) { display = Display.getDisplay(this); Forma = new Form("Choice Group"); Forma.append("Choice Group"); Image[] ArrImg = null; try { Image Imag = Image.createImage("/midp/uidemo/icono.png"); ArrImg = new Image[] { Imag, Imag, Imag, Imag }; } catch (java.io.IOException err) { } String[] arrString = { "Opcion A", "Opcion B", "Opcion C", "Opcion D" }; ChoiceGroup[] grupos = { new ChoiceGroup("Exclusivo", ChoiceGroup.EXCLUSIVE, arrString,ArrImg), new ChoiceGroup("Multiple", ChoiceGroup.MULTIPLE, arrString,ArrImg), new ChoiceGroup("Pop-Up", ChoiceGroup.POPUP, 77 mailxmail - Cursos para compartir lo que sabes arrString,ArrImg) }; for (int iter = 0; iter < grupos.length; iter++) { Forma.append(grupos[iter]); } Forma.addCommand(CMD_Salir); Forma.setCommandListener(this); primera = false; } display.setCurrent(Forma); } startApp crea tres grupos de selección, exclusivo, múltiple y pup-up. Todos estos grupos contienen cuatro opciones (A, B, C y D) y cuatro iconos, correspondientes, cada uno, a una opción, contenidos en arrString y arrImg, respectivamente. El grupo exclusivo es aquel que permite que uno y sólo uno de sus elementos esté seleccionado. El grupo múltiple permite la selección de uno o varios elementos. El modo pop-up permite escoger una sola opción, pero, a diferencia del exclusivo, las muestra en un menú desplegable. public void commandAction(Command c, Displayable d) { if (c == CMD_Salir) { destroyApp(false); notifyDestroyed(); } } 78 mailxmail - Cursos para compartir lo que sabes } protected void destroyApp(boolean unconditional) { } protected void pauseApp() { } } 5.5.7.List Una lista de elementos, a diferencia de lenguajes como HTML WML, en J2ME es una lista de la que se puede seleccionar un elemento, es decir, es como un grupo de selección (ChoiceGroup). A continuación se muestra un código de ejemplo para la utilización de listas: public class Lista extends MIDlet implements CommandListener { private final static Command SALIR = new Command("Salir", Command.EXIT, 1); private final static Command ATRAS = new Command("Atrás", Command.BACK, 1); private Display display; private List Lista1; private List ListaEx; private List ListaIm; private List ListaMul; private boolean primera; 79 mailxmail - Cursos para compartir lo que sabes En esta primera parte del código únicamente se han declarado las variables y los objetos que se utilizarán, notese que entre esos objetos se encuentran cuatro listas u objetos List. public Lista() { display = Display.getDisplay(this); String[] Opciones = {"Opción A", "Opción B", "Opción C", "Opción D" }; Image[] imagenes = null; ListaEx = new List("Exclusiva", Choice.EXCLUSIVE, Opciones, imagenes); ListaEx.addCommand(ATRAS); ListaEx.addCommand(SALIR); ListaEx.setCommandListener(this); ListaIm = new List("Implícita", Choice.IMPLICIT, Opciones, imagenes); ListaIm.addCommand(ATRAS); ListaIm.addCommand(SALIR); ListaIm.setCommandListener(this); ListaMul = new List("Múltiple", Choice.MULTIPLE, Opciones, imagenes); ListaMul.addCommand(ATRAS); ListaMul.addCommand(SALIR); ListaMul.setCommandListener(this); 80 mailxmail - Cursos para compartir lo que sabes primera = true; } El constructor de la clase principal crea un arreglo Opciones que contiene los elementos de la lista, otro arreglo imagenes de valor nulo que definirá las imágenes a utilizar como iconos a mostrar al lado de los elementos; finalmente se instancian las listas según el tipo al que pertenecerán y se les agrega un comando ATRAS y otro SALIR. protected void startApp() { if(primera) { Image[] imagenes = null; try { Image icono = Image.createImage("/GNG/reporte/Icono.png"); imagenes = new Image[] {icono, icono, icono }; } catch (java.io.IOException err) { } String[] Opciones = {"Exclusiva", "Implícita", "Múltiple" }; Lista1 = new List("Seleccione tipo", Choice.IMPLICIT, Opciones, imagenes); Lista1.addCommand(SALIR); Lista1.setCommandListener(this); display.setCurrent(Lista1); primera = false; } 81 mailxmail - Cursos para compartir lo que sabes } Al iniciar la aplicación, se llena el arreglo de imágenes con un archivo (icono.png) que se encuentra en el disco duro de la computadora (esa imagen se incluirá en el paquete que se ha de instalar en el dispositivo final). Se hace esto dentro de un bloque try-catch para evitar que la MIDlet se colapse en caso de no encontrar las imágenes. Después se instancia la lista Lista1, se le agrega un comando SALIR y se designa como actualmente activa en el display, esto es, en la pantalla del dispositivo. protected void destroyApp(boolean unconditional) { } protected void pauseApp() { } public void commandAction(Command c, Displayable d) { if (d.equals(Lista1)) { if (c == List.SELECT_COMMAND) { if (d.equals(Lista1)) { switch (((List)d).getSelectedIndex()) { case 0: display.setCurrent(ListaEx); break; case 1: display.setCurrent(ListaIm); break; case 2: display.setCurrent(ListaMul); break; } } 82 mailxmail - Cursos para compartir lo que sabes } } else { // in one of the sub-lists if (c == ATRAS) { display.setCurrent(Lista1); } } if (c == SALIR) { destroyApp(false); notifyDestroyed(); } } } Al presionarse cualquier botón de comando en el dispositivo, la MIDlet entra en un proceso de selección donde, dependiendo de la opción activa de Lista1 se mostrará en pantalla una u otra de las listas restantes. 5.5.8.Alert En ocasiones puede ser necesario mostrar en el dispositivo, algún mensaje de error, advertencia o simple información. Hacer esto con un objeto StringItem resultaría mucho más difícil que productivo. Para esta tarea, MIDP incluye una clase llamada Alert, la cual se explica enseguida: Alert alerta = new Alert("Alerta"); alert.setType(TipodeAlerta); 83 mailxmail - Cursos para compartir lo que sabes alert.setTimeout(Tiempo); alert.setString("Texto de la alerta"); display.setCurrent(alert); A diferencia de las clases anteriores, a partir de ésta, sólo se mostrará la parte fundamental del funcionamiento de la clase, esto debido a que el lector ya se habrá familiarizado con el resto del método de programación J2ME (declaración de variables, funciones, etc.). En las primeras líneas de código se muestra, primero, la declaración en instanciación de un objeto Alert, segundo, la definición de sus propiedades tipo, tiempo y texto; y, tercero, la forma de activar la alerta (ponerla en pantalla). Los tipos que una alerta permite son los siguientes: -AlertType.ALARM. -AlertType.CONFIRMATION. -AlertType.ERROR. -AlertType.INFO. -AlertType.WARNING. Todos los tipos de alerta agregan al mensaje escrito un timbre o vibración que depende de las capacidades y configuración del dispositivo en que se ejecute la MIDlet, por ejemplo, AlertType.ALARM hará que un teléfono vibre o timbre con el mismo tono configurado en su despertador. En cuanto a los tiempos, estos se dan en segundos: 84 mailxmail - Cursos para compartir lo que sabes int Tiempo = 2 * SECOND; int Tiempo=4 * SECOND; int Tiempo=7 * SECOND; O bien, puede no estar definido y esperar respuesta del usuario: int Tiempo=Alert.FOREVER; 5.5.9.Gauge Gauge es, como la traducción al español lo dirá, un medidor de avance, similar a las barras de progreso (progress bar) de sistemas como Windows. Aunque se podría pensar que su objetivo es meramente estético, es decir, fuera de la función de contador (que puede serlo cualquier variable entera) una barra de avance solamente sirve para informar gráficamente al usuario del progreso de determinada operación. En J2ME, hay un tipo de barra de avance llamado interactivo interactivo, este tipo interactivo es similar al control de volumen de los teléfonos celulares, en los cuales el usuario puede seleccionar el nivel de volumen que desea. Ahora ya no es meramente estético. El funcionamiento de Gauge se describe a continuación: Gauge G1=new Gauge("Interactiva", true, 20,0); mainForm.append(G1); Gauge G2=new Gauge("No interactiva", false, 20,0); mainForm.append(G2); 85 mailxmail - Cursos para compartir lo que sabes Donde el primer parámetro representa el título o etiqueta que el contador llevará, el segundo, un valor lógico, indica si se permitirá al usuario alterar el valor de avance (interactivo o no interactivo), el tercero define el número total de unidades que habrá de contener la barra y el último el valor inicial, en las mismas unidades, con que se mostrará. 5.6. Mejorando las aplicaciones Se puede añadir calidad de presentación y funcionalidad a las MIDlets dándoles la capacidad de enviar mensajes, agregándoles iconos, textos móviles o contenido multimedia 5.6.1. Teletipos Los teletipos son textos que se desplazan en la parte superior de la pantalla del dispositivo. Para crear un teletipo se utiliza la clase ticker y el método setticker para mostrarlo. private Ticker hi = new Ticker("Teletipo ¡HOLA!"); TextBox t = new TextBox("MID1", 256, 0); t.setTicker(hi); display.setCurrent(t); 5.6.2. Iconos Agregar iconos a las MIDlets es sumamente sencillo, sólo hay que agregar los nombres, incluyendo rutas de acceso a partir del archivo jad, del archivo de ícono que queramos agregar a cada MIDlet de nuestro paquete, en el archivo descriptor. 86 mailxmail - Cursos para compartir lo que sabes MIDlet-1: MID,icono.png,MIDlet1 MIDlet-2: MID1,ícono2.png,MIDlet2 Los iconos a utilizar deberán estar, generalmente, en formato png, por lo que si se tiene el ícono que se quiere utilizar en otro formato, es recomendable convertirlo, aunque los equipos modernos ya soportan imágenes en jpg. 5.7.Mensajería Según JCP (Java Community Procces), en su solicitud de especificación (JSR) 120, WMA (Wireless Messaging API, api de mensajería inalámbrica) es un paquete opcional de J2ME. Esta api está basada en la Estructura de Conexión Genérica (Generic Conection Framework, GCF) y orientada a CLDC, aunque también puede ser implementada en perfiles CDC y algunas aplicaciones de J2SE, esto último, una vez que la JSR 197 (el paquete opcional GCF para J2SE) esté completa. En la figura 5.4 se pueden ver los componentes de la api de mensajería inalámbrica. Figura 5.4. Componentes de WMA. Todo lo necesario para utilizar la api de mensajería está contenido en el paquete javax.wireless.messaging, el cual contiene las interfaces requeridas para 87 mailxmail - Cursos para compartir lo que sabes paquete javax.wireless.messaging, el cual contiene las interfaces requeridas para mandar y recibir mensajes inalámbricos, tanto binarios como de texto. La tabla 5.2 describe el contenido de este paquete. Tabla 5.2. Contenido de la api WMA (Wireless Messaging API) Interfaz Descripción Métodos Message Interfaz base, de la cual se derivan subinterfaces como BinaryMessage y TextMessage. getAdress(), getTimestamp(), setAdress(). BinaryMessage Contiene métodos para definir y ver el contenido getPayloadData(), setPayloadData(). binario de los mensajes. TextMessage Para obtener y establecer contenidos de texto en los mensajes, SMS (Short getPayloadText(), setPayloadText(). Message System) por ejemplo. Subinterfaz de la GCF con la cual se pueden MessageConnection crear, enviar y recibir los mensajes. MessageListener newMessage(), send(), receive(),setMessageListener(), numberOfSegments(). Define un escuchante para implementar la notifyIncomingMessage(). notificación asíncrona de mensajes entrantes. 5.7.1.Excepciones Utilizando WMA, el programador se enfrenta a algunas excepciones que deben ser controladas para asegurar la estabilidad de la aplicación. En la tabla 5.3 se muestran y describen las excepciones que pueden arrojar los métodos de la api de mensajería. Tabla 5.3. Excepciones en WMA. Excepción Lanzada por 88 Descripción mailxmail - Cursos para compartir lo que sabes ConnectionNotFoundException Connector.open(). La conexión no puede ser realizada o no existe ese tipo de conexión. InterruptedIOException receive(), send(). La conexión se cerró mientras se recibía un mensaje o se ha excedido el tiempo de espera al enviar. IllegalArgumentException Connector.open(), métodos open*Stream(), newMessage(), send(). Algún argumento de open() es inválido, no se soporta conexiones de tipo Stream, el tipo de mensaje no se ha definido o el mensaje contiene información no válida. IOException Connector.open(), receive(), send(), setMessageListener(). Ha ocurrido un error de entrada/salida o se ha cerrado la conexión inesperadamente. NullPointerException send(). El parámetro Message es nulo. SecurityException() Connector.open(), receive(), send(), setMessageListener(). El protocolo solicitado no esta disponible, no hay permiso para recibir o mandar mensajes por el puerto especificado, 5.7.2.La interfaz Message Esta interfaz es la base de todos los tipos de mensajes que se crean envían y reciben utilizando WMA. En algunos aspectos, un mensaje es similar a un datagrama: ambos tienen direcciones origen y destino, contenido y maneras de enviar y bloquear un mensaje. WMA agrega soporte para mensajes binarios y de texto y una interfaz escuchante para recibir mensajes de manera asincrona[1]. 89 mailxmail - Cursos para compartir lo que sabes Según la especificación de la api de mensajería, se definen dos subinterfaces para la interfaz Message,TextMessage y BinaryMessage. 5.7.3.La interfaz BinaryMessage Esta subinterfaz representa un mensaje con contenido binario (EMS o Enhanced Message System, mensajes multimedia) y define métodos para introducir y recibir tal contenido. 5.7.4.La interfaz TextMessage Se utiliza para obtener e introducir contenido de texto simple (SMS, Short Message System) de y en los mensajes. 5.7.5.La interfaz MessageConnection Es una subinterfaz del paquete javax.microedition.connection de la GCF, contiene los métodos newMessage(), send() y receive(); para crear, enviar y recibir mensajes, respectivamente. En la figura 5.5 se muestra la interfaz MessageConnection MessageConnection y su relación con la GCF. 90 mailxmail - Cursos para compartir lo que sabes Figura 5.5. MessageConnection y su relación con GCF. Con MessageConnection, como con cualquier otra conexión GCF, se utiliza Connector.ope Connector.open() para crear (abrir) un conexión y Connector.close() para cerrarla[2]. Una conexión se puede crear de las siguientes dos maneras: 1.Enviar. Una conexión en la que sólo se pueden enviar mensajes se crea de la siguiente manera: (MessageConnection)Connector.open("sms://+6251001287:5000"); 91 mailxmail - Cursos para compartir lo que sabes Donde sms será el protocolo por medio del cual se enviará el mensaje, 6251001287 6251001287 el número telefónico que lo recibirá y 5 0 0 0 el puerto por el cual se habrá de realizar la operación. 2.Recibir. Este tipo de conexión se crea indicando sólo el protocolo y el puerto que se habrán de utilizar para la recepción de los mensajes. (MessageConnection)Connector.open("sms:// :5000"); Si se intenta utilizar una dirección local (número de puerto) antes reservada se provoca una excepción de entrada/salida (IOException). Se ha mencionado que para crear un mensaje se debe definir el protocolo, la especificación de WMA contempla los siguientes protocolos: - SMS: Sistema de mensajes cortos(Short Messaging System), mensajes de texto plano. Estos mensajes son bidireccionales, es decir, se pueden enviar y recibir desde cualquier dispositivo. - EMS:Sistema de mensajes mejorados (Enhanced Messaging System), los recientemente presentados mensajes multimedia utilizan este protocolo bidireccional. - CBS:Mensaje corto de difusión en celda (Cell Broadcast Short message), estos mensajes son unidireccionales, es decir, son enviados por una estación base y los equipos portátiles únicamente pueden recibirlos. Si se intenta enviar un mensaje utilizando "cbs" se obtendrá una IOException. Algunos proveedores pueden implementar protocolos adicionales y nuevas subinterfaces de Message de ser necesario. 92 mailxmail - Cursos para compartir lo que sabes 5.7.6.La interfaz MessageListener Con esta interfaz se puede implementar un escuchante para recibir mensajes de manera asíncrona, es decir, sin bloquear el aparato mientras se espera un mensaje. MessageListener contiene únicamente un método: notifyIncommingMessage(), este es invocado por la plataforma cuando se recibe un mensaje. Para iniciar un escuchante se debe usar el método MessageConnection.setListener() MessageConnection.setListener(). Debido a que algunos equipos son monohilo (monothreaded), se debe ser muy cuidadoso de mantener el uso de MessageListener al mínimo. Se puede lanzar un hilo separado para capturar y procesar el mensaje, así la plataforma gasta el menor tiempo posible en notificar el nuevo mensaje. 5.7.7.Enviando y recibiendo mensajes 5.7.7.1.Crear una conexión Este es el primer paso a realizar cuando se desean enviar o recibir mensajes. A continuación se muestra como instanciar un objeto de la clase MessageConnection MessageConnection. MessageConnection mc=(MessageConnection)Connector.open(Dir); Donde m c es la conexión a crear y Dir es la variable String que contiene la cadena de dirección a la que se enviará o de la que se espera el mensaje ("sms://+1234567:5000", por ejemplo). Es recomendable utilizar un método que tome como parámetro la dirección e instancie el objeto basándose en ella, esto se puede hacer de la siguiente forma: 93 mailxmail - Cursos para compartir lo que sabes public MessageConnection NuevoMensaje(String Dir) throws Exception { return((MessageConnection)Connector.open(Dir)); } 5.7.7.2.Crear y enviar mensajes de texto Para enviar un mensaje de texto, se muestra a continuación un ejemplo en el que se emplea un método que toma como parámetros un objeto conexión y dos cadenas de caracteres: mensaje y dirección. El primer paso es crear un mensaje de texto. public void EnviarTexto(MessageConnection mc, String msg, String Dir) { try { TextMessage tmsg = (TextMessage)mc.newMessage (MessageConnection.TEXT_MESSAGE); Una vez creado el mensaje, se le introducen valores de dirección (Dir) y contenido (msg). if (Dir!= null) tmsg.setAddress(Dir); tmsg.setPayloadText(msg); Ahora está listo para ser enviado. 94 mailxmail - Cursos para compartir lo que sabes mc.send(tmsg); } También se implementa el control de excepciones. catch(Exception e) { System.out.println(Error al enviar mensaje " + e); } } 5.7.7.3.Crear y enviar mensajes binarios El procedimiento para la creación y envío de mensajes binarios es muy similar al de los mensajes de texto, únicamente hay que sustituir algunos valores y métodos. public void EnviarTexto(MessageConnection mc, byte[] msg, String Dir) { try { BinaryMessage bmsg = (BinaryMessage)mc.newMessage (MessageConnection.BINARY_MESSAGE); if (Dir!= null) bmsg.setAddress(Dir); bmsg.setPayloadData(msg); mc.send(bmsg); } catch(Exception e) { 95 mailxmail - Cursos para compartir lo que sabes System.out.println(Error al enviar mensaje " + e); } } Notese que en los mensajes binarios no se utilizan contenidos de tipo cadena de texto, sino que se utilizan arreglos de bytes, esto debido a que su tratamiento será como términos binarios, es decir bytes. 5.7.7.4.Esperar mensajes entrantes Para esperar la entrada de un mensaje, normalmente se lanza un hilo (thread) para invocar al método Message.receive(), el cual capturará el mensaje. Una vez que se ha recibido el mensaje, es necesario determinar su tipo (texto, binario u otro) para así llamar al método apropiado para ver su contenido. El siguiente código muestra un hilo de ejecución responsable de recibir y procesar mensajes. class Mensajero implements Runnable { Thread th = new Thread(this); MessageConnection mc; // MessageConnection a manejar. boolean hecho; // si es verdadero, el hilo termina. boolean uno; // si es verdadero se procesa sólo un mensaje y sale El siguiente constructor se utilizará para lanzar un hilo que procese múltiples mensajes. public Mensajero(MessageConnection mc) { 96 mailxmail - Cursos para compartir lo que sabes public Mensajero(MessageConnection mc) { this.mc = mc; th.start(); } Debido a que resultaría un gasto innecesario de procesamiento si sólo se quiere un mensaje, se puede utilizar el constructor descrito a continuación. public Mensajero(MessageConnection mc, boolean uno) { this.mc = mc; this.uno = uno; th.start(); } Se notifica cuando el hilo haya terminado su trabajo: public void notifyDone() { done = true; } El método r u n del hilo para esperar y procesar mensajes recibidos. Si se ha indicado en el constructor que se desea sólo un mensaje se llama el procedimiento procesar() procesar(), de otra manera se revisan todos los mensajes. public void run() { if (singlepass == true) procesar(); 97 mailxmail - Cursos para compartir lo que sabes procesar(); else RevisarTodo(); } El procedimiento RevisarTodo(), el cual procesa todos los mensajes recibidos. public void RevisarTodo() { while (!hecho) procesar(); } Para procesar un solo mensaje: public void procesar() { Message msg = null; // Se intenta recibir un mensaje try { msg = mc.receive(); } catch (Exception e) { System.out.println("Error al recibir mensaje " + e); } // Se procesa el mensaje recibido //Se compara si msg es una instancia de TextMessage if (msg instanceof TextMessage) { 98 mailxmail - Cursos para compartir lo que sabes if (msg instanceof TextMessage) { TextMessage tmsg = (TextMessage)msg; // Se maneja como mensaje de texto ticker.setString(tmsg.getPayloadText()); } else { // Se compara si es mensaje binario if (msg instanceof BinaryMessage) { BinaryMessage bmsg = (BinaryMessage)msg; byte[] data = bmsg.getPayloadData(); // se maneja como tal, mostrándolo en un visor multimedia, tal //vez } else { // Se ignora cualquier otro tipo } } } Hay que recordar que el código anterior pertenece a un hilo de ejecución, por lo que debe ir dentro de una clase principal y debe llamarse de la siguiente manera: Mensajero Msj = new Mensajero (mc); // o Mensajero Msj = new Mensajero (mc,true); 99 mailxmail - Cursos para compartir lo que sabes Teniendo, obviamente, un objeto de la clase MessageConnection llamado m c. Con lo expuesto en este apartado se puede realizar una utilización modesta del servicio de mensajería de J2ME. Se recomienda a los interesados en la utilización de este servicio en sus aplicaciones la revisión de la literatura indicada en la bibliografía del presente documento, esto para la profundización en temas de seguridad, segmentación y otros que pueden colaborar a la creación de aplicaciones más robustas y potentes. 5.8.Almacenamiento persistente MIDP proporciona un mecanismo para las MIDlets, el cual les permite el almacenamiento de datos de manera que éstos permanezcan en el equipo aunque la MIDlet se cierre y puedan ser recuperados cuando se solicite. Este mecanismo es una base de datos muy simple llamada Sistema de Administración de Registros (RMS, por sus siglas en inglés). Los registros de RMS son archivos binarios cuya codificación es dependiente del hardware, ya que son creadas en localidades dependientes de la plataforma. Las apis para manejo de registros y conjuntos de registros (archivos) proveen las siguientes funcionalidades: -Eliminación y agregado de registros. -Se permite a MIDlets de la misma aplicación compartir los registros. -No existe un mecanismo para compartir registros o archivos entre diferentes aplicaciones. -Una aplicación puede, además de crear, borrar y modificar sus archivos. Como casi todo en Java y en especial en J2ME, los nombres de archivos son 100 mailxmail - Cursos para compartir lo que sabes Como casi todo en Java y en especial en J2ME, los nombres de archivos son sensibles a mayúsculas y minúsculas, además no deben tener un nombre con longitud mayor a 32 (treinta y dos) caracteres. No se puede crear archivos con nombres duplicados. En el caso de los simuladores, al crear un archivo en una aplicación MIDP, este se ubicará en un directorio llamadoNOJAM, por ejemplo, si se tiene el WTK en el directorio "/usr/local/JWTK2.0/" los archivos se crearán en el directorio "/usr/local/JWTK2.0/NOJAM". Se puede acceder a un archivo desde varios hilos (threads) al mismo tiempo, sólo hay que mantener la atención en la sincronización de tales hilos, un error podría causar la pérdida de los datos. 5.8.1.El paquete RMS El paquete RMS consta de cuatro interfaces, una clase y cinco clases de excepción, a continuación se explica cada una de ellas. 1.Interfaces: -RecordComparator: Define una herramienta para comparar dos registros. -RecordEnumeration: Representa un enumerador bidireccional de registros. -RecordFilter: Define un filtro para examinar un registro y comprobar que coincida con un criterio establecido. -RecordListener: Escuchante de adhesiones, modificaciones y eliminaciones de registros. Captura el registro procesado. 2.Clase: -RecordStore: Representa un conjunto de registros, es decir, un archivo. 3.Excepciones: 101 mailxmail - Cursos para compartir lo que sabes -InvalidRecordIDException: Es lanzada (throw) para indicar que el ID de un registro no es válido. -RecordStoreException: Cuando una excepción general es lanzada. -RecordStoreFullException: Indica que el sistema de archivos para esa aplicación está lleno (actualmente se permiten 30KB por aplicación). -RecordStoreNotFoundException: Se lanza cuando no se ha podido encontrar el archivo. -RecordStoreNotOpenException: Ocurre cuando se trata de acceder a un archivo que no ha sido abierto. 5.8.2.Programando con RMS A continuación se muestra cómo realizar algunas de las operaciones básicas en el manejo de archivos utilizando el paquete RMS. 5.8.2.1.Abrir o crear un archivo Para hacer esto, se utiliza el método openRecordStore() de la siguiente forma: RecordStore db=new RecordStore.openRecordStore("miArchivo",true); Donde "miArchivo" es el nombre del archivo que se desea abrir y true indica si se creará el archivo en caso de no existir. Si se llama el método openRecordStore() sobre un archivo abierto, se generará una referencia al objeto RecordStore() abierto. 5.8.2.2.Agregar un registro 102 mailxmail - Cursos para compartir lo que sabes Un registro se almacena como un arreglo de bytes. Para agregarlos al archivo y empaquetar los tipos de datos en el arreglo se emplean las clases DataOutputStream y ByteAr DataOutputStream y ByteArrayOutputStream. El primer registro que se crea tiene una ID de 1, esa es su llave primaria. El valor de ID irá aumentando en 1 según se agreguen registros. El siguiente código muestra, sin manejo de excepciones (en una aplicación real se deben manejar), como agregar un registro a la base de datos. ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeUTF(record); byte[] b = baos.toByteArray(); db.addRecord(b, 0, b.length); Se comienza por instanciar los objetos DataOutputStream y ByteArrayOutputStream, acto seguido se agrega el registro como una cadena de caracteres (se puede utilizar el formato "campo1,campo2,campo3", siendo ',' un carácter separador), después se agrega un nuevo registro al archivo, dejando así el espacio preparado para agregar un nuevo registro. 5.8.2.3.Leer datos de un archivo Al leer datos de un archivo, se deben utilizar los objetos DataInputStream y ByteArrayInputStream, esto se hace como sigue: ByteArrayInputStream bais = new ByteArrayInputStream(record1); DataInputStream dis = new DataInputStream(bais); 103 mailxmail - Cursos para compartir lo que sabes DataInputStream(bais); String in = dis.readUTF(); El objeto record1 es un arreglo de bytes que puede ser generado como resultado de ejecutar el método miRS.enumerateRecords(null,null,false). 5.8.2.4.Comparar registros Cuando se necesita buscar un registro en la base de datos, ya sea para mostrarlo al usuario o para eliminarlo, esto hace necesaria la comparación entre registros o de registros con cadenas de caracteres. En J2ME no se puede hacer una comparación directa de tiposString, así que se debe usar el método compareTo(String) de la clase String y quizá sea necesario, como en el ejemplo siguiente, implementar la interfaz Comparator. public Clase implements Comparator { public int comparar(byte[] reg1,byte[] reg2) { ByteArrayInputStream bais1 = new ByteArrayInputStream(reg1); DataInputStream dis1 = new DataInputStream(bais1); ByteArrayInputStream bais2 = new ByteArrayInputStream(reg2); DataInputStream dis2 = new DataInputStream(bais2); String nom1 = dis1.readUTF(); String nom2 = dis.readUTF(); int num = nom1.compareTo(nom2); if (num > 0) { return RecordComparator.FOLLOWS; } else if (num < 0) { 104 mailxmail - Cursos para compartir lo que sabes } else if (num < 0) { return recordcomparator.PRECEDES; } else { return recordcomparator.EQUIVALENT; } } } Después de abrir dos registros del mismo archivo se realiza la comparación entre ellos con el método compareTo, el cual regresa 0 si ambos elementos son iguales, un número mayor a 0 si el primero ( nom 1) es mayor que el segundo (nom 2) y menor a 0 en el caso inverso. La función arriba descrita devuelve uno de los siguientes valores constantes: -FOLLOWS: 1, cuando el primer parámetro es mayor que el segundo. -PRECEDES: -1,cuando el segundo es mayor que el primero. -EQUIVALENT: 0, cuando son equivalentes. 5.8.2.5.Borrar registros Para borrar un registro se debe conocer su ID y J2ME no proporciona ningún método para obtenerlo directamente, una técnica recomendada es agregar un contador a la función de comparación, abrir únicamente un registro y compararlo con la cadena de caracteres que buscamos en el registro y devolver el valor del contador en lugar del resultado de la comparación. Una vez que se ha localizado el registro, se utiliza Recordstore.deleteRecord 105 mailxmail - Cursos para compartir lo que sabes (ID), por ejemplo, si el RecordStore utilizado se llama RS1 y la cadena de caracteres a buscar en el registro es buscado, la eliminación será así: re=RS1.enumerateRecords(null,null,false); while(re.hasNextElement){ Supongamos que se han hecho algunas modificaciones a comparar como la de sustituir un parámetro byte[] por un String. id=comparar(re.NextRecord(),buscado); if(id!=-1) RS1.deleteRecord(id); } 5.8.2.6.Cerrar el archivo Para cerrar un archivo solo basta con llamar el método closeRecordStore(), por ejemplo: try{ datos.closeRecorStore(); }catch(Exception e){ } 5.9.Multimedia Los más modernos equipos celulares y otra clase de dispositivos pequeños de 106 mailxmail - Cursos para compartir lo que sabes Los más modernos equipos celulares y otra clase de dispositivos pequeños de alta tecnología tienen gran potencia para la reproducción de contenidos multimedia, principalmente a través de Internet, puesto que, debido a sus escasas capacidades de almacenamiento no podrían contener, un clip de video, por ejemplo, en el espacio reservado para tal fin. Música en formatos WAV, MDI y MP3, animaciones y videos en formatos MPG, MOV o AVI; son hasta ahora los contenidos multimedia que la api opcional de multimedia de J2ME puede reproducir o hasta grabar, dependiendo, obviamente de las capacidades del aparato al que las MIDlets con tal contenido estarán dedicadas. La MMAPI (MultiMedia API) ha sido diseñada para correr sobre cualquier máquina virtual basada en J2ME, esto incluye las máquinas virtuales de CLDC (KVM) y CDC. Esta api es un paquete opcional de J2ME e incluye las siguientes características: -Soporte para generación de tonos, reproducción y grabado de contenidos de audio y video basados en tiempo (música y video tradicional). -Bajo consumo de recursos, MMAPI se ajusta a las especificaciones de CLDC. -La api nunca conoce el contenido o protocolo utilizados. -El programador puede limitar el soporte a ciertos tipos de contenido, audio básico, por ejemplo. -Se ha dejado margen para agregar nuevas características a esta api sin que haya necesidad de romper con la funcionalidad de las antiguas. 5.9.1.Procesamiento multimedia El procesamiento de contenido multimedia se divide en dos partes: 1.Manejo de protocolos. Consiste en leer los datos de la fuente, esta puede 107 mailxmail - Cursos para compartir lo que sabes ser un archivo o un servidor de la red u otro medio de procesamiento. 2.Manejo de contenidos. Descodificar el contenido y presentarlo en un dispositivo de salida como pueden ser una bocina, una pantalla o ambas. Para facilitar esta tarea, MMAPI proporciona dos tipos de objetos de alto nivel: Datasource Datasource y Player. Datasource encapsula el manejo del protocolo escondiendo los detalles de lectura de los datos. Los métodos de este objeto habilitan al objeto Player para interpretar el contenido. Player, lee los datos arrojados por Datasource, los procesa y los muestra en el dispositivo de salida. Este objeto provee de métodos para controlar la reproducción del contenido, esto incluye aquellos métodos para acceder a ciertas características de tipos medios. MMAPI especifica un tercer objeto, un controlador conocido como Manager, para que la aplicación pueda crear reproductores (Players) desde un Datasource y también de un InputStream. En la figura 5.6 se observa la arquitectura completa de MMAPI. 108 mailxmail - Cursos para compartir lo que sabes Figura 5.6. Arquitectura MMAPI. El objeto Manager provee de un método llamado createPlayer(), utilizado para crear los objetos Player que habrán de servir de base para la reproducción de contenidos multimedia. 5.9.1.1.Los paquetes de MMAPI La api de multimedia de J2ME consta de tres paquetes: 1 .javax.microedition.media: Provee algunas interfaces, una excepción y la clase Manager, la cual es el intermediario para obtener recursos del sistema (como Player Player) para el procesamiento multimedia. 2 .javax.microedition.media.control: Define los tipos específicos de control que se pueden utilizar e un reproductor: VolumeControl, VideoControl y otros. 3 .javax.microedition.media.protocol: Define los protocolos para el manejo de controles personalizados, incluye la clase Datasource, una abstracción para manejadores de controles multimedia. 5.9.1.2.Generación de tonos La generación de tonos, como en algunos lenguajes de programación para equipos de escritorio, se define por su frecuencia y duración, este tipo de medio es particularmente importante para los juegos y en equipos pequeños (entre los pequeños) para los que es su única forma de multimedia. El método Manager.playTone() es el encargado de generar los tonos, esto se hace de la siguiente manera: 109 mailxmail - Cursos para compartir lo que sabes try{ Manager.playTone(ToneControl.C4,4000,100); }catch (MediaException me){ } Este fragmento de código reproduce un tono (ToneControl.C4) durante 4 segundos a un volumen de 100 dB. En los dispositivos más poderosos, se puede utilizar un reproductor para sintetizar secuencias de tonos. Player Rep1; Manager.createPlayer (Manager.TONE_DEVICE_LOCATOR); Este tipo de reproductor provee de un objeto ToneControl, el cual puede ser usado para programar una secuencia de tonos. 5.9.1.3.Reproducción de MP3 El siguiente segmento de código muestra como reproducir audio simple sin detallar totalmente un control de reproducción. try { Player p = Manager.createPlayer("http://servidor/musica.mp3"); p.setLoopCount(5); p.start(); } 110 mailxmail - Cursos para compartir lo que sabes } catch(IOException ioe) { } catch(MediaException e) { } El segmento siguiente incorpora un pequeño control. Player p; VolumeControl vc; try { p = Manager.createPlayer("http://servidor/musica.mp3"); p.realize(); // obtener control de volumen y establecer volumen al máximo vc = (VolumeControl) p.getControl("VolumeControl"); if(vc != null) { vc.setVolume(100); } //El reproductor puede iniciar con la menor latencia. p.prefetch(); p.start(); } catch(IOException ioe) { } catch(MediaException e) { } 111 mailxmail - Cursos para compartir lo que sabes } Debido a que cualquier (InputStream) puede ser utilizado como parámetro en el método Manager.createPlayer(), una aplicación puede reproducir contenido de un RMS o de un archivo JAR, A continuación se muestra cómo tomar un InputStream de un RMS y reproducir lo contenido en él. RecordStore store; int id; // Reproducción desde un registro try { InputStream is = new ByteArrayInputStream (store.getRecord(id)); Player player = Manager.createPlayer(is, "audio/X-wav"); p.start(); } catch (IOException ioe) { } catch (MediaException me) { } Como ya se mencionó, también se puede reproducir contenido multimedia almacenado en un archivo JAR, enseguida se muestra cómo: try { InputStream is = getClass().getResourceAsStream("audio.wav"); Player player = Manager.createPlayer(is, "audio/X-wav"); 112 mailxmail - Cursos para compartir lo que sabes p.start(); } catch(IOException ioe) { } catch(MediaException me) { } 5.9.1.4.Reproducción de video En el código siguiente se intenta mostrar un video contenido en el archivo pelicula.mpg. Player p; VideoControl vc; try { p = Manager.createPlayer("http://servidor/pelicula.mpg"); p.realize(); // Control de video vc = (VideoControl) p.getControl("VideoControl"); p.start(); } catch(IOException ioe) { } catch(MediaException me) { } 113 mailxmail - Cursos para compartir lo que sabes 5.9.1.5.Uso de la cámara MMAPI incluye soporte para la cámara que algunos dispositivos incorporan, la api tiene un localizador (URL) especial, capture://video, el cual se usa para crear su reproductor. Se puede utilizar el control VideoControl para mostrar los que la cámara va captando y VideoControl.getSnapshot(String ImageType), para capturar una fotografía. El tipo predeterminado (si no se incluye el parámetro) de imagen es PNG, aunque se puede utilizar cualquiera de los formatos soportados por el equipo, para saber que formatos son soportados se puede utilizar la propiedad del sistemavideo.snapsh video.snapshot.encodings . 5.10. Servlets Las Java Server Pages (JSP), tambien llamadas servlets (palabra servlet se deriva de la unión de los términos server y applet) son páginas que, al igual que ASP o PHP, se ejecutan en un servidor y dan respuesta al cliente en formato de página web (HTML. Tomando en cuenta el hecho de que la inmensa mayoría de los dispositivos compatibles con J2ME lo son también con WML y WAP, resulta obvio considerar que en estos dispositivos se puedan observar páginas WML. Y si estas páginas pueden ser generadas de la misma manera como los lenguajes de servidor (ASP, PHP, JSP) lo hacen con el código HTML, es decir, si al introducir el código que una página en servidor habrá de generar, se introduce, en lugar de código HTML, código WML. Se tiene entonces que los dispositivos J2ME también son compatibles con ASP, PHP y JSP. Debido a que el tema central de este documento es la programación que se realiza orientada a los dispositivos inalámbricos, el tema de los servlets no se tocará a fondo, ya que excede los alcances de la investigación . 5.11. ¿Cómo instalar la aplicación en el dispositivo? 114 mailxmail - Cursos para compartir lo que sabes 5.11. ¿Cómo instalar la aplicación en el dispositivo? Si la MIDlet está terminada, ahora puede publicarse en un servidor Web, esto se logra copiando al servidor los archivos .jar y .jad. Una vez en el servidor, la MIDlet es descargable. Sin embargo se deben agregar algunos tipos MIME (MIMEtypes) a la configuración del servidor: text/vnd.sun.j2me.app_descriptor. Para descargar y ejecutar una MIDlet desde un emulador se puede usar la siguiente instrucción: midp -transient http://servidor/ruta/archivo.jad. O bien, desde el JWT, escribir el URL completo de la MIDlet en la caja de texto del nombre del proyecto del cuadro de diálogo Abrir proyecto. Es muy importante asegurarse que el servidor esté correctamente configurado para manejar, tanto los documentos WML, con todos los formatos que estos puedan incluir, como los archivos .jad y .jar. [1]La codificación e información de control de los mensajes son específicas del protocolo y transparentes a WMA. [2]La clase Connector está contenida en el paquete javax.microedition.io, por lo que hay que importarlo si se desea emplear la mensajería en la aplicación. 115 mailxmail - Cursos para compartir lo que sabes 6. Desarrollo de una aplicación 6. DESARROLLO DE UNA APLICACIÓN 6.1.Descripción del proyecto El proyecto en el que se ha pretendido aplicar los conocimientos adquiridos mediante esta investigación consiste en una pequeña hoja de cálculo con la que se puedan realizar las operaciones aritméticas básicas (igualación, suma, resta, multiplicación y división) y combinaciones sencillas de estas (potencia, promedio, por ejemplo), además de comparaciones entre varios elementos (determinar el valor mayor o menor de una lista). También se busca que la aplicación tenga la capacidad de almacenar los datos de la hoja de cálculo de manera persistente en el equipo, así como de abrir documentos (hojas) guardados con anterioridad y borrar las hojas, que ya han sido guardadas previamente, para liberar el limitado espacio de almacenamiento del dispositivo. 6.2. Herramientas a utilizar Para la realización de la hoja de cálculo (llamada hojaC, nombre perteneciente a la clase principal), se ha utilizado una computadora de las siguientes características: -Procesador AMD Duron a 1.3 Ghz. -160 MB de memoria RAM. -Disco duro de 8.4 Gb. -Sistema operativo: Mandrake Linux 8.2. Se inició la escritura del código de hojaC, como se comentó en el capítulo 4, utilizando un editor de texto llamado Gedit y Java Wireless Toolkit para la compilación. Aunque esto resultaba sumamente tediosos, por lo que se optó por utilizar el Entorno Integrado de Desarrollo (IDE) Sun ONE Studio, en una versión de evaluación especial para la plataforma micro de Java. 6.3. Desarrollo La aplicación está compuesta por cinco clases principales, de las cuales la 116 mailxmail - Cursos para compartir lo que sabes llamada hojaC es la que se ejecuta al iniciar y la que inicia el resto de las clases. A continuación se describen las funciones con que cada clase debe cumplir. - hojaC: Es la clase MIDlet, es decir, la que se ejecuta. esta clase sólo utiliza una forma (Form) sobre la que se instancian una clase Tabla y una clase TextField, de estas, TextField se encarga de mostrar en la parte inferior de la pantalla el valor de la celda actual, Tabla se explica enseguida. - Tabla [1]: Esta es la clase que realiza la mayor parte del trabajo, ya que es la encargada de dibujar la hoja y sus valores, almacenar dichos valores temporalmente, realizar las llamadas a otras clases y la actualización del campo de texto (TextField). Esta clase implementa la interfaz CustomItem, la que permite la utilización de elementos propios de la clase Canvas (eventos de teclas, dibujo de líneas, rellenado de rectángulos, cambio de colores, etc.) y de los formularios o formas (TextField, por ejemplo). - Texto: Hereda o extiende a la clase TextBox, su única función consiste en capturar los valores de las celdas y enviarlos al arreglo donde los guarda Tabla. - funcion: Esta clase contiene los métodos necesarios para la resolución de las fórmulas que se introducen a la hoja de cálculo. Un objeto instanciado en esta clase se construye cada vez que la hoja se actualiza, esto es, cada que se presiona una tecla - Archivo: La clase Archivo es la encargada de todas las operaciones que se realizan con archivos. En ella se encuentran los métodos destinados a pedir el nombre o mostrar la lista de los archivos, así como guardarlos, borrarlos o abrirlos, según lo solicite el usuario. Ahora se explicará cada clase mostrando aquellos fragmentos de código que se consideren importantes para tal explicación. 6.3.1.Clase hojaC La clase hojaC, como el resto de las clases de esta aplicación, forma parte del paquete hoja. Debido a que será la clase que habrá de albergar a las demás, debe heredar el paquete midlet, lcdui será para poder utilizar la clase Form. Además la clase, para poder se ejecutada, necesita extender la clase MIDlet. package hoja; 117 mailxmail - Cursos para compartir lo que sabes import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class hojaC extends MIDlet implements CommandListener { Esta clase incluirá los comandos Salir, Guardar, Abrir, Borrar y Nuevo. También se declaran las clases disp, forma1, tabla, t x t 1 y una variable lógica p r i m que servirá para identificar cuando sea la primer vez que se ejecuta la clase, esto sirve para no redefinir los comandos y los valores de algunas variables. private final static Command Salir = new Command("Salir", Command.EXIT, 1); private final static Command Guardar = new Command("Guardar", Command.SCREEN, 3); private final static Command Abrir = new Command("Abrir", Command.SCREEN, 3); private final static Command Borrar = new Command("Borrar", Command.SCREEN, 3); private final static Command Nuevo = new Command("Nuevo", Command.SCREEN, 3); private Display disp; private Form forma1; private Tabla tabla; public TextField txt1; private boolean prim; En el constructor de la clase sólo se instancian las clases forma1, tabla, t x t 1 y se le asigna a p r i m el valor inicial de verdadero. public hojaC(){ 118 mailxmail - Cursos para compartir lo que sabes forma1=new Form("Mini-hoja de cálculo"); tabla=new Tabla(null, Display.getDisplay(this),this); txt1=new TextField("=", null, 64, 0); prim=true; } En el método startApp() se agregan los comandos, la tabla y el campo de texto a forma1 y se define esta como objeto a mostrar. public void startApp() { if (prim){ disp=Display.getDisplay(this); forma1.append(tabla); forma1.append(txt1); forma1.addCommand(Salir); forma1.addCommand(Guardar); forma1.addCommand(Abrir); forma1.addCommand(Borrar); forma1.addCommand(Nuevo); forma1.setCommandListener(this); prim=false; disp.setCurrent(forma1); }else{ disp.setCurrent(forma1); } } public void pauseApp() { } 119 mailxmail - Cursos para compartir lo que sabes public void destroyApp(boolean unconditional) { } La función settexto es utilizada por la clase Tabla para actualizar el contenido de t x t 1 cada vez que en la tabla se cambia de celda. protected void settexto(String texto){ txt1.setString(texto); } Finalmente, en el método commandAction se define que se hará en caso de cada opción del menú. public void commandAction(Command c, Displayable d){ if (c==Salir){ destroyApp(false); notifyDestroyed(); } if(c==Abrir){ Archivo ar=new Archivo('A',tabla.getDatos(),disp,forma1); forma1.setCommandListener(this); } if(c==Guardar){ Archivo ar=new Archivo('G',tabla.getDatos(),disp,forma1); forma1.setCommandListener(this); } if(c==Borrar){ Archivo ar=new Archivo('B',tabla.getDatos(),disp,forma1); forma1.setCommandListener(this); } 120 mailxmail - Cursos para compartir lo que sabes if(c==Nuevo){ tabla.limpiar(); } } } 6.3.2.Clase Tabla Esta clase importará el paquete javax.microedition.lcdui y la clase funcion de el paquete hoja. Además agrega un comando al menú que había creado hojaC, el comando cmdAny (que mostrará el texto "Valor"). Se hereda la capacidad de manejo de herramientas de Canvas en la misma pantalla que elementos de Form. package hoja; import javax.microedition.lcdui.*; import hoja.funcion; public class Tabla extends CustomItem implements ItemCommandListener { private final static Command cmdAny = new Command("Valor", Command.SCREEN,1); private Display display; //Ancho de la columna que muestra el número de fila o renglón private static int num=18; //Filas y columnas en pantalla private int rows = 6; private int cols = 3; //Filas y columnas reales de la tabla (filas y columnas del arreglo) private int rrows = 5; private int rcols = 3; //Tamaño de una celda private int dx = 51; 121 mailxmail - Cursos para compartir lo que sabes private int dy = 19; //Celda seleccionada private int currentX = 0; private int currentY = -1; //Arreglo donde se almacenarán los datos private String[][] data = new String[rrows][rcols]; //Indicador de la pantalla vertical y horizontal donde se encuentra[2]. private int pantallaV=1; private int pantallaH=1; //Se necesita utilizar una clase hojaC para la actualización de txt1. private hojaC hoja1; En el constructor de Tabla se crea una clase CustomItem (super) con title como parámetro, se asigna valor a d y se definen el comando. public Tabla(String title, Display d,hojaC hoja) { super(title); display = d; addCommand(cmdAny); setDefaultCommand(cmdAny); setItemCommandListener(this); hoja1=hoja; } El método paint es el encargado de dibujar el estado actual de la tabla, originalmente integra a la clase Canvas, y CustomItem lo hereda de esta. Lo primero que se hace es dibujar las líneas y los títulos de columna y renglón. protected void paint(Graphics g, int w, int h) { for (int i = 0; i <= rows; i++) { 122 mailxmail - Cursos para compartir lo que sabes for (int i = 0; i <= rows; i++) { if (i>0&&i<rows){ int ClipX = g.getClipX(); int ClipY = g.getClipY(); int ClipWidth = g.getClipWidth(); int ClipHeight = g.getClipHeight(); g.setClip(1, (i*dy), num - 1, dy - 1); g.drawString(String.valueOf(i), num-2, ((i + 1) * dy)+1, Graphics.BOTTOM | Graphics.RIGHT); g.setClip(ClipX, ClipY, ClipWidth, ClipHeight); } if (i==0) g.drawLine(0,0, cols * dx+num, i * dy); g.drawLine(0, i * dy, cols * dx+num, i * dy); } for (int i = 0; i <= cols; i++) { if (i>0){ int x; x=((i * dx)+((i-1)*dx))/2; int ClipX = g.getClipX(); int ClipY = g.getClipY(); int ClipWidth = g.getClipWidth(); int ClipHeight = g.getClipHeight(); g.setClip(((i-1)*dx)+num, 1, dx - 1, dy - 1); g.drawString(letrade(i), x+num,dy, 123 mailxmail - Cursos para compartir lo que sabes Graphics.BOTTOM | Graphics.HCENTER); g.setClip(ClipX, ClipY, ClipWidth, ClipHeight); }else g.drawLine(0,0, 0, rows * dy); g.drawLine((i * dx)+num, 0, (i * dx)+num, rows * dy); } Después se dibuja un rectángulo de color diferente al resto para indicar la celda seleccionada. int oldColor = g.getColor(); g.setColor(0x00D0D0D0); g.fillRect((currentX * dx) + 1+num, ((currentY+1) * dy)+1, dx - 1, dy 1); g.setColor(oldColor); Finalmente se busca en el arreglo data todas aquellas celdas que contengan algún valor y se dibuja su contenido en la posición adecuada. for (int i = 1; i < rows; i++) { for (int j = 0; j < cols; j++) { if (data[i-1][j] != null) { int oldClipX = g.getClipX(); int oldClipY = g.getClipY(); int oldClipWidth = g.getClipWidth(); int oldClipHeight = g.getClipHeight(); funcion func=new funcion(data); g.setClip((j * dx) + num, i * dy+1, dx - 1, dy - 1); g.drawString(func.Valor(data[i-1][j]), (j * dx) + num+1, 124 mailxmail - Cursos para compartir lo que sabes ((i + 1) * dy) - 2, Graphics.BOTTOM | Graphics.LEFT); g.setClip(oldClipX, oldClipY, oldClipWidth, oldClipHeight); } } } } El método traverse está definido como integrante de CustomItem, esta clase utiliza los eventos de teclas de la clase Canvas para lograr el desplazamiento por las celdas de la tabla. protected boolean traverse(int dir, int viewportWidth, int viewportHeight, int[] visRect_inout) { switch (dir) { case Canvas.DOWN: if (currentY < (rrows - 1)) { currentY++; repaint(currentX * dx, currentY * dy, dx, dy); repaint(currentX * dx, currentY+1 * dy, dx, dy); } else { pantallaV++; } break; case Canvas.UP: if (currentY > 0) { currentY--; repaint(currentX * dx, (currentY + 2) * dy, dx, dy); 125 mailxmail - Cursos para compartir lo que sabes repaint(currentX * dx, (currentY + 2) * dy, dx, dy); repaint(currentX * dx, (currentY+1) * dy, dx, dy); } else { pantallaV--; return false; } break; case Canvas.LEFT: if (currentX > 0) { currentX--; repaint((currentX + 1) * dx, currentY * dy, dx, dy); repaint(currentX * dx, currentY * dy, dx, dy); }else{ pantallaH--; } break; case Canvas.RIGHT: if (currentX < (rcols - 1)) { currentX++; repaint((currentX - 1) * dx, currentY * dy, dx, dy); repaint(currentX * dx, currentY * dy, dx, dy); }else{ pantallaH++; } } 126 mailxmail - Cursos para compartir lo que sabes visRect_inout[0] = currentX; visRect_inout[1] = currentY; visRect_inout[2] = dx; visRect_inout[3] = dy; //Se actualiza el contenido de txt1, de la clase hoja. hoja1.settexto(data[currentY][currentX]); return true; } Se utiliza setText para almacenar en el arreglo data una cadena de caracteres que corresponde al valor de la celda actual. public void setText(String text) { if (text.compareTo("")==0){ text=null; } data[currentY][currentX] = text; currentY--; repaint(currentY * dx, currentX * dy, dx, dy); } Cuando en el menú se selecciona la opción "Nuevo", la clase hojaC llama al método limpiar, el cual recorre toda la tabla eliminando cualquier valor que ésta tenga y actualizando la celda. public void limpiar() { for(int y=0;y<rrows;y++) for(int x=0;x<rcols;x++){ data[y][x] = null; 127 mailxmail - Cursos para compartir lo que sabes repaint(y * dx, x * dy, dx, dy); } } Las funciones getdato, getText y getDatos se emplean para obtener el valor de la celda actual, de una celda determinada y de toda la tabla, respectivamente. protected String getdato(){ return data[currentY][currentX]; } protected String getText(int x, int y){ return data[y][x]; } protected String[][] getDatos(){ return data; } Al seleccionar la opción "Valor" del menú se asigna a modo el valor para admitir cualquier carácter y se crea una instancia de Texto, mostrándola en pantalla. public void commandAction(Command c, Item i) { if (c==cmdAny){ modo=TextField.ANY; Texto txt1 = new Texto(data[currentY][currentX], this, display, modo); display.setCurrent(txt1); } } Por efecto de claridad y espacio, se han omitido algunos fragmentos del código de la clase Tabla que se han considerado innecesarios en la explicación del funcionamiento de la clase, aunque eso no significa que no sean vitales para la 128 mailxmail - Cursos para compartir lo que sabes operación real de la misma. 6.3.3.Clase Texto La clase Texto extiende a la clase TextBox, motivo por el cual debe importar javax.microedition.midlet.MIDlet, aunque no sea una clase ejecutable por sí sola. Se utiliza una clase Tabla, una clase Display para mostrar el TextBox, los comandos CMD_OK CMD_OK y CMD_CANCEL para insertar el texto en la tabla o cancelar la modificación y una variable String que habrá de almacenar el texto ingresado. package hoja; import javax.microedition.lcdui.*; import javax.microedition.midlet.MIDlet; public class Texto extends TextBox implements CommandListener { private final static Command CMD_OK = new Command("OK", Command.OK,1); private final static Command CMD_CANCEL = new Command("Cancelar", Command.CANCEL,1); private Tabla parent; private Display display; private String text; El constructor toma como parámetros una cadena de texto, una clase Tabla que es la que lo llama, una clase Display donde se ha de mostrar ca caja de texto y una variable entera modo, esta variable entera está pensada para la posible utilización de varios formatos de cadenas de texto (numérico, carácter, correo electrónico, etc.). Los valores de los parámetros tomados se asignan a los objetos con el mismo nombre declarados en esta clase. public Texto(String text, Tabla parent, Display display, int modo) { super("Ingrese valor", text, 64, modo); this.parent = parent; 129 mailxmail - Cursos para compartir lo que sabes this.display = display; this.text=text; addCommand(CMD_OK); addCommand(CMD_CANCEL); setCommandListener(this); } El método commandAction utiliza el método setText de la clase Tabla (parent en este caso) y regresa display a dicha clase si se selecciona el comando CMD_OK, de los contrario no utiliza setText. public void commandAction(Command c, Displayable d) { if (c == CMD_OK) { parent.setText(getString()); display.setCurrentItem(parent); } else if (c == CMD_CANCEL) { display.setCurrentItem(parent); } } } 6.3.4.Clase funcion Al igual que las hojas de cálculo para computadoras de escritorio, ésta permite la utilización de fórmulas para realizar operaciones, ya sea con valores constantes o con valores introducidos dentro de las celadas de la hoja. El formato para la utilización de fórmulas en hojaC es el siguiente: operación(operando1, operando2, ... , operando n) Donde los operandos pueden ser números enteros, celdas de la hoja (A1, B3, C4, por ejemplo) u otras funciones y operación se sustituye con el símbolo 130 mailxmail - Cursos para compartir lo que sabes C4, por ejemplo) u otras funciones y operación se sustituye con el símbolo correspondiente a la operación que se desee realizar, tales símbolos se muestran en la tabla 6.1. Tabla 6.1. Símbolos utilizados en las funciones de la hoja de cálculo. Símbolo Función Sintaxis + Suma. +(op1, op2, ... , opn) +(A1,2,8,B4) - Resta (A-B). -(op1, op2) * Multiplicación. *(op1, op2, ... , opn) *(4,5,2,1,3) / División (A/B). /(op1, op2) < Mínimo. <op1, op2, ... , opn) <(32,1,0,A2) > Máximo. >op1, op2, ... , opn) >(C3,A2,B1,5) % Tanto por ciento (A * B%). %(op1, op2) : Promedio. :(op1, op2, ... , opn) :(10,9,8,7,6) @ Potencia. @(base, potencia) @(9,2) Igualación. =(Valor) =(B2) = Ejemplo -(356,*(A1,3)) /(25,5) %(100,25) A continuación se explica el código fuente de esta clase: Se utilizará una variable data, la cual es una arreglo de String (igual al data de la clase Tabla), para tener acceso a todos los valores de la hoja, debido a que una función puede hacer referencia al valor de cualquier celda. private static String data[][]; int lev=0; public funcion(String[][] data1){ data=data1; } La función getDato toma como parámetro el nombre de una celda (A1, B2, etc.), lo transforma a coordenadas en la tabla ([1][2], por ejemplo) para después obtener el valor contenido en esa localidad, ese valor es el que esta función devuelve como cadena de caracteres. private String getDato(String celda){ int x,y; 131 mailxmail - Cursos para compartir lo que sabes int x,y; String a; char b; try{ a=celda.substring(1); y=Integer.parseInt(a); b=celda.charAt(0); x=Character.digit(b,Character.MAX_RADIX)-9; }catch (Exception e){ return "Error"; } if ((x<1)||(y<1)||(x>24)||(y>65)) return "Error"; a=data[y-1][x-1]; if (a==null||a.compareTo("")==0) return "0"; return data[y-1][x-1]; } Los valores de las celdas que empleará una operación (suma, resta. división etc.) se almacenan en un objeto de la clase Vector, lo más similar en Java a los arreglos dinámicos de C++, de manera que primero se deben introducir todos los datos al vector y después se procesan según lo solicite la fórmula. En la suma se convierten a enteros todos los términos del vector y se suman los resultados de tal conversión. Si alguno de los términos no se puede convertir (no es la representación de un entero) la función devuelve la constante -32767 que se ha designado como error. private int suma(Vector datos){ 132 mailxmail - Cursos para compartir lo que sabes private int suma(Vector datos){ int result=0,R=0; for(int x=0;x<datos.size();x++){ try{ R=Integer.parseInt(datos.elementAt(x).toString()); }catch (Exception e){ return -32767; } result+=R; } return result; } La resta se puede realizar únicamente entre dos elementos enteros, si el vector tiene más elementos o si alguno de ellos no se ha podido convertir a entero, se devuelve la constante de error (-32767), de lo contrario la resta se realiza y se devolverá el resultado. private int resta(Vector datos){ int result,r=0,R=0; int s=datos.size(); if(s==2){ try{ R=Integer.parseInt(datos.elementAt(0).toString()); r=Integer.parseInt(datos.elementAt(1).toString()); }catch (Exception e){ return -32767; } 133 mailxmail - Cursos para compartir lo que sabes } result=R-r; }else{ result=-32767; } return result; } El proceso de la igualación es muy sencillo, sólo se verifica que en el vector exista un único elemento y se regresa ese valor, o -32767 de no ser así. private String igual(Vector datos){ if(datos.size()==1){ return datos.elementAt(0).toString(); }else{ return "Error"; } } El proceso de multiplicación es igual al de la suma, se convierten los elementos a entero y se procesan (multiplican, en este caso) acumulativamente. private int multi(Vector datos){ int result=1,R=0; for(int x=0;x<datos.size();x++){ try{ R=Integer.parseInt(datos.elementAt(x).toString()); }catch (Exception e){ return -32767; 134 mailxmail - Cursos para compartir lo que sabes } result*=R; } return result; } Así como la suma y la multiplicación siguen el mismo procedimiento, la resta y la división también lo hacen, con las obvias diferencias en la operación medular. private int divi(Vector datos){ int result,r=0,R=0; if(datos.size()==2){ try{ R=Integer.parseInt(datos.elementAt(0).toString()); r=Integer.parseInt(datos.elementAt(1).toString()); }catch (Exception e){ return -32767; } result=R/r; }else{ result=-32767; } return result; } Para calcular máximos y mínimos se utiliza la misma función, incluyendo el parámetro de entrada m m, el cual habrá de tomar un valor de 'm' si se trata de un mínimo o 'M' si lo que hay que calcular es el máximo. Al igual que el resto de las operaciones, el máximo y el mínimo también devuelven la constante de error si los 135 mailxmail - Cursos para compartir lo que sabes datos del vector no se pueden convertir a enteros. private int maxmin(Vector datos, char mm){ int M=-32767,x,B; try{ M=Integer.parseInt(datos.elementAt(0).toString()); }catch(Exception e){ return -32767; } for (x=1;x<datos.size();x++){ try{ B=Integer.parseInt(datos.elementAt(x).toString()); }catch(Exception e){ return -32767; } if(mm=='M') if(M<B){ M=B; } if(mm=='m') if(M>B){ M=B; } } return M; } 136 mailxmail - Cursos para compartir lo que sabes El cálculo del tanto por ciento también utiliza sólo dos valores, el primero es el valor base y el segundo el tanto por ciento a calcular. private int pciento(Vector datos){ int result,r=0,R=0; if(datos.size()==2){ try{ R=Integer.parseInt(datos.elementAt(0).toString()); r=Integer.parseInt(datos.elementAt(1).toString()); }catch (Exception e){ return -32767; } result=R/100 * r; }else{ result=-32767; } return result; } Debido a lo similares que son las funciones en su procedimiento, las siguientes sólo se especificarán en nombre, ya que sería una redundancia innecesaria el explicar los procesos ya explicados. Promedio. private int prom(Vector datos){ int result=0,R=0; for(int x=0;x<datos.size();x++){ try{ 137 mailxmail - Cursos para compartir lo que sabes R=Integer.parseInt(datos.elementAt(x).toString()); }catch (Exception e){ return -32767; } result+=R; } result=result/datos.size(); return result; } Potencia. private int pot(Vector datos){ int result,r=0,R=0; if(datos.size()==2){ try{ R=Integer.parseInt(datos.elementAt(0).toString()); r=Integer.parseInt(datos.elementAt(1).toString()); }catch (Exception e){ return -32767; } result=1; for (int x=0;x<r;x++){ result*=R; } }else{ result=-32767; 138 mailxmail - Cursos para compartir lo que sabes } return result; } La función recurrente Valor es la que realiza la mayor parte del trabajo, toma el valor de la ceda tal y como se ha introducido, lo analiza para verificar si se trata de una constante o de una fórmula. En este último caso, separa sus términos, ordena la obtención del valor final (constante) de los datos y finalmente selecciona la operación a realizar. public String Valor(String dato){ char c; Vector A=new Vector(); String cad; lev++; Para evitar referencias circulares, no puede haber más de nueve niveles de anidación en las fórmulas. if (lev>9){ dato=null; return "Error"; } cad=dato; //separar términos c=cad.charAt(0);//se toma el primer carácter (donde está el símbolo) i f(c=='+'||c=='-'||c==' *' ||c==' /' ||c==' < ' ||c==' > ' ||c==' %' ||c==' :' || c=='='||c=='@'||c=='&'){ int pa=0,pc=0,x=0,pp=0,up=0,y=0; //Verificar paréntesis 139 mailxmail - Cursos para compartir lo que sabes while (x<cad.length()){ if (cad.charAt(x)=='(') pa++; if ((pa==1)&&(x==1)) pp=x; if (cad.charAt(x)==')') pc++; if (pc==pa) up=x; x++; }; //Si no coinciden paréntesis de apertura con de cierre. if ((pa!=pc)||(pp!=1)) return "Error"; for (x=pp+1; x<up;x++){ if (x<cad.length()){ //si se hace referencia a una celda String celda=null; if (((cad.charAt(x)<='Z')&&(cad.charAt(x)>='A'))&& (cad.charAt(x+1)>='1')&&(cad.charAt(x+1)<='6')){ celda=cad.substring(x,x+2); //se utiliza la recurrencia para las funciones anidadas. A.addElement(Valor(getDato(celda))); lev--; 140 mailxmail - Cursos para compartir lo que sabes x++; }else{ y=x; int par=0; //Se busca ',' para separar los términos while ((x<up)&&((cad.charAt(x)!=',')||(par!=0))){ celda=cad.substring(y,x+1); if (cad.charAt(x)=='(') par++; if (cad.charAt(x)==')') par--; x++; }; //se agrega el término al vector if (celda!=null){ A.addElement(Valor(celda)); //Se disminuye el nivel en una cuando se agrega una //constante lev--; } } } } } Una vez habiendo llenado el vector con todos los datos, se selecciona la 141 mailxmail - Cursos para compartir lo que sabes operación a realizar. switch (c){ //suma (s1,s2,s3...,sn) case '+':cad=cad.valueOf(suma(A));break; //resta(r1,r2) case '-':cad=cad.valueOf(resta(A));break; //multiplicación (m1,m2,m3...,mn) case '*':cad=cad.valueOf(multi(A));break; //división (d1,d2) case '/':cad=cad.valueOf(divi(A));break; //Igual (I) case '=':cad=igual(A);break; //porcentaje (P,p) case '%':cad=cad.valueOf(pciento(A));break; //máximo (M1,M1,M3..,Mn) case '<':cad=cad.valueOf(maxmin(A,'m'));break; //mínimo(m1,m2,m3...,mn) case '>':cad=cad.valueOf(maxmin(A,'M'));break; //promedio(p1,p2,p3,...pn) case ':':cad=cad.valueOf(prom(A));break; //potencia case '@':cad=cad.valueOf(pot(A));break; //raiz case '&':break; } 142 mailxmail - Cursos para compartir lo que sabes if (cad.compareTo("-32767")==0){ cad="Error"; } return cad; } } 6.3.5.Clase Archivo Con las clases que se han analizado hasta ahora, se tiene ya una aplicación completamente funcional. Pero falta una parte importante de una hoja de cálculo: el almacenamiento persistente. De poco serviría poder realizar las operaciones más complicadas en un teléfono celular si no se puede tener memoria de ellas una vez que se cierra el programa. La clase Archivo del proyecto es la que se encarga de la administración del almacenamiento persistente. Debido a que esta clase utiliza formularios, comandos y objetos Display, se deben importar los paquetes javax.microedition.midlet y javax.microedition.lcdui. Y como se trata de la clase que manejará los archivos, también se debe importar javax.microedit javax.microedition.rms. Se definen tres comandos, dos para aceptar y una para salir. Los comandos para aceptar, son dos porque la interfaz de usuario para abrir un archivo es la misma que la utilizada para seleccionar el archivo a borrar, está interfaz utiliza uno y guardar, que tiene una interfaz diferente, utiliza el otro. package hoja; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.rms.*; import java.io.*; public class Archivo implements CommandListener { 143 mailxmail - Cursos para compartir lo que sabes private final static Command pres= new Command("Aceptar", Command.OK,1); private final static Command Salir= new Command("Atrás", Command.EXIT,1); private final static Command Aceptar = new Command("Aceptar", Command.OK, 1); Se utilizarán las siguientes variables con ámbito "global" en esta clase: //Arreglo de donde se toman o donde se almacenan los datos. private String [][] tabla; //Nombre del archivo. private String nombre; //Objeto Display donde se muestran los formularios y la lista. private Display disp; //Formulario que se utilizará para guardar un archivo. private Form forma1; //Base de datos o archivo. private RecordStore datos; //Formulario que se mostrará al terminar la operación (el principal, de hojaC) private Form hoja1; //Lista que mostrará los archivos existentes para abrir o borrar. private List Lista; //Variable que indica si se guarda, abre o borra el archivo. char G; 144 mailxmail - Cursos para compartir lo que sabes //Campo de texto para pedir el nombre del archivo al guardar. TextField txtnom; Al construir la clase se igualan las variables locales a los parámetros solicitados y se prepara la forma o la lista según lo indique G. public Archivo(char G, String[][] tabla,Display disp, Form hoja1){ this.tabla=tabla; this.disp=disp; this.hoja1=hoja1; this.G=G; if (G=='G'){ txtnom=new TextField("Nombre del archivo",null, 10,0); forma1=new Form("GUARDAR un archivo"); forma1.append(txtnom); forma1.addCommand(pres); forma1.addCommand(Salir); forma1.setCommandListener(this); disp.setCurrent(forma1); } else{ ListaArch(); } } Si G indica que se ha de abrir o borrar un archivo, se crea la lista de archivos y se muestran en pantalla. private void ListaArch(){ 145 mailxmail - Cursos para compartir lo que sabes if (G=='A') Lista = new List("ABRIR un archivo", Choice.IMPLICIT, RecordStore.listRecordStores(),null); else Lista = new List("BORRAR un archivo", Choice.IMPLICIT, RecordStore.listRecordStores(),null); Lista.addCommand(Salir); Lista.addCommand(Aceptar); Lista.setCommandListener(this); disp.setCurrent(Lista); } El método commandAction decide que hacer, según el comando que se haya elegido y el valor de G. public void commandAction(Command c, Displayable d) { if (c == pres) { nombre=txtnom.getString(); Guardar(); } if (c == Aceptar) { // disp.setCurrent(forma1); nombre=Lista.getString(Lista.getSelectedIndex()); if (G=='A') Abrir(); if (G=='B') Borrar(); 146 mailxmail - Cursos para compartir lo que sabes } if (c == Salir) { disp.setCurrent(hoja1); } } Para abrir un archivo se sigue el proceso explicado en la sección dedicada a ese tema en este documento y se llama al procedimiento llenar, el cual se encargará de mostrar los datos en la pantalla. private void Abrir(){ RecordEnumeration re; try{ datos=RecordStore.openRecordStore(nombre,false); re=datos.enumerateRecords(null,null,false); String reg; while (re.hasNextElement()){ try{ llenar(re.nextRecord()); }catch(Exception e){} } try{ datos.closeRecordStore(); }catch (Exception e){ System.out.println("Error3"); } disp.setCurrent(hoja1); 147 mailxmail - Cursos para compartir lo que sabes }catch (Exception e){ Alert a= new Alert("Error","No se pudo abrir el archivo",null, AlertType.ERROR); a.setTimeout(Alert.FOREVER); disp.setCurrent(a); } } Tomando como parámetro una arreglo de bytes, en este caso un registro de la base de datos, llenar traduce esos datos para calcular la posición en que ese dato debe ir y el contenido con que habían sido almacenados. private void llenar(byte[] b){ int x,y; String dato,reg; ByteArrayInputStream bais= new ByteArrayInputStream(b); DataInputStream dis=new DataInputStream(bais); try{ reg=dis.readUTF(); x=Integer.parseInt(reg.substring(0,reg.indexOf(","))); y=Integer.parseInt(reg.substring(reg.indexOf(",")+1,reg.indexOf("|"))); dato=reg.substring(reg.indexOf("|")+1); tabla[x][y]=dato; }catch(Exception e){} } Para guardar una hoja, primero se busca alguna que tenga el mismo nombre que el que se ha introducido, de existir, el archivo es eliminado y a continuación se guarda el archivo nuevo. 148 mailxmail - Cursos para compartir lo que sabes Debido a que RMS está diseñado para bases de datos, se introducen las celdas ocupadas (aquellas que tengan un valor) con el siguiente formato: "X,Y|Valor" Donde X es el renglón de la tabla donde se encuentra, Y la columna convertida de letra a número (A=0, B=1, C=2) y Valor es la cadena de caracteres que el usuario ha introducido. private void Guardar(){ try{ datos=RecordStore.openRecordStore(nombre,false); datos.closeRecordStore(); RecordStore.deleteRecordStore(nombre); }catch (Exception e){ System.out.println("Error"); } try{ datos=RecordStore.openRecordStore(nombre,true); /**Formato del registro en el archivo="x,y|valor"*/ String reg=""; //Se recorre toda la tabla. for (int x=0;x<5;x++){ for (int y=0;y<3;y++){ reg=tabla[x][y]; //Se agrega el registro si la celda tiene valor if (reg!=null){ reg=String.valueOf(x)+","+String.valueOf(y)+"|"+reg; 149 mailxmail - Cursos para compartir lo que sabes ByteArrayOutputStream baos=new ByteArrayOutputStream(); DataOutputStream dos= new DataOutputStream(baos); try{ dos.writeUTF(reg); byte[] b=baos.toByteArray(); datos.addRecord(b, 0, b.length); }catch (Exception e){ //Si no se puede agregar el registro Alert a= new Alert("Error", "No se pudo abrir el archivo",null,AlertType.ERROR); a.setTimeout(Alert.FOREVER); disp.setCurrent(a); return; } } } } }catch (Exception e){ //Si no se puede crear el archivo Alert a= new Alert("Error","No se pudo abrir el archivo",null, AlertType.ERROR); a.setTimeout(Alert.FOREVER); disp.setCurrent(a); return; } 150 mailxmail - Cursos para compartir lo que sabes try{ //Cerrar el archivo datos.closeRecordStore(); }catch (Exception e){} //Regresa a la vista principal. disp.setCurrent(hoja1); } El procedimiento para borrar un archivo es muy sencillo, sólo se utiliza el método deleteRecordStore y se muestra una mensaje indicando el resultado de la operación. private void Borrar(){ try{ //Borra el archivo RecordStore.deleteRecordStore(nombre); //Crea una alerta para el mensaje Alert a= new Alert("Hecho","Se ha eliminado el archivo",null, AlertType.INFO); a.setTimeout(2000); //Muestra la alerta disp.setCurrent(a); Lista.delete(Lista.getSelectedIndex()); }catch (Exception e){ Alert a= new Alert("Error","No se pudo abrir el archivo",null, AlertType.ERROR); a.setTimeout(Alert.FOREVER); 151 mailxmail - Cursos para compartir lo que sabes disp.setCurrent(a); } } } 6.4. Probar la aplicación Las pruebas de la aplicación se han realizado en el emulador de un teléfono a color incluido en Sun ONE Studio 5, en su edición especial para J2ME. Las figuras 6.1 a 6.8 se muestran las diferentes pantallas (no se incluyen las de error) que, según las muestra el emulador, que se pueden ver en la mini-hoja de cálculo. Al iniciar la aplicación se muestra la hoja de cálculo sin datos (figura 6.1). Figura 6.1. La hoja de cálculo. Cuando se selecciona una celda que contiene algún valor, este se muestra en el campo de texto de la parte inferior (Fig. 6.2). 152 mailxmail - Cursos para compartir lo que sabes Figura 6.2. El campo de texto muestra el valor de la celda. Al seleccionar el comando "Menu", se despliega un menú con las opciones: "Guardar", "Abrir", "Borrar", "Nuevo" y "Valor". (Fig. 6.3) Figura 6.3. El menú de la hoja de cálculo. Al tomar la opción 5 (Valor) del menú, o presionar la tecla "Select" del emulador se ingresa a la caja de texto donde se pide que se introduzca el valor para la celda (Fig. 6.4). 153 mailxmail - Cursos para compartir lo que sabes Figura 6.4. Caja de texto para introducir el valor. Cuando se elige "Guardar" en el menú, se pide el nombre del archivo en un nuevo formulario (Fig. 6.5). Figura 6.5. Formulario para dar nombre a un archivo. Si lo que se ha seleccionado en el menú es la opción "Abrir" o la opción "Borrar", se muestra una lista de archivos de donde se elegirá uno para realizar la operación. (Figs. 6.6 y 6.7) 154 mailxmail - Cursos para compartir lo que sabes Figura 6.6. Abrir un archivo. Así se vería al seleccionar "Borrar"; Figura 6.7. Borrar un archivo. Si se trataba de borrar un archivo y tal operación fue exitosa -No hay razón para que no lo sea, puesto que sólo se muestran los archivos existentes- se muestra un mensaje notificándolo. Este mensaje sólo dura unos segundos (Fig. 6.8). 155 mailxmail - Cursos para compartir lo que sabes Figura 6.8. El archivo se ha borrado con éxito.. 6.5. Instalación de la aplicación en un dispositivo La instalación se ha realizado a través de una servidor web "apache", sobre la plataforma linux, se ha utilizado también, como pasarela (gateway) entre el servidor y el dispositivo Kennel wap gateway. Para descargar la hoja de cálculo se ha creado una página sencilla en la cual se colocó un enlace al archivo hoja.jad, con el teléfono conectado a Internet se introdujo la dirección del servidor (148.246.162.16, en esa ocasión) y una vez que la página principal se hubo abierto, se seleccionó el enlace a la aplicación. Después de unos momentos que tardó en descargarse el archivo, la aplicación se estaba ejecutando satisfactoriamente en el teléfono. El primer paso ha sido configurar el servidor web según como se especifica en el capítulo referente al tema, del libro "Linux, guía de instalación y administración", escrito por Vicente López Camacho, además de otros siete autores, y publicado por la editorial McGraw-Hill, en España, el año 2001. Una vez instalado el servidor, se procedió a configurar el wap gateway, para esto se siguieron las instrucciones detalladas en el manual de usuario del producto. Hecho lo anterior, se copiaron los archivos de la página WML y los archivos jad jad y jar de la aplicación para prepararlos para su descarga. [1]Las clases Tabla y Texto están basadas en una MIDlet de ejemplo incluida en el Java Wireless Toolkit llamada CustomItenDemo. [2]La capacidad de utilizar múltiples pantallas (más de 6 renglones y 3 columnas) no se implementa en esta versión 156 mailxmail - Cursos para compartir lo que sabes 157 mailxmail - Cursos para compartir lo que sabes 7. Conclusión y bibliografía 7. CONCLUSIONES De unos años a la fecha las instituciones de educación tecnológica en México, particularmente las relacionadas con la computación y el desarrollo de software, han centrado sus esfuerzos en un solo fin para la programación: aplicaciones de bases de datos, principalmente para computadoras de escritorio con conexión a una red o, en los mejores casos, para servidores de grandes compañías. Esta situación se debe, principalmente, a lo difícil o casi imposible que se ha vuelto el competir con los gigantes del software (Microsoft, Sun, Borland, etc.) en aplicaciones de interés general, como pueden ser: procesadores de texto, juegos, anti virus, hojas de cálculo, reproductores multimedia, entornos de programación y un largo etcétera; todo el mercado de estos productos de consumo masivo se considera, en la mayoría de las ocasiones, fuera del alcance de un estudiante de nivel licenciatura, algunas veces de posgrado, y que decir de uno de preparatoria, por muy brillantes que estos sean. Si bien es cierto que para un ser humano solitario, y hasta cierto grado principiante, es imposible competir contra un grupo enorme de profesionales que llevan años dedicándose al desarrollo de los mismos módulos para un mismo paquete, también lo es que ese ser humano puede llegar a formar parte de uno de esos mitificados grupos y, por qué no, puede incluso ser el fundador de uno de ellos. A lo largo de este documento se han podido ver las enormes capacidades de desarrollo que los llamados "pequeños dispositivos inalámbricos" presentan, desde la mas sencilla página web, hasta una completa aplicación con fines científicos, de manejo de bases de datos o de acceso a la red empresarial. Lo anterior sugiere que, siendo el campo de la programación de equipos pequeños e inalámbricos, un campo hasta cierto punto virgen en muchas regiones del mundo, se puede orientar a los alumnos a la creación, no ya de sistemas de inventarios para un supermercado[1], sino de aplicaciones que, por ser de uso general -y aunque su precio de venta sea mucho menor- resuelvan más necesidades de más personas alrededor del mundo. Actualmente, en los teléfonos celulares, se pueden utilizar aplicaciones como calculadoras simples, sencillísimos editores de texto para el envío de mensajes, 158 mailxmail - Cursos para compartir lo que sabes juegos que cada vez tienen mayor complejidad, entre otras. No hay todavía una calculadora científica, un editor de texto con corrección de ortografía (aunque sea por medio de la red) o un paquete de oficina compatible con los de las PC. Se ha elegido, para este proyecto de investigación, demostrar los conocimientos adquiridos con una aplicación de hoja de cálculo debido a la representatividad de este tipo de paquetes, es decir, ¿Qué tipo de software puede ser más general?. En una hoja de cálculo se puede ingresar texto, datos numéricos, realizar operaciones, hacer programas sencillos (y en ocasiones no tan sencillos) en las mejores hojas. Con lo anterior se pretende enviar un mensaje a cada lector que, habiendo pasado por las páginas anteriores, haya conservado el interés en este reporte y esté ahora leyendo estas líneas. "Tenemos ante nuestros ojos un campo por labrar, un campo donde quién va pasando por un tramo se apodera de él. Es un campo enorme y, hasta ahora, son pocos los que lo ocupan y relativamente poco el espacio que han abarcado. Podemos dejar que suceda lo que sucedió con las computadoras de escritorio y quedarnos sólo con el espacio donde estamos parados (y probablemente ni eso), o podemos comenzar a ganar terreno. Quizá nunca tengamos más del espacio suficiente para construir nuestra choza, pero es mejor que poder apenas levantar las manos para cubrirnos de la lluvia". 8.BIBLIOGRAFÍA 1.Deitel, H.M. Deitel, P.J. "Cómo programar en Java", Prentice Hall. México. 1998. 2.Froute, Agustín, "Tutorial de Java". froufe@arrakis.es. España. Julio de 1996. 3.Lopez Camacho, Vicente, "Linux, guía de instalación y administración", McGraw-Hill, España, 2001. 4.Java.sun.com 5.www.motocoder.com 6 .www.motorola.com 7 .www.nokia.com 8 .www.sonyericsson.com 159 mailxmail - Cursos para compartir lo que sabes 8 .www.sonyericsson.com 9.www.wapforum.com 10.www.wmlclub.com [1]Con este comentario se pretende sólo ejemplificar, por ningún motivo se desdeña la capacidad de algunos programadores que han decidido dedicarse a las pequeñas bases de datos. 160