Cómo detectar si un fichero ha sido creado, modificado o borrado utilizando Java

Java

Desde hace tiempo estamos acostumbrados a que nuestros IDEs y otros gestores de archivos nos avisen cuando un fichero ha cambiado.

La razón del cambio puede ser desde la modificación o creación del fichero, hasta el borrado del mismo.

La forma más eficiente de conseguir esto es recibiendo los eventos de estas situaciones directamente del sistema nativo de notificación de eventos del SO.

Pues bien, desde Java podemos conseguir este comportamiento utilizando la interface WatchService.

Veamos en qué consiste

Para empezar partimos de un directorio sobre el que queremos ser notificados de eventos que se produzcan sobre él. Partimos entonces de un objeto que implementa la interface

  • java.nio.file.Watchable.

como es el caso de los objetos de tipo

  • java.nio.file.Path.

Y partimos de los tipos de eventos que queremos ser notificados, como son los

  • StandardWatchEventKinds.ENTRY_CREATE
  • StandardWatchEventKinds.ENTRY_DELETE
  • StandardWatchEventKinds.ENTRY_MODIFY
  • StandardWatchEventKinds.OVERFLOW

Entonces creamos el objeto Watchable y le registramos el servicio WatchService junto con los eventos en los que estamos interesados.

Este proceso de registro devuelve un objeto WatchKey, que es sobre el que recibiremos todos los eventos que se produzcan. Todos los eventos que se produzcan serán encolados en este objeto y los recibiremos utilizando el método poll() o take() del servicio WatchService.

  • poll()
  • take()

Ambos recuperan el WatchKey con los eventos producidos. La diferencia entre ambos métodos es que take() espera a que exista al menos un WatchKey con eventos, mientras que poll() si no hay ninguno devolvería null.

Una vez hemos procesado todos los eventos encolados en el WatchKey debemos resetearlo para que pueda seguir recibiendo y encolando más eventos.

En el caso de que un sistema de ficheros sea más rápido informando de los eventos que la implementación que los procesa, cuando procesemos dichos eventos a través del WatchKey recibiremos un evento del tipo OVERFLOW.

El servicio WatchService se puede utilizar de forma concurrente por varios consumidores. El único cuidado que tenemos que tener en estos casos es llamar al método reset() solo después de que sus eventos del WatchEvent hayan sido todos procesados.

Finalmente llamaremos al método close() del servicio WatchService para finalizar con la monitorización del directorio en el que estábamos interesados.

Código de ejemplo

Sigue leyendo

Importar un Certificado de una CA en el cacerts de la Jdk

Java

Es habitual encontrarnos en situaciones donde una empresa tiene su propia autoridad de certificación (Certificate authority o CA) con la que firma los certificados de sus servidores o bien nos encontramos con servicios REST o web services de sites cuyo certificado esta firmado por una CA que no la tenemos en nuestro almacén de CAs de confianza.

En estos casos para que nuestras aplicaciones puedan establecer una conexión segura a estos servicios, necesitamos incorporar el certificado de la CA en nuestro almacén de CAs de confianza.

De esta forma, siempre y cuando nos encontremos con un certificado firmado por cualquiera de esas nuevas CAs, confiaremos automáticamente en ellos.

Como sabemos una CA firma los certificados que emite con su clave privada y es mediante el certificado de la CA con lo que podemos verificar la firma. Esta firma va incluida en el certificado emitido. Por ese motivo incorporamos el certificado de la CA entre los que confiamos.

En la Jdk el almacen de CAs de confianza lo vamos a encontrar en:

$JAVA_HOME/jre/lib/security/cacerts

Para incorporar el Certificado de la CA en el cacerts necesitamos importarlo.

Importar un certificado de una CA en nuestro almacén de CAs de confianza o cacerts

$JAVA_HOME/bin/keytool -import -alias NuevoCA -file NuevoCertificadoCA.crt -keystore $JAVA_HOME/jre/lib/security/cacerts

La contraseña por defecto del cacerts es: changeit.

Este comando nos pedirá confirmación de que queremos importar este nuevo certificado en el almacén de CAs.

Importar un certificado de una CA en un almacén de CAs de confianza

keytool -import -trustcacerts -alias NuevoCA -file NuevoCertificadoCA.crt -keystore cacerts_app

-trustcacerts: Se utiliza cuando el -keystore no es el cacerts, y queremos que al importar el certificado, keytool tenga en cuenta los Certificados de las CAs que tenemos en el fichero cacerts

Herramienta gráfica para administrar un almacén de certificados

Si no queremos utiliza la línea de comando siempre podemos recurrir a alguna de las herramientas gráficas existente para manejar y administrar certificados.

