Demostración de georreferenciación al estilo web 2.0

Desarrollo de software, Internet, Software geográfico September 9th, 2008

No es que me sienta viejo aún, pero como me hubiera gustado que como ahora, hubieran existido tantas cosas y que hubieran estado al alcance de los desarrolladores cuando yo era estudiante hace un tiempo ya.  Gloriosa época de muchas trasnochadas expermientando cositas, en particular con Java.

Desafortunadamente, en mi época de estudiante me tocó un mundo no tan interesante como el actual, ya que empezamos con C/C++ para MS-DOS y en esa época el reto adicional era mostrar imágenes en los programas y simular el ambiente 3D de ventanas con sólo las líneas y cuadrados del BGI :-P

Java llegó después y me tocó la época en que todos estabamos aprendiendo y las cosas emocionantes empezaron a llegar con el tiempo y con Internet.  Recuerdo que ya terminando mis clases, cuando abrí mis primeras direcciones de correo en USA.net y Hotmail, mis compañeros estuvieron renuentes por un buen tiempo porque no le veían la utilidad al correo electrónico o simplemente porque no tenían quien les escribiera.  Pero por suerte ya todo eso pasó.

Ahora hay muchas cosas, tantas que no alcanza el tiempo para buscarlas, estudiarlas y hacer algo con ellas.  Inclusive hay muchas cosas del mismo estilo.  Todo un mundo de delicias para escoger.  Lo que falta es tiempo.  El trabajo y el cansancio son malos amigos para la investigación.  Ahora, donde uno mire, hay cosas para probar y lo mejor de todo es que la mayoría son abiertas y grátis ... digo libres (free), el límite es el tiempo y la pereza mental de las personas.

Hoy, a pesar de que tengo muchos informes atrasados, me dieron ganas de hacer una aplicación muy sencilla para demostrar un par de estas librerías que están allí, esperando que hagamos algo interesante con ellas.   Este ejemplo muy simple me tomó al rededor de 2 horas implementarlo y eso porque son muy lento para hacer las interfaces de usuario desde código.

Así surgió la demostración de georreferenciación al estilo web 2.0 que permite visualizar el mapa de cualquier ubicación del mundo y ubicar sobre él marcadores georreferenciados.

Para esto utiliza los servicios de Google Maps para la generación de las imágenes y GeoNames para la georreferenciación, es decir, para convertir las ubicaciones (Manizales, Caldas, Colombia) en sus respectivas duplas latitud/longitud para poder ser relacionadas geográficamente.

En la parte izquierda se aprecia la lista con las ubicaciones seleccionadas que corresponden con las marcas rojas en el mapa, en la parte derecha está el mapa junto con tres barras de desplazamiento que regulan su presentación y en la parte inferior se encuentran los botones de opciones.

El demo le permite al usuario Agregar y Remover ubicaciones como puntos seleccionados.  Como se mencionó, estas se ingresan con los nombres de las ubicaciones y el sistema obtiene su ubicación geográfica a través de la consulta de un servicio web.  Es posible en cualquier momento, Centrar el mapa al rededor de cualquiera de los puntos seleccionados almacendos.

La manipulación del mapa se realiza con las barras de desplazamiento.  La naranja (derecha) corresponde con el zoom: hacia abajo aumenta, haciendo mayor el acercamiento del mapa.  La horizontal, azul, corresponde con la longitud y la vertical, verde, corresponde con la latitud.  Cuando se modifica cualquiera de estos valores se deberá solicitar la actualización del mapa presionando el botón Refrescar.

La aplicación ha sido desarrollada en Java por lo que su código es muy claro y modular.  En términos de la implementación, el acceso al webservice de GeoNames utiliza su propio API para el cual se descargaron dos archivos: geonames-1.0-java5.jar y jdom-1.0.jar.

