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.

2 comentarios:

Anónimo dijo...

Que cachondo ;-)))

Adivina que me sucede si la ejecuto

"Unsupported major.minor version"

....

Sergio Montesa dijo...

Realmente curioso, suena a broma, no lo he provocado a propósito.

Seguramente porque compilo ya todo con 1.6, pero ni aún así me lo explico porque no estoy utilizando nada extraño en el código fuente.