Portecle es una de ellas. Muy simple y sencilla de utilizar.

Portecle

Excepción habitual que pretendemos evitar al incorpotar el Certificado de la CA

Cuando accedemos a un servicio por conexión segura y el certificado es autofirmado o no tenemos el certificado de la CA que lo firma en nuestro cacerts, obtendremos la siguiente excepción:

Exception in thread "main" javax.xml.ws.WebServiceException: Fallo al acceder al WSDL en: https://localhost/myservice. Ha fallado con: 
    sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.
    at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:250)
    at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:231)
    at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:194)
    at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:163)
    ...
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1937)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    ...

Incorporando el Certificado de la CA evitaremos este tipo de excepciones.

Convertir coordenadas en grados, minutos y segundos a su valor en grados decimales en Java

Java

Existen varias formas de convertir las coordenadas representadas en grados, minutos y segundos a su representación en grados decimales.

El objetivo es pasar de

  • latitud: 40° 20.2′ 55.68
  • longitud: 3° 21′ 3.29

a

  • latitud: 40.352133333333335
  • longitud: 3.350913888888889

Ejemplo de clase Java

public class LatLongUtils {

    public static Double[] toDecimal(String latitude, String longitude) {
        try {
            String[] lat = latitude.replaceAll("[^0-9.\\s-]", "").split(" ");
            String[] lng = longitude.replaceAll("[^0-9.\\s-]", "").split(" ");
            Double dlat = toDecimal(lat); 
            Double dlng = toDecimal(lng);
            return new Double[]{dlat, dlng};
        } catch(Exception ex) {
            log.error(String.format("Error en el formato de las coordenadas: %s %s", new Object[]{latitude, longitude}), ex);
            return null;
        }
    }

    public static Double toDecimal(String latOrLng) {
        try {
            String[] latlng = latOrLng.replaceAll("[^0-9.\\s-]", "").split(" ");
            Double dlatlng = toDecimal(latlng); 
            return dlatlng;
        } catch(Exception ex) {
            log.error(String.format("Error en el formato de las coordenadas: %s ", new Object[]{latOrLng}), ex);
            return null;
        }
    }

    public static Double toDecimal(String[] coord) {
        double d = Double.parseDouble(coord[0]);
        double m = Double.parseDouble(coord[1]);
        double s = Double.parseDouble(coord[2]);
        double signo = 1;
        if (coord[0].startsWith("-"))
            signo = -1;
        return signo * (Math.abs(d) + (m / 60.0) + (s / 3600.0));
    }

    public static void main(String[] args) {
        Double[] coord = toDecimal("40° 20.2' 55.68\"", "3° 21' 3.29\"");
        System.out.println(coord[0] + " " + coord[1]);
    }

}

Eliminar acentos y diacríticos de un String en Java

Java

Todos nos hemos encontrado en la situación de tener que eliminar las tildes y diacrípticos de una cadena de texto o String en una aplicación Java. Los motivos por los que hacemos esto pueden ser muy diversos:

  • para realizar algún tipo de comparación entre cadenas de texto
  • para intentar normalizar de alguna manera la cadena de texto antes de guardarlos en una base de datos
  • etc

En cualquier caso el código que nos resuelve esto es muy sencillo

Código Java

package example.string;

import java.text.Normalizer;

public class Norm {

    public static void main(String[] args) throws Exception {
        String cadena = "áéíóú";
        System.out.println(cadena + " = " + cleanString(cadena));
        cadena = "àèìòù";
        System.out.println(cadena + " = " + cleanString(cadena)); 
        cadena = "äëïöü";
        System.out.println(cadena + " = " + cleanString(cadena));
        cadena = "âêîôû";
        System.out.println(cadena + " = " + cleanString(cadena));
        cadena = "2² + 2³";
        System.out.println(cadena + " = " + cleanString(cadena));
    }

    public static String cleanString(String texto) {
        texto = Normalizer.normalize(texto, Normalizer.Form.NFD);
        texto = texto.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
        return texto;
    }
}

Resultado de la ejecución

áéíóú = aeiou
àèìòù = aeiou
äëïöü = aeiou
âêîôû = aeiou

Normalizer.normalize(texto, Normalizer.Form.NFD);

Según el API de la Jdk los métodos normalize de java.text.Normalizer transforman un texto Unicode en su representación compuesta o descompuesta, lo que nos permite poder buscar y ordenar textos de una forma más sencilla después de la transformación.

La transformación está recogida en un estándar de Unicode que lo podemos consultar en Unicode Standard Annex #15 Unicode Normalization Forms.