La georrefernciación no podría ser más fácil.

    public static GeoLocation locate(String location) throws Exception
    {
        GeoLocation result = new GeoLocation();
 
        // Creates the toponym searcher
 
        ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();
 
        // Sets the criteria based on the specified location
 
        searchCriteria.setQ(location);
 
        // Request the geolocalization to the webserver
 
        ToponymSearchResult searchResult = WebService.search(searchCriteria);
 
        // Gets the results
 
        List toponyms = searchResult.getToponyms();
 
        // Checks if there were results
 
        if(toponyms == null || toponyms.size() == 0)
            return null;
 
        // Gets the first result of all (could be many)
 
        Toponym first = toponyms.get(0);
 
        // Prepares the result with its information
 
        result.put("geoNameId",   first.getGeoNameId()   + "");
        result.put("name",        first.getName()        + "");
        result.put("latitude",    first.getLatitude()    + "");
        result.put("longitude",   first.getLongitude()   + "");
        result.put("countryCode", first.getCountryCode() + "");
        result.put("countryName", first.getCountryName() + "");
 
        return result;
    }

La clase GeoLocation que utilizo para manejar el resultado no es mas que un Hashtable<String, String> con algunos adendos para facilitar su uso.

Por otro lado, la generación del mapa requiere aún menos ciencia aunque la realizo en dos pasos discretos que en general no incluyen nada extraño.

    public String prepareUrl()
    {
		String markers = "";
 
		// Walks thru all the points to create its markers
 
		for(int i=0; i
<points.size(); i++)
		{
			// Reads the points one by one
 
			GeoLocation point = points.elementAt(i);
 
			// The markers will be green
 
			String color = "green";
 
			// Sets the point location information based on its longitude and latitude
 
			String pointLocation = point.get("latitude") + "," +
			                       point.get("longitude") + "," +
			                       color +
			                       point.get("name").toLowerCase().charAt(0);
 
			// Adds it to the markers accumulator
 
			markers += pointLocation + ((i < points.size() - 1) ? "%7C" : "");
		}
 
		// Prepares the full URL based on markers information, centered location, size, zoom and API key
 
		String url = "http://maps.google.com/staticmap?" +
		             "center=" + centerLatitude + "," + centerLongitude + "&" +
		             "markers=" + markers + "&" +
		             "zoom=" + zoomLevel + "&" +
		             "size=" + sizeWidth + "x" + sizeHeight + "&" +
		             "key=" + apiKey;
 
		// System.out.println(url);
 
		return url;
    }

En el primer paso preparo el URL del consulta al servicio basado en información como la coordenada centro del mapa, el nivel de zoom, el tamaño de la imagen, los puntos seleccionados (marcadores) y la llave del API que debe ser privada y es única para cada sitio web (FQDN), aunque para el caso específico de aplicaciones de escritorio no es muy relevante a pesar de ser obligatoria.  La llave del API puede ser obtenida de manera gratuita por cualquier desarrollador desde esta dirección.

    public Image prepareImage(String url) throws Exception
    {
        Image image = ImageIO.read(new URL(url));
 
        return image;
    }

El segundo paso se relaciona con consultar el servicio de Google utilizando el URL recién generado, leer los bytes que conforman la imagen y crear con ellos un objeto Image para ser mostrado posteriormente en la interfaz de usuario.  Con Java, este procedimiento es extremadamente sencillo: 1 línea de código.

Para su uso se deberá tener cuidado en el manejo de las posibles excepciones que pueda lanzar el requerimiento como por ejemplo, producto de un fallo de red.

        // Gets the map image
 
        Image imageMap = mapService.getMap();
 
        // Checks if was received
 
        if(imageMap == null)
        {
            JOptionPane.showMessageDialog(this, "El mapa no se pudo recuperar.",
                    "Error recuperando mapa", JOptionPane.ERROR_MESSAGE);
            return;
        }
 
        // Puts the map on the gui
 
        map.setIcon(new ImageIcon(imageMap));

Teniendo el objeto Image lo podemos poner en cualquier componente de AWT/Swing.  Por facilidad, yo utilizo un JLabel que incluye el soporte de íconos (ImageIcon) los cuales se basan en objetos de imagen.

Como se puede apreciar, la complejidad de la aplicación es muy baja por lo que reitero: la imaginación es el límite.

Es posible acceder a la aplicación desde web sin instalar ningún archivo local desde el siguiente enlace o ejecutando el siguiente comando en una consola/símbolo del sistema operativo:

   $ javaws http://demo.jorgeivanmeza.com/Java/DemoGeoReferenceMap/0.1/DemoGeoReferenceMap.jnlp

