martes, 12 de mayo de 2009

Exception : Unsupported major.minor version

Estaba ya un poco harto de la excepción "Unsupported major.minor version". Mis condiciones de trabajo hacen que no "despliege" mis aplicaciones Java siempre en la misma máquina, con lo que me encuentro con diferentes entornos en los que es necesario que estas aplicaciones funcionen correctamente. Estos entornos, muchas veces, no son controlados por mí.

Uno de los elementos, de estos entornos, que más me influyen es: la versión de la máquina virtual Java (JVM) que hay instalada en ellos. Si la máquina virtual Java es una revisión menor que la de mi entorno de desarrollo... tendré problemas. Si la máquina virtual Java es una revisión menor que la utilizada por quien compiló las librerías (en formato JAR) que utilizo en mis aplicaciones... tendré problemas.

Pero, ¿cómo sé con qué versión de máquina virtual han sido compiladas el conjunto de clases de mi aplicación? Sé que cada .class tiene unos primeros bytes que me informan de ello, pero, ¿alguién ha desarrollado ya alguna utilidad que lea estos bytes y me haga un sencillo informe con el resultado de su lectura?

Curiosamente, me costó menos desarrollar esta pequeña utilidad que encontrarla en Internet (no fuí capaz de encontrarla, realmente).

La he llamado "CheckJvmVersion" y la "dono" ;-)
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class CheckJvmVersion {

public static void main(String[] argv) {
JarFile[] jar = null;
String dir = "";
if (argv.length <= 1) {
if (argv.length == 1) {
if (argv[0].endsWith(".jar")) {
jar = new JarFile[1];
try {
jar[0] = new JarFile(argv[0]);
} catch (IOException e) {
System.out.println("No se ha podido leer el fichero jar: " + argv[0]);
System.exit(-1);
}
} else {
dir = argv[0];
int j = 0;
String[] ljar = new File(dir).list();
jar = new JarFile[ljar.length];
for (int i = 0; i < ljar.length; i++) {
if (ljar[i].endsWith(".jar")) {
try {
jar[j++] = new JarFile(argv[0] + System.getProperty("file.separator") + ljar[i]);
} catch (IOException e) {
System.out.println("No se ha podido leer el fichero jar: " + ljar[i]);
System.exit(-1);
}
}
}
}
} else {
dir = ".";
int j = 0;
String[] ljar = new File(dir).list();
jar = new JarFile[ljar.length];
for (int i = 0; i < ljar.length; i++) {
if (ljar[i].endsWith(".jar")) {
try {
jar[j++] = new JarFile(ljar[i]);
} catch (IOException e) {
System.out.println("No se ha podido leer el fichero jar: " + ljar[i]);
System.exit(-1);
}
}
}
}
} else System.out.println("Uso: CheckJvmVersion [<jar-file>]");
if (jar != null && jar.length > 0) {
try {
for (int i = 0; i < jar.length; i++) {
if (jar[i] != null) {
System.out.println("Leyendo el fichero jar [" + jar[i].getName() + "]: ");
List<String> l = new ArrayList<String>();
for (Enumeration e = jar[i].entries(); e.hasMoreElements();) {
ZipEntry ze = (ZipEntry) e.nextElement();
InputStream is = jar[i].getInputStream(ze);
DataInputStream dis = new DataInputStream(is);
try {
int magic = dis.readInt();
if(magic == 0xcafebabe) {
int minor = dis.readUnsignedShort();
int major = dis.readUnsignedShort();
String version = "";
if ((major + "." + minor).equals("45.3")) version = "1.0";
else if ((major + "." + minor).equals("45.3")) version = "1.1";
else if ((major + "." + minor).equals("46.0")) version = "1.2";
else if ((major + "." + minor).equals("47.0")) version = "1.3";
else if ((major + "." + minor).equals("48.0")) version = "1.4";
else if ((major + "." + minor).equals("49.0")) version = "1.5";
else if ((major + "." + minor).equals("50.0")) version = "1.6";
else version = "?";
if (!l.contains(version)) l.add(version);
}
} catch (IOException ie) {}
dis.close();
}
for (Iterator iter = l.iterator(); iter.hasNext();) System.out.println("JVM-" + iter.next() + " " + (isEmpty(dir) ? jar[i].getName() : replace(replace(jar[i].getName(), dir, "", -1), System.getProperty("file.separator"), "", -1)));
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
System.exit(-1);
}
}
}

private static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}

//@param max - maximum number of values to replace, or <code>-1</code> if no maximum
private static String replace(String text, String searchString, String replacement, int max) {
if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) return text;
int start = 0;
int end = text.indexOf(searchString, start);
if (end == -1) return text;
int replLength = searchString.length();
int increase = replacement.length() - replLength;
increase = (increase < 0 ? 0 : increase);
increase *= (max < 0 ? 16 : (max > 64 ? 64 : max));
StringBuffer buf = new StringBuffer(text.length() + increase);
while (end != -1) {
buf.append(text.substring(start, end)).append(replacement);
start = end + replLength;
if (--max == 0) break;
end = text.indexOf(searchString, start);
}
buf.append(text.substring(start));
return buf.toString();
}

}
Recibe un único argumento (que puede ser un fichero JAR o un directorio) y te dice las diferentes versiones de JVM utilizadas para compilar las clases que hay empaquetadas en las librerías (en formato JAR) localizadas.