De este estándar nos interesa la transformación llamada Canonical decomposition.

Esta transformación transforma los caracteres con tildes y diacríticos separandolos en dos caracteres. Por ejemplo

á = a´
ä = a¨
â = a^

y con lo que el código

Normalizer.normalize(texto, Normalizer.Form.NFD);

convertimos los textos a su forma Canonical decomposition que utilizaremos en el siguiente paso para lograr nuestro objetivo.

\p{InCombiningDiacriticalMarks}

Lo utilizamos en expresiones regulares, y es independiente del lenguaje, por lo que lo podemos utilizar desde Java, Javascript, Php, Perl, Node.js y todo aquel lenguaje con una implementación básica de expresiones regulares.

Esta expresión representa los caracteres UTF-8 comprendidos entre

  • U+0300 hasta U+036F

entre los que se encuentran las tildes y todos los diacríticos en los que estamos interesados.

Entonces el código

texto = texto.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");

lo que hace es eliminar todos esos caracteres del texto, consiguiendo finalmente el resultado esperado.

Tabla con los caracteres comprendidos entre U+0300 y U+036F

U+0300  ̀   ̀   ̀   ̀   ̀   COMBINING GRAVE ACCENT
U+0301  ́   ́   ́   ́   ́   COMBINING ACUTE ACCENT
U+0302  ̂   ̂   ̂   ̂   ̂   COMBINING CIRCUMFLEX ACCENT
U+0303  ̃   ̃   ̃   ̃   ̃   COMBINING TILDE
U+0304  ̄   ̄   ̄   ̄   ̄   COMBINING MACRON
U+0305  ̅   ̅   ̅   ̅   ̅   COMBINING OVERLINE
U+0306  ̆   ̆   ̆   ̆   ̆   COMBINING BREVE
U+0307  ̇   ̇   ̇   ̇   ̇   COMBINING DOT ABOVE
U+0308  ̈   ̈   ̈   ̈   ̈   COMBINING DIAERESIS
U+0309  ̉   ̉   ̉   ̉   ̉   COMBINING HOOK ABOVE
U+030A  ̊   ̊   ̊   ̊   ̊   COMBINING RING ABOVE
U+030B  ̋   ̋   ̋   ̋   ̋   COMBINING DOUBLE ACUTE ACCENT
U+030C  ̌   ̌   ̌   ̌   ̌   COMBINING CARON
U+030D  ̍   ̍   ̍   ̍   ̍   COMBINING VERTICAL LINE ABOVE
U+030E  ̎   ̎   ̎   ̎   ̎   COMBINING DOUBLE VERTICAL LINE ABOVE
U+030F  ̏   ̏   ̏   ̏   ̏   COMBINING DOUBLE GRAVE ACCENT
U+0310  ̐   ̐   ̐   ̐   ̐   COMBINING CANDRABINDU
U+0311  ̑   ̑   ̑   ̑   ̑   COMBINING INVERTED BREVE
U+0312  ̒   ̒   ̒   ̒   ̒   COMBINING TURNED COMMA ABOVE
U+0313  ̓   ̓   ̓   ̓   ̓   COMBINING COMMA ABOVE
U+0314  ̔   ̔   ̔   ̔   ̔   COMBINING REVERSED COMMA ABOVE
U+0315  ̕   ̕   ̕   ̕   ̕   COMBINING COMMA ABOVE RIGHT
U+0316  ̖   ̖   ̖   ̖   ̖   COMBINING GRAVE ACCENT BELOW
U+0317  ̗   ̗   ̗   ̗   ̗   COMBINING ACUTE ACCENT BELOW
U+0318  ̘   ̘   ̘   ̘   ̘   COMBINING LEFT TACK BELOW
U+0319  ̙   ̙   ̙   ̙   ̙   COMBINING RIGHT TACK BELOW
U+031A  ̚   ̚   ̚   ̚   ̚   COMBINING LEFT ANGLE ABOVE
U+031B  ̛   ̛   ̛   ̛   ̛   COMBINING HORN
U+031C  ̜   ̜   ̜   ̜   ̜   COMBINING LEFT HALF RING BELOW
U+031D  ̝   ̝   ̝   ̝   ̝   COMBINING UP TACK BELOW
U+031E  ̞   ̞   ̞   ̞   ̞   COMBINING DOWN TACK BELOW
U+031F  ̟   ̟   ̟   ̟   ̟   COMBINING PLUS SIGN BELOW
U+0320  ̠   ̠   ̠   ̠   ̠   COMBINING MINUS SIGN BELOW
U+0321  ̡   ̡   ̡   ̡   ̡   COMBINING PALATALIZED HOOK BELOW
U+0322  ̢   ̢   ̢   ̢   ̢   COMBINING RETROFLEX HOOK BELOW
U+0323  ̣   ̣   ̣   ̣   ̣   COMBINING DOT BELOW
U+0324  ̤   ̤   ̤   ̤   ̤   COMBINING DIAERESIS BELOW
U+0325  ̥   ̥   ̥   ̥   ̥   COMBINING RING BELOW
U+0326  ̦   ̦   ̦   ̦   ̦   COMBINING COMMA BELOW
U+0327  ̧   ̧   ̧   ̧   ̧   COMBINING CEDILLA
U+0328  ̨   ̨   ̨   ̨   ̨   COMBINING OGONEK
U+0329  ̩   ̩   ̩   ̩   ̩   COMBINING VERTICAL LINE BELOW
U+032A  ̪   ̪   ̪   ̪   ̪   COMBINING BRIDGE BELOW
U+032B  ̫   ̫   ̫   ̫   ̫   COMBINING INVERTED DOUBLE ARCH BELOW
U+032C  ̬   ̬   ̬   ̬   ̬   COMBINING CARON BELOW
U+032D  ̭   ̭   ̭   ̭   ̭   COMBINING CIRCUMFLEX ACCENT BELOW
U+032E  ̮   ̮   ̮   ̮   ̮   COMBINING BREVE BELOW
U+032F  ̯   ̯   ̯   ̯   ̯   COMBINING INVERTED BREVE BELOW
U+0330  ̰   ̰   ̰   ̰   ̰   COMBINING TILDE BELOW
U+0331  ̱   ̱   ̱   ̱   ̱   COMBINING MACRON BELOW
U+0332  ̲   ̲   ̲   ̲   ̲   COMBINING LOW LINE
U+0333  ̳   ̳   ̳   ̳   ̳   COMBINING DOUBLE LOW LINE
U+0334  ̴   ̴   ̴   ̴   ̴   COMBINING TILDE OVERLAY
U+0335  ̵   ̵   ̵   ̵   ̵   COMBINING SHORT STROKE OVERLAY
U+0336  ̶   ̶   ̶   ̶   ̶   COMBINING LONG STROKE OVERLAY
U+0337  ̷   ̷   ̷   ̷   ̷   COMBINING SHORT SOLIDUS OVERLAY
U+0338  ̸   ̸   ̸   ̸   ̸   COMBINING LONG SOLIDUS OVERLAY
U+0339  ̹   ̹   ̹   ̹   ̹   COMBINING RIGHT HALF RING BELOW
U+033A  ̺   ̺   ̺   ̺   ̺   COMBINING INVERTED BRIDGE BELOW
U+033B  ̻   ̻   ̻   ̻   ̻   COMBINING SQUARE BELOW
U+033C  ̼   ̼   ̼   ̼   ̼   COMBINING SEAGULL BELOW
U+033D  ̽   ̽   ̽   ̽   ̽   COMBINING X ABOVE
U+033E  ̾   ̾   ̾   ̾   ̾   COMBINING VERTICAL TILDE
U+033F  ̿   ̿   ̿   ̿   ̿   COMBINING DOUBLE OVERLINE
U+0340  ̀   ̀   ̀   ̀   ̀   COMBINING GRAVE TONE MARK
U+0341  ́   ́   ́   ́   ́   COMBINING ACUTE TONE MARK
U+0342  ͂   ͂   ͂   ͂   ͂   COMBINING GREEK PERISPOMENI
U+0343  ̓   ̓   ̓   ̓   ̓   COMBINING GREEK KORONIS
U+0344  ̈́   ̈́   ̈́  ̈́  ̈́  COMBINING GREEK DIALYTIKA TONOS
U+0345  ͅ   ͅ   ͅ   ͅ   ͅ   COMBINING GREEK YPOGEGRAMMENI
U+0346  ͆   ͆   ͆   ͆   ͆   COMBINING BRIDGE ABOVE
U+0347  ͇   ͇   ͇   ͇   ͇   COMBINING EQUALS SIGN BELOW
U+0348  ͈   ͈   ͈   ͈   ͈   COMBINING DOUBLE VERTICAL LINE BELOW
U+0349  ͉   ͉   ͉   ͉   ͉   COMBINING LEFT ANGLE BELOW
U+034A  ͊   ͊   ͊   ͊   ͊   COMBINING NOT TILDE ABOVE
U+034B  ͋   ͋   ͋   ͋   ͋   COMBINING HOMOTHETIC ABOVE
U+034C  ͌   ͌   ͌   ͌   ͌   COMBINING ALMOST EQUAL TO ABOVE
U+034D  ͍   ͍   ͍   ͍   ͍   COMBINING LEFT RIGHT ARROW BELOW
U+034E  ͎   ͎   ͎   ͎   ͎   COMBINING UPWARDS ARROW BELOW
U+034F  ͏   ͏   ͏   ͏   ͏   COMBINING GRAPHEME JOINER
U+0350  ͐   ͐   ͐   ͐   ͐   COMBINING RIGHT ARROWHEAD ABOVE
U+0351  ͑   ͑   ͑   ͑   ͑   COMBINING LEFT HALF RING ABOVE
U+0352  ͒   ͒   ͒   ͒   ͒   COMBINING FERMATA
U+0353  ͓   ͓   ͓   ͓   ͓   COMBINING X BELOW
U+0354  ͔   ͔   ͔   ͔   ͔   COMBINING LEFT ARROWHEAD BELOW
U+0355  ͕   ͕   ͕   ͕   ͕   COMBINING RIGHT ARROWHEAD BELOW
U+0356  ͖   ͖   ͖   ͖   ͖   COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW
U+0357  ͗   ͗   ͗   ͗   ͗   COMBINING RIGHT HALF RING ABOVE
U+0358  ͘   ͘   ͘   ͘   ͘   COMBINING DOT ABOVE RIGHT
U+0359  ͙   ͙   ͙   ͙   ͙   COMBINING ASTERISK BELOW
U+035A  ͚   ͚   ͚   ͚   ͚   COMBINING DOUBLE RING BELOW
U+035B  ͛   ͛   ͛   ͛   ͛   COMBINING ZIGZAG ABOVE
U+035C  ͜   ͜   ͜   ͜   ͜   COMBINING DOUBLE BREVE BELOW
U+035D  ͝   ͝   ͝   ͝   ͝   COMBINING DOUBLE BREVE
U+035E  ͞   ͞   ͞   ͞   ͞   COMBINING DOUBLE MACRON
U+035F  ͟   ͟   ͟   ͟   ͟   COMBINING DOUBLE MACRON BELOW
U+0360  ͠   ͠   ͠   ͠   ͠   COMBINING DOUBLE TILDE
U+0361  ͡   ͡   ͡   ͡   ͡   COMBINING DOUBLE INVERTED BREVE
U+0362  ͢   ͢   ͢   ͢   ͢   COMBINING DOUBLE RIGHTWARDS ARROW BELOW
U+0363  ͣ   ͣ   ͣ   ͣ   ͣ   COMBINING LATIN SMALL LETTER A
U+0364  ͤ   ͤ   ͤ   ͤ   ͤ   COMBINING LATIN SMALL LETTER E
U+0365  ͥ   ͥ   ͥ   ͥ   ͥ   COMBINING LATIN SMALL LETTER I
U+0366  ͦ   ͦ   ͦ   ͦ   ͦ   COMBINING LATIN SMALL LETTER O
U+0367  ͧ   ͧ   ͧ   ͧ   ͧ   COMBINING LATIN SMALL LETTER U
U+0368  ͨ   ͨ   ͨ   ͨ   ͨ   COMBINING LATIN SMALL LETTER C
U+0369  ͩ   ͩ   ͩ   ͩ   ͩ   COMBINING LATIN SMALL LETTER D
U+036A  ͪ   ͪ   ͪ   ͪ   ͪ   COMBINING LATIN SMALL LETTER H
U+036B  ͫ   ͫ   ͫ   ͫ   ͫ   COMBINING LATIN SMALL LETTER M
U+036C  ͬ   ͬ   ͬ   ͬ   ͬ   COMBINING LATIN SMALL LETTER R
U+036D  ͭ   ͭ   ͭ   ͭ   ͭ   COMBINING LATIN SMALL LETTER T
U+036E  ͮ   ͮ   ͮ   ͮ   ͮ   COMBINING LATIN SMALL LETTER V
U+036F  ͯ   ͯ   ͯ   ͯ   ͯ   COMBINING LATIN SMALL LETTER X 