Enlace:

Tags: , , ,

Desarrollo de sistemas multiagentes

Desarrollo de software, Internet September 3rd, 2008

El tema de estudio del mes pasado era la implementación de sistemas multiagentes o SMA bajo la asesoría del profesor Luis Castillo.  Estuvo muy interesante ya que alcanzamos a hacer varias prácticas utilizando la infraestructura de Jade, un framework para Java.

Como práctica propia desarrollé un prototipo llamado Buscador de talento amigo que resultó hasta interesante.  Su objetivo es el de ayudarnos con algo que nos interesa bastante y frecuentemente: encontrar talento técnico para el desarrollo de nuestros proyectos, o visto desde el otro ángulo: buscar empleo.

La idea original era utilizar la base de datos de contactos de una red social real (Facebook o alguna OpenSocial), sin embargo revisando sus APIs y reglamentos es aparentemente inviable, ya que no permiten legalmente que se extraiga información de estas redes.   Por esto tuve que volver aún mas prototipo a mi prototipo y elaborar mi propia versión de una extremadamente simple red social llamada Mandarina Social donde los usuarios pudieran tener un perfil propio (nombres, profesión, rol e información de ubicación) y tener a otros usuarios como amigos.  El objetivo del sistema es aprovechar esta relación de FOAF.

En términos generales, el sistema le pregunta a un usuario de la red social: cómo es el perfil de las personas que necesita, es decir, que requiere que hayan estudiado: "ingeniería de sistemas", "zootecnia", etc; así como alguna información adicional: dónde está ubicada la red social, cuántos perfiles requiere, cuántos niveles de amistad desea verificar y cuánto tiempo desea que el SMA utilice en la búsqueda de talento.

Con esta información el sistema crea a un agente Atendedor quien se encargará de la interacción con el usuario final y que a su vez crea a agentes Buscadores que basados en el perfil del usuario que realiza el requerimiento empiezan a escudriñar a la red social en búsqueda del perfil especificado, saltando entre los contactos amigos del usuario y recursivamente entre los amigos de sus amigos.  Mandarina Social fue desarrollada basada en la especificación hCard de Microformats la cual estandariza ciertas etiquetas (clases CSS en realidad) para demarcar la información del perfil y de las listas de amigos, permitiendo posteriormente su extracción fácil utilizando XPath.  Este enfoque tiene un valor agregado, si se nota, se evita la necesidad de crear una interfaz web para los usuarios humanos y otra, probablemente XML, para el intercambio de datos entre aplicaciones.  Todo se resume en una única versión XHTML/hCard.

Al final de esta etapa de búsqueda se tiene una lista de contactos candidatos que fueron encontrados entre los contactos del usuario.  Los contactos de esta lista incluyen en su información de perfil los datos de su ubicación de la forma: Ciudad, Departamento, País.  Esta información es georreferenciada gracias al agente GeoLocalizador que utiliza al servicio web de Geonames para obtener la ubicación de latitud y longitud de cada uno de los candidatos.

Los candidatos georreferenciados son entregados a un agente Seleccionador quien deberá elegir cuales de los candidatos serán finalmente elegidos.  Esto lo hará basado en la cantidad de perfiles solicitados y un criterio de selección, el cual para este prototipo es la cercanía de los candidatos con respecto al usuario solicitante.  El cálculo de distancias entre candidatos y usuario solicitante es realizado por el agente CalculadorDeDistancias el cual desarrolla su función gracias a la librería JCoord.

Una vez el agente Seleccionador ha decidido cuales de los candidatos son los realmente elegidos, transmite este listado de regreso al agente Atendedor quien gracias al servicio de Google Maps elabora un mapa con las ubicaciones de los contactos con el perfil requerido.  Este mapa junto con el listado de los perfiles es mostrado al usuario como respuesta a su solicitud.

La descripción realizada hasta el momento se complementa con la presentación informal que creé para la muestra del prototipo en sociedad y que incluyo a continuación.