En caso de no pasarle ningún argumento, busca dentro del directorio "local" desde el que lanzas el comando.

sábado, 9 de mayo de 2009

RIA no quiere decir RapIdAmente

A raíz de mis últimos artículos, sobre tecnologías para el desarrollo de aplicaciones empresariales y "ricas" para la web, he recibido algún comentario argumentando que: "este tipo de tecnologías no nos están ayudando en el desarrollo rápido de nuestros productos".

Algunos programadores tienen la sensación, incluso, de contar con técnicas y herramientas "cavernícolas" para emprender nuevos desarrollos con este tipo de tecnologías. No puedo estar más que "de acuerdo" con esta última sensación.

Como todos sabemos, muchos de los proyectos que abordamos sufren, al final, retrasos o se entregan con funcionalidades faltantes. Muchas veces, la dirección de nuestras empresas de tecnología achacan estos defectos a los propios desarrolladores o a la tecnología seleccionada.

Los desarrolladores y la tecnología seleccionada no suelen ser, casi nunca, la causa de los retrasos de un proyecto (a no ser que contemos con programadores sin experiencia y no se haya seleccionado la tecnología adecuada para abordar el proyecto requerido).

Normalmente la causa de los retrasos está en la mala gestión del propio proyecto. Es decir, la culpa normalmente es de la "dirección". Tampoco son buenas compañeras las prisas...

Además, si la "dirección" quiere contar con una tecnología con la que se desarrolle rápidamente, o que encontremos un entorno de desarrollo que genere aplicaciones enriquecidas con tan solo "pulsar un botón", lo que hay que hacer es... cambiar de "dirección".

El construir aplicaciones RIA (enriquecidas) no quiere decir construir aplicaciones RapIdAmente. Mi experiencia me dice que, ninguna de las tecnologías (que yo conozco) te van a ayudar en "esto" de la rapidez.

Más bien todo lo contrario. Hoy en día, igual te da que selecciones Flex, Silverlight, JSF, GWT, AJAX o ColdFusion o una combinación de alguna de las anteriores. Con ninguna he conseguido rapidez en el desarrollo. La tecnología actualmente tendrá otras virtudes, pero no esta.

Los tiempos han cambiado (aunque parece que aún estamos en el pleístoceno de la programación) y pensar que podemos desarrollar una aplicación (web, enriquecida y con un comportamiento típico de aplicación escritorio) con una única tecnología que nos dé solución a cualquier problema que nos surja... es una utopía.

viernes, 8 de mayo de 2009

Ser un emprendedor o ser, mejor, un prendeador

En un evento, que presencié hace unos días, se hablaba de cómo los emprendedores tecnológicos nos ayudarán a salir de esta dura crisis económica que estamos viviendo. Ya he comentado anteriormente que, no creo en el concepto de emprendedor tal y como se utiliza habitualmente.

Yo "emprendo" todos los días cantidad de acciones y no por ello soy un emprendedor. Ser emprendedor se ha, mal, asociado, en multitud de veces, con aquella persona que tiene éxito (económico) en la creación de empresas, productos y/o servicios.