java.lang.ClassNotFoundException

Java

Esta excepción esta muy relacionada con el artículo anterior java.lang.NoClassDefFoundError, Caused by: java.lang.ClassNotFoundException.

A menudo nos encontraremos ambas excepciones, pero en ocasiones solo nos encontraremos con java.lang.ClassNotFoundException.

El motivo de esta excepción es prácticamente el mismo. En la documentación nos dice que esta excepción se produce cuando el ClassLoader intenta leer la descripción de una clase, y la definición de dicha clase no es encontrada.

Estamos ante el mismo motivo visto en el artículo anterior. La definición de la clase no se encuentra debido a que la librería que lo contiene no esta en el classpath de la aplicación, o bien, si estamos ante un servidor de aplicaciones, la librería no está o el classloader que intenta cargarla no tiene visible esa librería. En cualquier caso tenemos que repasar la configuración de nuestras aplicaciones para solucionar este tipo de errores.

En los servidores de aplicaciones es más común encontrarnos con estas excepciones, ya que existen varios ClassLoader diferentes para una aplicación, y distinta políticas de carga de clases. Tenemos un ClassLoader para el servidor, uno para librerías comunes a todas las aplicaciones, y un ClassLoader para cada aplicación. La jerarquía normalmente es esta aunque depende del servidor de aplicaciones. Así por ejemplo si se delega la carga de clases al classloader del servidor, y la librería con definición de la clase se encuentra en la aplicación (es decir en el WEB-INF/lib) al final obtendremos esta excepción o la que comentábamos en el artículo anterior.