El desarrollo de este prototipo despertó mi curiosidad respecto a los SMA y me permitió darme cuenta que no son abstracciones tan complejas y extrañas como esperaba, mas que un paradigma diferente es una infraestructura que facilita la solución de ciertos tipos de problemas, infraestructura que uno mismo podría terminar desarrollando parcialmente en su propia aplicación pero que no es necesario ya que es provista como una base por la librería, en mi caso de estudio, Jade.  Obviamente existen muchas otras opciones diferentes a Jade, aún mas, dentro de ella hay muchos otros recursos diferentes de los aprovechados con este prototipo, como por ejemplo, la movilidad de agentes entre plataformas físicas gracias a que, según pude apreciar, está desarrollado sobre RMI.  A fin de cuentas, esta es apenas mi primera aproximación al mundo de los agentes.

A pesar de que el prototipo es completamente funcional estas son algunas de las cosas que podría mejorarle en un futuro.

  • Algunas secciones de código son suceptibles de ser optimizadas con refactorización.
  • Sería interesante confirmar la no viabilidad de acceso a las redes sociales reales y en caso se ser posible implementar interfaces para permitir el acceso a su información, en caso contrario, implementar una mejor versión de Mandarina Social.
  • Determinar y solucionar el porqué la manipulación de los datos XHTML de la red social utilizando a XPath genera algunas veces problemas sin motivos aparentes: Connection Reset.
  • Optimizar la distribución de tiempos de espera entre los agentes buscadores.
  • Mejorar la transmisión de mensajes entre agentes, en especial, los que indican condiciones de error.
  • Mejorar el manejo de condiciones de error y su recuperación ante estas o eventos inesperados.
  • Buscar otras formas de interacción con el usuario además de la ya muy conocida ventana de GUI.
  • Mostrar al usuario final través de la GUI los mensajes de estado de los agentes que actualmente se muestran a través de la consola.
  • Solucionar posibles problemas con la muerte (remoción) de los agentes de la plataforma.
  • Verificar el correcto funcionamiento de la concurrencia de solicitudes.

Probablemente mas adelante publique algunos artículos prácticos relacionados con lo aprendido sobre la librería Jade en este blog, alias mi caché mental.

Como para todo lo que aprendo y me emociona, espero por estos temas volver a pasar en un futuro cercano.

Tags: , ,

Serialización con XML

Desarrollo de software August 17th, 2008

Esta semana encontré algo que me pareció interesante.  Es la posibilidad de serializar objetos en Java utilizando un codificador XML.  Lo mas interesante se que para realizar esta codificación básica no es necesario de ningún OXM (Object XML Mapper), por el contrario, todo lo necesario viene incluído ya en el J2SE.

Para el ejemplo he creado una clase Worker (POJO) que será serializada y sólo incluye atributos y sus respectivos métodos set/get.  Por presentación también sobreescribí al método toString para presentar el contenido del objeto.

public class Worker
{
    private String username;
    private String password;
    private String name;
    private int age;
    private Date birthDate;
 
    // ...
}

Creo una instancia de esta clase y asigno valores a sus atributos.

GregorianCalendar birthDate = new GregorianCalendar(2005, 02, 14);   
 
Worker workerman = new Worker();
 
workerman.setAge(31);
workerman.setName("Pepito Pimentón");
workerman.setUsername("pepitouser");
workerman.setPassword("pepitopass");
workerman.setBirthDate(birthDate.getTime());

A continuación se procede a codificar el objeto y a generar el archivo XML con su contenido.

// Creates the stream to the file that will storage the serialized object
FileOutputStream outputFile = new FileOutputStream("workerman.xml");
 
// Relates the XML encoder with the output file stream
XMLEncoder xe = new XMLEncoder(outputFile);
 
// Serializes the selected object using an XML encoding
xe.writeObject(workerman);
 
// Closes the XML encoder
xe.close();

Terminado este proceso, deberá existir el archivo workerman.xml con la representación XML del objeto workerman creado anteriormente.  Esta representación de la información podría ser compartida inclusive con otros sistemas/lenguajes/plataformas diferentes a la actual, cosa que podría tener algunos inconvenientes con la serialización convencional de java.io.  El contenido XML (texto plano) probablemente ocupe mas espacio y esto lo haga menos eficiente que una representación binaria.

Por supuesto también es posible realizar el proceso contrario: basados en el archivo workerman.xml con el contenido codificado, obtener la información y crear nuevamente la representación del objeto Worker.