O, también, con aquella persona que, sin tener éxito, intenta una y otra vez crear empresas, productos y/o servicios.

Quizá sería más acertado utilizar, para estos dos casos, el término empresario o el término tozudo.

A mí me gusta más usar los términos prendedor (de prender) y prendador (de prendar). Quizá sería, pues, necesario acuñar (la propondré a la RAE) una nueva palabra: el prendeador.

Desde mi punto de vista, la característica principal (además del "prender" y "prendar") que define a este tipo de personas, al menos a los prendeadores que yo he conocido, es simplemente la ilusión.

Hoy en día, en tecnología, yo hablo de tres perfiles: ingenieros, artesanos e inventores. Los verdaderos prendeadores deben tener un 60% de inventores, un 30% de artesanos y un 10% de ingenieros.

Dentro de una organización, sin embargo, el porcentaje debe cambiar. Al menos, debe haber un inventor, un par de artesanos y el resto pueden ser ingenieros ;-) Si estas organizaciones cuentan, además, con un prendeador... mucho mejor.

TwinDocs o del buzón al cajón

El pasado miércoles 6 de mayo, un "viejo" conocido (Alfonso Lahuerta) presentó en Zaragoza, junto con sus dos socios, el proyecto TwinDocs. Oficialmente, TwinDocs es un nuevo servicio "on-line" de archivo y gestión de documentos electrónicos oficiales.

La presentación fue respaldada por el Gobierno de Aragón, teniendo lugar en la sala donde se celebran los eventos a los que se les quiere dar gran relevancia. Se aprovechó, también, para hablar de cómo los emprendedores de esta región nos ayudarán a salir de la crisis económica. Sobre todo, según parece, los emprendedores tecnológicos. Yo no creo en la palabra emprendedor.

Pero, ¿qué es realmente TwinDocs? o ¿qué necesidad pretende cubrir?

Presencié el evento por Internet, no sin dificultades técnicas, y me registré como usuario ese mismo día, para probar el servicio. La presentación me había dejado un poco "contrariado". Algunas de las funcionalidades me parecían sorprendentes, otras sonaban más obvias y otras no eran tan originales.

Tenía la sensación de que TwinDocs iba a estar más orientado hacia la Administración Electrónica (aún queda mucho de la Ley 11/2007 por desarrollar en la región), pero, no era así. Este servicio está orientado, fundamentalmente, al ciudadano, dotándole de una herramienta para que "tenga bien ordenados todos sus papeles" y esto lo haga, además, en la propia Red. TwinDocs es, pues, un cajón de documentos en la Red.

Pero, ¿quién dice que el ciudadano necesite tener ordenados sus papeles? ¿Quién dice, tan siquiera, que necesite archivarlos/conservarlos? Respecto al papeleo personal ¿no nos manejamos mejor en el caos, o en la despreocupación, que intentando mantener el orden?

Yo, normalmente, paso los papeles poco importantes del buzón a un cajón "desastre" y los importantes (son los menos) al cajón de la mesita de noche. Cuando se acumulan, o ya no tiene sentido el conservarlos, los "arrojo" al contenedor del reciclaje de papel. ¿TwinDocs hará todo esto por mí, de forma automática? Espero que sí...

Si consiguen incluir, en su servicio, los módulos necesarios para que "esto" se haga de forma transparente para el usuario... tendrán el éxito asegurado.

Es necesario, además, que las compañías que me prestan servicios (bancos, eléctricas, telefónicas, la administración, etc.) puedan enviarme documentación a mi cajón TwinDocs, de forma sencilla. También resultaría útil que TwinDocs sea bidireccional: del ciudadano a la compañía (o administración) y de la compañía (o administración) al ciudadano.

Pero lo más importante, si este servicio ahorra dinero (en papel, trámites e infraestructuras)... lo que el ciudadano quiere es que este ahorro le repercuta a él en su bolsillo. Esto es lo que me hará ser fiel al servicio TwinDocs. Si las compañías ahorran todo ese dinero, que lo inviertan en TwinDocs (que sufraguen ellos el coste del servicio) y en el propio usuario (por ejemplo, mediante descuentos a aquellos usuarios que usen esta herramienta).