Vamos a reproducir esta excepción y así veremos como es el código que provoca estos errores.

Creación de la estructura del proyecto

mkdir a
mkdir b
mkdir main

Creación de la clase A.java

La creamos en el directorio o paquete a.

package a;
public class A {
    public void echo() {
        System.out.println("Hello from A !");
    }
}

Creación de la clase B.java

La creamos en el directorio o paquete b.

package b;
public class B {
    public void echo() {
        System.out.println("Hello from B !");
    }
}

Creación de la clase MainClass.java

Esta clase hace uso de las dos anteriores, y la creamos en el directorio o paquete main.

package main;

import b.*;
import a.*;
public class MainClass {
    public static final void main(String argv[]) {
        A a = new A();
        a.echo();
        try {
            Class bclass = Class.forName("b.B");
            B b = (B) bclass.newInstance();
            b.echo();
        } catch(Throwable ex) {
            ex.printStackTrace();
        }
    }
}

Compilamos los fuentes

Nos situamos en el directorio raiz y compilamos

$ javac a*.java b*.java main*.java

Ejecutamos la clase principal

$ java -cp . main.MainClass
Hello from A !
Hello from B !

Provocamos la excepción java.lang.ClassNotFoundException

La forma mas sencilla es renombrar el directorio b por otro nombre, y así la clase B ya no es encontrara por la Jvm, ya que deja de existir el package b.