// Creates the stream from the file that storages the already serialized object
inputFile = new FileInputStream("workerman.xml");
 
// Relates the XML decoder with the input file stream
XMLDecoder xd = new XMLDecoder(inputFile);
 
// Reads the object from the stream and deserializes it using an XML decoding
clone = (Worker)xd.readObject();
 
// Closes the XML decoder
xd.close();

El objeto clone deberá contener la misma información que su versión original: workerman.

El contenido del archivo workerman.xml es bastante explícito para su procesamiento en otras plataformas.

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_10-beta" class="java.beans.XMLDecoder">
 <object class="Worker">
  <void property="age">
   <int>31</int>
  </void>
  <void property="birthDate">
   <object class="java.util.Date">
    <long>1110776400000</long>
   </object>
  </void>
  <void property="name">
   <string>Pepito Pimentón</string>
  </void>
  <void property="password">
   <string>pepitopass</string>
  </void>
  <void property="username">
   <string>pepitouser</string>
  </void>
 </object>
</java>

Enlace:   XML Serialization Demo (fuentes).

Tags: ,

Práctica: distribución de midlets mediante OTA con J2ME - parte I

Desarrollo de software, Educación, Móvil August 2nd, 2008

Durante la sesión Compilación y distribución de midlets se expusieron los métodos de distribución de aplicaciones móviles, en especial los remotos a través de la red (Over-the-air provisioning).

Ahora, contando ya con el primer midlet empaquetado y listo para su distribución (cualquiera de los tres métodos utilizados) nos disponemos durante la presente sesión a implementar de manera práctica este procedimiento.

Para esto se va a utilizar Apache como el servidor HTTP y al emulador de aplicaciones móviles incluído en el JWTK como cliente de las mismas.

A pesar de que el objetivo de la práctica es el de realizar una simulación del proceso de distribución de midlets a nivel local, el procedimiento real en Internet no difiere en mas que en la ubicación en que se localicen los archivos y se especifique a su vez en el atributo MIDlet-Jar-URL del archivo descriptor de la aplicación.

 
  • Introducción.
  • Distribución de midlets.
    • OTA.
    • Flujo de eventos.
  • Herramientas.
  • Servidor de páginas: XAMPP.
    • Instalación.
    • Configuración.
  • Creación del sitio web.
  • Despliegue del midlet.
  • Instalación de la aplicación.
  • Distribución a través de Internet.
  • Enlaces de interés.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,

Generación del midlet HolaMundo utilizando EclipseME

Desarrollo de software, Educación, Móvil August 1st, 2008

La creación de software no es una tarea fácil, por este motivo es necesario utilizar de la manera mas eficiente posible las herramientas que se tengan a la mano y que permitan un desarrollo mas estructurado, confiable y rápido de los productos de software.

Los ambientes integrados de desarrollo (IDE) son un tipo de estas herramientas, facilitan la manipulación de  los proyectos con un nivel de abstracción tal que evitan al desarrollador tener que atender aspectos repetitivos o de bajo nivel.

Uno de los IDE mas conocidos para el desarrollo de aplicaciones Java es Eclipse, el cual es extendible mediante plugins, permitiéndole aumentar su funcionalidad e incluír el soporte a mas lenguajes de programación, incluyendo a J2ME.

En la presente sesión se llevará a cabo la instalación de Eclipse, su plugin EclipseME y con ellos se realizará la generación del midlet HolaMundo de manera análoga a las sesiones anteriores, pero esta vez utilizando esta herramienta de nivel profesional.

 
  • Introducción.
  • Herramientas.
    • J2SDK y JWTK.
    • Eclipse.
    • Plugin EclipseME.
  • Instalación de Eclipse.
  • Instalación del plugin EclipseME.
  • Configuración del plugin EclipseME.
  • Creación de un proyecto J2ME.
  • Ejecución (emulación) del midlet.
  • Generación del archivo JAR.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,

Generación del midlet HolaMundo utilizando las herramientas de línea de comando

Desarrollo de software, Móvil July 25th, 2008

KToolBar es una herramienta muy útil provista por el JWTK ya que permite abstraer al desarrollador de varias de las actividades necesarias para la generación de los midlets.