Ejecutamos de nuevo la aplicación y obtendremos la excepción esperada.

$ java -cp . main.MainClass
Hello from A !
java.lang.ClassNotFoundException: b.B
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown S
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at main.MainClass.main(MainClass.java:10)

En esta ocasión el error no se produce en la creación de la clase (new B()), si no cuando intentamos leer su definición

Class bclass = Class.forName("b.B");

java.lang.NoClassDefFoundError, Caused by: java.lang.ClassNotFoundException

Java

Según la documentación la excepción java.lang.NoClassDefFoundError se produce cuando la Jvm o el ClassLoader intenta cargar la definición de una clase y no es capaz de encontrarla.

Es una excepción que se produce en tiempo de ejecución o Runtime, y no de compilación.

Nuestro código es capaz de compilar perfectamente pero en tiempo de ejecución no es capaz de encontrar la definición de la clase.

Esta excepción es un claro ejemplo de un problema en el classpath. Por lo tanto lo primero que tenemos que hacer es verificar que tenemos todas las librerías en el clasapath de nuestra aplicación y de que nadie lo ha modificado.

Es muy frecuente encontrarnos con estos problemas en un servidor de aplicaciones. Se producen cuando cambiamos la configuración de los classloader del servidor o bien hemos eliminado la referencia a la librería donde se encuentra la clase a la que hacemos referencia.

La solución pasa por repasar la configuración del classpath y de los classloader de nuestro servidor de aplicaciones o de nuestra aplicación.

Vamos a provocar esta misma excepción en una aplicación muy simple para que entendamos que efectivamente el problema se debe a que no se ha encontrado la definición de la clase.

Creación de la estructura del proyecto

mkdir a
mkdir b
mkdir main

Creación de la clase A.java

La creamos en el directorio o paquete a.

package a;
public class A {
        public void echo() {
                System.out.println("Hello from A !");
        }
}

Creación de la clase B.java

La creamos en el directorio o paquete b.

package b;
public class B {
        public void echo() {
                System.out.println("Hello from B !");
        }
}

Creación de la clase MainClass.java

Esta clase hace uso de las dos anteriores, y la creamos en el directorio o paquete main.

package main;

import b.*;
import a.*;
public class MainClass {
        public static final void main(String argv[]) {
                A a = new A();
                a.echo();
                try {
                        B b = new B();
                        b.echo();
                } catch(Throwable ex) {
                        ex.printStackTrace();
                }                        
        }
}