Como ejercicio académico es interesante poder realizar estas actividades una a una y desde la línea de comandos para poder observar con precisión su papel e importancia dentro del proceso de desarrollo de las aplicaciones móviles.

A continuación se realizarán las actividades necesarias para compilar preverificar, empaquetar y ejecutar al midlet HolaMundo de manera similar a lo realizado durante la sesión anterior, sin embargo ahora utilizando únicamente los comandos básicos provistos por la plataforma Java.

Para la elaboración de este documento se utilizará Windows Vista, motivo por el cual ciertas ubicaciones o comandos relacionados con el shell podrán variar de acuerdo con el sistema operativo utilizado.

 
  • Introducción.
  • Herramientas de software.
  • Estructura de los directorios.
  • Variables de ambiente.
  • Compilación.
  • Preverificación.
  • Empaquetamiento.
    • Creación del archivo de manifiesto.
    • Creación del archivo JAR.
    • Creación del archivo JAD.
  • Ejecución (emulación).
  • Enlaces de interés.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,

Generación del midlet HolaMundo utilizando el JWTK

Desarrollo de software, Educación, Móvil July 21st, 2008

Durante el presente capítulo práctico se realizará la construcción (build), ejecución (emulación) y despliegue del midlet HolaMundo cuyo código fuente fue implementado en capítulos anteriores.

Para esto se utilizarán las herramientas básicas que provee la plataforma Java como lo son el J2SDK y el JWTK.

Esta práctica permitirá también verificar los conceptos teóricos expuestos en los capítulos anteriores.

 
 
  • Introducción.
  • Herramientas necesarias.
    • Editor de texto.
    • Ambiente de desarrollo (J2SDK).
    • Java Wireless Toolkit (JWTK).
  • Instalación de las herramientas.
  • Creación del proyecto.
  • Configuración del proyecto.
  • Estructura de los directorios generados.
  • Edicion del código fuente del midlet.
  • Compilación.
  • Ejecución (emulación).
  • Despliegue del midlet.
  • Archivos generados.
    • Archivo JAD.
    • Archivo JAR.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,

Compilación y distribución de midlets con J2ME

Desarrollo de software, Móvil July 17th, 2008

 
  • Introducción.
  • Descripción del proceso.
    • Entorno de desarrollo.
      • Edición.
      • Compilación.
      • Preverificación (4 fases).
      • Empaquetado.
        • Archivos JAR/JAD.
    • Dispositivo móvil.
      • Verificación.
      • Ejecución.
  • Distribución.
    • Local.
    • Remota.
  • Funciones del AMS.
    • Instalación.
    • Actualización.
    • Ejecución.
    • Eliminación.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,

Disección del midlet HolaMundo en J2ME

Desarrollo de software, Móvil July 10th, 2008

  • Introducción.
  • Importación del API.
  • Un midlet básico.
  • Acceso a la pantalla.
  • Caja de texto.
  • Comando aceptar.
  • Constructor para el midlet.
  • Método startApp().
  • Problema: CommandListener.
  • Manejo de eventos del Command.
  • Método pauseApp().
  • Método destroyApp(boolean).
  • Fin de la implementación.

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: ,

Midlets con J2ME

Desarrollo de software, Móvil July 3rd, 2008

El Objetivo del capítulo es el de identificar los estados entre los cuales puede transitar un midlet durante su ciclo de vida, conocer cual es el significado de cada uno de estos estados y que se espera que el desarrollador implemente en ellos así como el identificar los métodos que deberán ser invocados para solicitar/obligar la transición entre estados.

  • Definición
  • Ciclo de vida de un midlet
    • Estado DETENIDO (paused)
    • Estado ACTIVO (active)
    • Estado DESTRUÍDO (destroyed)
  • Métodos modificadores de estado
    • Método startApp()
    • Método pauseApp()
    • Método destroyApp(boolean)
    • Relación entre destroyApp() y notifyDestroyed()
    • Método notifyPaused()
    • Método resumeRequest()
  • Entorno de ejecución

Para mas información consulte el curso Introducción al desarrollo de aplicaciones móviles con J2ME.

Tags: , ,