Compilamos los fuentes

Nos situamos en el directorio raiz y compilamos

$ javac a*.java b*.java main*.java

Ejecutamos la clase principal

$ java -cp . main.MainClass
Hello from A !
Hello from B !

Provocamos la excepción java.lang.NoClassDefFoundError

Como hemos visto, la excepción se produce cuando la Jvm o ClassLoader no es capaz de encontrar la definición de la clase. Por lo tanto para provocar esta excepción modificamos el classpath de la aplicación.

La forma mas sencilla es renombrar el directorio b por otro nombre, y así la clase B ya no es encontrada por la Jvm, ya que deja de existir el package b.

Ejecutamos de nuevo la aplicación y obtendremos la excepción esperada.

$ java -cp . main.MainClass
Hello from A !
java.lang.NoClassDefFoundError: b/B
        at main.MainClass.main(MainClass.java:10)
Caused by: java.lang.ClassNotFoundException: b.B
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more

Para que la Jvm pueda crear la instancia de la clase B necesita tener la definición de la clase. Para ello busca en el classpath la clase B y al no encontrarla lanza la excepción ClassNotFoundException. Esto provoca que la definición de la clase no pueda ser recuperada y la Jvm nos avisa con la excepción NoClassDefFoundError.

Ambas excepciones están relacionadas y normalmente las veremos siempre juntas.

Solución

Como hemos visto la solución pasa por repasar el classpath de nuestra aplicación y añadir la librería que nos falta. Si estamos en un servidor de aplicaciones tendremos igualmente que verificar si tenemos incluida la librería en el classpath y si aun así la tenemos incluida, debemos revisar la configuración de los classloaders ya que el que esta intentando leer la definición de la clase no tiene visible la lectura de esa librería.

Java, java.lang.UnsupportedClassVersionError: Unsupported major.minor version

Java

Seguro que en más de un ocasión nos hemos encontrado con esta excepción. Sabemos que el problema se debe a versiones de compilación y ejecución diferentes. Compilamos con una versión de la Jsdk superior a la versión que vamos a utilizar en ejecución o runtime.

Es decir, que la versión del formato utilizado para generar el fichero *.class es diferente y no esta soportado por la versión de la Jsdk donde se esta ejecutando la clase.

Para reproducir este error es muy fácil. Dada la siguiente clase:

public class Main {
    public static final void main(String argv[]) {
        System.out.println("This is a simple class ...");
    }   
}

vamos a compilarla y ejecutarla con versiones diferentes de Jsdk.

Compilación y ejecución con la Jsdk: java version 1.8.0_45

Versión Jsdk

$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

Compilación

$ javac Main.java

Ejecución

$ java Main
This is a simple class ...

Ejecución con la Jsdk: java version 1.6.0_45

Aquí es donde conseguiremos reproducir esta excepción.

Versión Jsdk

$ java -version
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

Ejecución

$ java Main
Exception in thread "main" java.lang.UnsupportedClassVersionError: Main : Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)

Listado de la relación entre esta numeración y las versiones de la Jsdk actuales

Esta relación la encontraremos en muchos otros artículos, y es la siguiente:

Major Jsdk version 
45    1.1
46    1.2
47    1.3
48    1.4
49    1.5
50    1.6
51    1.7
52    1.8

Como vemos la versión de la Jsdk 1.6 solo soporta hasta el número de versión 50, y no el 52 como vemos en la excepción.

Pero lo realmente interesante es dado una clase compilada, saber cual es la máxima Jdk con la que se puede ejecutar. Y esto lo podemos ver con el comando javap que viene con las Jsdk.

$ javap -verbose Main
...
...
  SourceFile: "Main.java"
  minor version: 0
  major version: 52

Con lo que vemos de nuevo que esta clase solo se puede ejecutar con la versión 52 que se corresponde con la Jsdk 1.8.

Solución

La solución pasa por compilar con la misma versión de la Jsdk donde se va a ejecutar el código, o bien, cambiar la versión de la Jsdk donde se va a ejecutar el código por la misma versión de la Jsdk utilizada para compilar.

¿Donde viene indicado estas numeraciones?

Viene en los primeros bytes de todas las clases compiladas, ya que el compilador javac deja esta información en los primeros bytes de las clases compiladas.

Un poco más sobre javap -verbose y sobre lo que dice la especificación de la Jvm sobre los números de versión de los ficheros *.class

La salida del comando informa entre otras cosas de minor version y major version de la versión del *.class que le pasamos como parámetro.

Esto determina la versión del formato del fichero *.class. En este caso como vemos la versión del formato del *.class es la 52.0, que se corresponde con major version.minor version y esto se corresponde con la Jsdk 1.8.

Según la especificación, la versión del formato del *.class soportada por una Jsdk tiene que estar comprendida entre su rango minor version.0 <= Número Versión fichero Compilación <= major version.minor version, así para la Jsdk 6 esto significa 0.0 <= Número Versión fichero Compilación <= 50.0.

Si en la salida javap -verbose tenemos que la versión del formato del fichero es la 52.0, vemos como esta condición 0.0 <= 52.0 <= 50.0 no se cumple. Y es el motivo por el que estamos obteniendo esta excepción.

Leer un fichero línea a línea utilizando una expresión Lambda y Stream

Java

La aparición de las expresiones Lambda y Stream en Jdk 8 nos ha facilitado la implementación de ciertas funcionalidades. Este es el caso, donde ahora resulta mucho más sencillo leer las líneas de un fichero de texto y procesarlas utilizando las expresiones lambdas y los Stream.

El código es simple, utilizamos la clase Files disponible a partir de la Jdk 7, la cual ha sido refactorizada en la Jdk 8 para aprovechar las ventajas de los Stream y poder así utilizar las expresiones lambda

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ReadLinesFromFile {

    public static final String DEFAULT_CHARSET = "UTF-8";

    public static void main(String[] args) throws IOException {
        // Por ejemplo args[0] puede ser en windows c:\temporal\fichero.txt o unix /var/fichero.txt
        Path path = Paths.get(args[0]);
        Files.lines(path, Charset.forName(DEFAULT_CHARSET)).forEach(line -> {
            System.out.println("Caracter number por line: " + line.length());
        });
    }

}

Como vemos hemos indicado el encoding del fichero a UTF-8. En realidad es el charset por defecto utilizado por Files.lines(…) cuando lo utilizamos sin indicar el charset, es decir, ambas líneas de código son análogas:

Files.lines(path, Charset.forName(DEFAULT_CHARSET)).forEach( ... );
Files.lines(path).forEach( ... );

El método lines devuelve un Stream de String, sobre el cual utilizamos su método forEarch que admite una expresión lambda, que en este caso es una implementación de la interface funcional Cosumer.

Genéricos en Java, declaración y uso de los tipos y métodos genéricos

Java

En Java hemos utilizado desde la JDK 5.0 los tipos genéricos o Generic Types, y aunque estamos acostumbrados a utilizarlos, en este artículo vamos a resumir su sintaxis y utilización.

  • Tipos genéricos o Generic Types son clases o interfaces parametrizadas por un tipo
  • Métodos genéricos o Generic Methods son métodos donde se definen y utilizan los tipos genéricos pero que están limitados al ámbito del método donde se declara

Por tanto en una clase genérica podemos tener métodos genéricos y métodos normales, y en una clase normal podemos tener métodos genéricos y métodos normales.

Sigue leyendo

Genéricos en Java, donde y cómo utilizar los wildcards o comodines

Java

Los wildcards o comodines representan en los tipos genéricos de Java (Java Generics) un tipo desconocido. Deberíamos evitar su utilización y ser siempre lo más especificos posible. Cuando esto no es posible utilizaremos los wildcards o comodines.

¿Cómo y donde podemos utilizar los comodines o wildcards?

  • cómo tipo de un parámetro, es decir, en las definiciones de los parámetros de los métodos
  • cómo variable local, es decir, en las definiciones de las variables locales
  • cómo campo o propiedad de una clase, es decir, en las definiciones de las propiedades de una clase
  • cómo tipo devuelto por un método, es decir, en las definiciones del tipo devuelto por un método

El código de ejemplo no tiene lógica o sentido práctico, solo pretende mostrar donde y como utilizar los wildcards.

public class MiTipo<T> {

    private T t;

    public MiTipo(T t) {
        this.t = t;
    }

    public T getT() {
        return this.t;
    }

}
import java.io.File;
import java.io.Reader;
import java.util.List;

public class UsoDeWildcards {

    /* como campo o propiedad de clase */
    public MiTipo<? extends Reader> tipo1;
    public List<? extends File> ficheros;

    /* como tipo devuelto por un metodo */
    public MiTipo<? extends String> usoUno() {
        /* ... */
        return null;
    }

    /* como tipo de parametro o argumento de un metodo */
    public void usoDos(MiTipo<? super Integer> mitipo) {
        /* ... */
    }

    /* como variable local */
    public void usoTres() {
        @SuppressWarnings("unused")
        MiTipo<? extends String> mitipo;
        /* ... */
    }

}