Compilare per una versione precedente di Java con Animal Sniffer

Spesso è necessario compilare le proprie applicazioni Java in modo che siano compatibili con versioni precedenti del Java Runtime Environment (JRE).

Benché sia possibile utilizzare una vecchia versione del compilatore javac, tale soluzione è sconsigliabile in quanto si rinuncia a tutte le più recenti migliorie apportate al compilatore stesso.

javac supporta l’opzione -target che consente di specificare qual è la versione minima su cui si intende far girare la classe compilata. Tale opzione, tuttavia, è necessaria ma non sufficiente a garantire il corretto funzionamento della classe, in quanto se da un lato verifica che non siano state utilizzate caratteristiche del linguaggio non supportate dalla versione specificata, dall’altro non si accerta che non siano state utilizzate API, ovvero classi e metodi, non presenti in quella versione, a meno che non si prendano precise precauzioni.

A questo proposito, consideriamo un caso pratico: a partire dalla versione 9 di Java, l’interfaccia java.util.Set è stata arricchita di una serie di metodi statici denominati of(…), che consentono, con una sola istruzione, di istanziare degli insiemi contenenti zero o più elementi.

La seguente classe denominata, con molta fantasia, AnimalSnifferExample.java, fa uso proprio di uno di questi metodi:

public class AnimalSnifferExample {

   public static void main(String... args) {
      System.out.println(java.util.Set.of("1st item", "2nd item", "3rd item"));
   }

}

Effettueremo ora delle prove di compilazione ed esecuzione con diverse versioni di javac e java, utilizzando talvolta alcune specifiche opzioni di compilazione:

  • Compilando con javac 11 (l’importante è che la versione sia successiva alla 9) ed eseguendo con java 11 si ha:

    $ /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt/bin/javac -version AnimalSnifferExample.java
    javac 11.0.1-BellSoft
    
    $ /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt/bin/java -showversion AnimalSnifferExample
    openjdk version "11.0.1-BellSoft" 2018-10-16
    OpenJDK Runtime Environment (build 11.0.1-BellSoft+0)
    OpenJDK Server VM (build 11.0.1-BellSoft+0, mixed mode)
    [1st item, 3rd item, 2nd item]
    

    Nessuna sorpresa: il codice compila e funziona correttamente (se vi state chiedendo come mai gli elementi non siano ordinati, potete consultare la documentazione di java.util.Set).

    Se a questo punto provassimo a rieseguire con java 8 la classe compilata con javac 11 otterremmo invece il seguente errore:

    $ /usr/lib/jvm/jdk1.8.0_191/jre/bin/java -showversion AnimalSnifferExample
    java version "1.8.0_191"
    Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
    Java HotSpot(TM) Client VM (build 25.191-b12, mixed mode)
    
    Error: A JNI error has occurred, please check your installation and try again
    Exception in thread "main" java.lang.UnsupportedClassVersionError: AnimalSnifferExample has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
       at java.lang.ClassLoader.defineClass1(Native Method)
       at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
       at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
       at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
       at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
       at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
       at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
       at java.security.AccessController.doPrivileged(Native Method)
       at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
       at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
       at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
       at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
       at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
    

    Ancora una volta, nessuna sorpresa: è normale che una classe compilata con una certa versione di javac, senza aver specificato opportune opzioni, non risulti eseguibile con versioni precedenti di java.

  • Ora compiliamo sempre con javac 11 ma specificando l’opzione -target 1.8 (e conseguentemente anche l’opzione -source 1.8 richiesta dal compilatore); successivamente proviamo ad eseguire la classe con java 8:

    $ /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt/bin/javac -target 1.8 -source 1.8 -version AnimalSnifferExample.java
    javac 11.0.1-BellSoft
    warning: [options] bootstrap class path not set in conjunction with -source 8
    1 warning
    
    $ /usr/lib/jvm/jdk1.8.0_191/jre/bin/java -showversion AnimalSnifferExample
    java version "1.8.0_191"
    Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
    Java HotSpot(TM) Client VM (build 25.191-b12, mixed mode)
    
    Exception in thread "main" java.lang.NoSuchMethodError: java.util.Set.of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Set;
       at AnimalSnifferExample.main(AnimalSnifferExample.java:4)
    

    L’avviso emesso da javac relativamente al bootstrap class path non andrebbe sottovalutato, tuttavia la compilazione termina con successo. L’esecuzione, invece, termina in errore, segnalando che il metodo java.util.Set.of(…) non esiste. Questa situazione è assolutamente indesiderabile perché si ha un codice apparentemente compatibile con una versione precedente di Java, ma che in realtà va in errore a tempo di esecuzione.

  • Se volessimo rendere compatibile il nostro codice anche con java 7 (-target 1.7), paradossalmente saremmo più fortunati:

    $ /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt/bin/javac -target 1.7 -source 1.7 -version AnimalSnifferExample.java
    javac 11.0.1-BellSoft
    warning: [options] bootstrap class path not set in conjunction with -source 7
    AnimalSnifferExample.java:4: error: static interface method invocations are not supported in -source 7
          System.out.println(java.util.Set.of("1st item", "2nd item", "3rd item"));
                                          ^
      (use -source 8 or higher to enable static interface method invocations)
    1 error
    1 warning
    

    Come si vede, la compilazione fallisce perché il supporto ai metodi statici nelle interfacce è stato introdotto con Java 8, quindi quando si ha un’incompatibilità a livello di linguaggio, si riscontra un errore già a tempo di compilazione. In questa situazione, almeno, non c’è quella pericolosa parvenza di compatibilità sperimentata nel caso precedente.


Fortunatamente esistono diversi modi per far sì che la build notifichi eventuali problemi di compatibilità, evitando, così, spiacevoli problemi a runtime:

  • Se per compilare si utilizza JDK versione 9 o successiva e si intende compilare per JRE versione 6 o successiva, la cosa migliore è aggiungere la nuova opzione -release alla riga di comando del compilatore, seguita dal numero di versione del runtime target desiderato, ad esempio -release 6 per JRE 1.6; fatto questo, il problema si può considerare completamente risolto e non occorre fare altro. Se invece non fosse possibile utilizzare JDK versione 9 o successiva per la compilazione, vedere i punti successivi.

  • Aggiungere l’opzione --boot-class-path al compilatore, specificando il percorso della libreria di runtime Java di interesse, che nel nostro caso è costituita dal file rt.jar incluso nel JRE 1.8:

    $ /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt/bin/javac -source 1.8 -target 1.8 --boot-class-path /usr/lib/jvm/jdk1.8.0_191/jre/lib/rt.jar -version AnimalSnifferExample.java
    javac 11.0.1-BellSoft
    AnimalSnifferExample.java:4: error: cannot find symbol
          System.out.println(java.util.Set.of("1st item", "2nd item", "3rd item"));
                                          ^
      symbol:   method of(String,String,String)
      location: interface Set
    1 error
    

    Prima di tutto si nota che non viene più emesso il warning relativo al bootstrap class path, dopo di che la compilazione fallisce perché il compilatore non trova il metodo of(...) all’interno della libreria di runtime specificata.
    Si tratta di una soluzione assolutamente valida; il problema, tuttavia, è che non sempre si ha a disposizione il runtime necessario, specialmente se si utilizza un ambiente di build (ad esempio di continuous integration) fuori dal proprio controllo.

  • Utilizzare Animal Sniffer dopo la compilazione. Animal Sniffer è uno strumento open source che fa parte del progetto MojoHaus. Se integrato nel processo di build, è in grado di esaminare i file compilati (.class) per verificare che siano effettivamente compatibili con versioni precedenti del runtime Java. Questo controllo è reso possibile da opportune firme (signature) generate a partire dalle stesse librerie di runtime e rese disponibili sempre da MojoHaus. Vi sono anche firme specifiche per versioni particolari del JRE, anche proprietarie (ad es. Apple e IBM).

Nulla vieta, infine, di mettere in atto entrambi questi ultimi due accorgimenti.


Utilizzo di Animal Sniffer con Maven

Utilizzare Animal Sniffer con Maven è estremamente semplice, è sufficiente aggiungere l’apposito plugin nel pom.xml e il gioco è fatto:

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>animal-sniffer-maven-plugin</artifactId>
   <version>1.16</version>
   <executions>
      <execution>
         <phase>process-classes</phase>
         <goals>
            <goal>check</goal>
         </goals>
      </execution>
   </executions>
   <configuration>
      <signature>
         <groupId>org.codehaus.mojo.signature</groupId>
         <artifactId>java18</artifactId>
         <version>1.0</version>
      </signature>
   </configuration>
</plugin>

Occorre impostare artifactId e version della signature a seconda della compatibilità desiderata. In generale le ultime cifre dell’artifactId indicano la versione del runtime di riferimento (ad esempio 18 corrisponde al JRE 1.8).

Tutte le firme disponibili sono consultabili sul repository Maven Central; ove fossero presenti più versioni dello stesso artefatto, conviene scegliere la più recente.

Un’ultima considerazione riguarda la fase di esecuzione, qui impostata su process-classes; in alcuni esempi presenti in rete viene preferita la fase verify, ma poiché verify è successiva a package, la segnalazione di errore si avrebbe ad artefatto ormai prodotto (sebbene non installato), invece con process-classes o test, Animal Sniffer interviene subito dopo la compilazione.

Esempio completo con Maven Vedi su GitHub

Prepariamo la seguente alberatura di progetto:

├── src
│   └── main
│       └── java
│           └── AnimalSnifferExample.java
└── pom.xml

Il contenuto del file AnimalSnifferExample.java è stato già presentato; segue invece il listato completo del file pom.xml di Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <groupId>com.example</groupId>
   <artifactId>animal-sniffer-example-mvn</artifactId>
   <version>0.0.1</version>

   <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>

   <build>
      <plugins>
         <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-maven-plugin</artifactId>
            <version>1.16</version>
            <executions>
               <execution>
                  <phase>process-classes</phase>
                  <goals>
                     <goal>check</goal>
                  </goals>
               </execution>
            </executions>
            <configuration>
               <signature>
                  <groupId>org.codehaus.mojo.signature</groupId>
                  <artifactId>java18</artifactId>
                  <version>1.0</version>
               </signature>
            </configuration>
         </plugin>
      </plugins>
   </build>

</project>

Notare le righe evidenziate in cui sono presenti i riferimenti alle firme del runtime Java 1.8.

A questo punto, eseguendo Maven con Java 9 o versioni successive (di norma Maven utilizza il JDK referenziato dalla variabile di ambiente JAVA_HOME) si ha:

$ mvn clean package -V
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T20:41:47+02:00)
Maven home: /opt/maven
Java version: 11.0.1-BellSoft, vendor: BellSoft, runtime: /usr/lib/jvm/jdk-11-bellsoft-arm32-vfp-hflt
Default locale: en_GB, platform encoding: UTF-8
OS name: "linux", version: "4.14.79-v7+", arch: "arm", family: "unix"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< com.example:animal-sniffer-example-mvn >---------------
[INFO] Building animal-sniffer-example-mvn 0.0.1
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ animal-sniffer-example-mvn ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ animal-sniffer-example-mvn ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/animal-sniffer-example-mvn/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ animal-sniffer-example-mvn ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /tmp/animal-sniffer-example-mvn/target/classes
[INFO]
[INFO] --- animal-sniffer-maven-plugin:1.16:check (default) @ animal-sniffer-example-mvn ---
[INFO] Checking unresolved references to org.codehaus.mojo.signature:java18:1.0
[ERROR] /tmp/animal-sniffer-example-mvn/src/main/java/AnimalSnifferExample.java:4: Undefined reference: java.util.Set java.util.Set.of(Object, Object, Object)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.422 s
[INFO] Finished at: 2018-12-15T09:17:14+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.codehaus.mojo:animal-sniffer-maven-plugin:1.16:check (default) on project animal-sniffer-example-mvn: Signature errors found. Verify them and ignore them with the proper annotation if needed. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

Come è evidente, la build fallisce, prevenendo eventuali errori a tempo di esecuzione. L’output segnala inoltre il riferimento non valido al metodo java.util.Set.of(Object, Object, Object) che infatti in Java 8 non esiste.

Ulteriori informazioni sono disponibili sul sito di MojoHaus, nella pagina dedicata all’Animal Sniffer Maven Plugin.


Utilizzo di Animal Sniffer con Ant

Usare Animal Sniffer con Ant è leggermente più complicato rispetto a Maven, ma comunque nulla che richieda più di qualche minuto di messa a punto:

  1. Scaricare il file JAR animal-sniffer-ant-tasks (link) e il file .signature del runtime Java di interesse (link) dal repository Maven Central.
  2. Posizionare i due file scaricati all’interno della directory del progetto, ad esempio in una sottodirectory dedicata: ant/animal-sniffer/
  3. Modificare il file build.xml aggiungendo all’elemento <project> l’attributo xmlns:as="antlib:org.codehaus.mojo.animal_sniffer", in modo da definire un namespace as.
  4. Aggiungere, sempre al build.xml, un target che effettui il controllo delle classi compilate, ad esempio:

    <target name="check-signature">
       <typedef uri="antlib:org.codehaus.mojo.animal_sniffer">
          <classpath path="ant/animal-sniffer/animal-sniffer-ant-tasks-1.16.jar" />
       </typedef>
       <as:check-signature signature="ant/animal-sniffer/java18-1.0.signature">
          <path path="build/classes" />
          <classpath>
             <fileset dir="lib" erroronmissingdir="false">
                <include name="*.jar" />
             </fileset>
          </classpath>
       </as:check-signature>
    </target>
    

    Il task as:check-signature deve essere configurato con lo stesso classpath utilizzato dal task javac (che tipicamente si trova nel target compile). Verificare anche la correttezza dei riferimenti al JAR animal-sniffer-ant-tasks e al file .signature.

Esempio completo con Ant Vedi su GitHub

Prepariamo un’alberatura come la seguente, scaricando manualmente animal-sniffer-ant-tasks-1.16.jar e java18-1.0.signature dal repository Maven Central:

├── ant
│   └── animal-sniffer
│       ├── animal-sniffer-ant-tasks-1.16.jar
│       └── java18-1.0.signature
├── src
│   └── AnimalSnifferExample.java
└── build.xml

La classe AnimalSnifferExample.java è sempre la stessa; segue invece il listato completo del file build.xml di Ant:

<?xml version="1.0" encoding="UTF-8"?>
<project name="animal-sniffer-example-ant" xmlns:as="antlib:org.codehaus.mojo.animal_sniffer">

   <target name="clean">
      <delete dir="build" />
   </target>

   <fileset id="classpath" dir="lib" erroronmissingdir="false">
      <include name="*.jar" />
   </fileset>

   <target name="compile">
      <mkdir dir="build/classes" />
      <javac srcdir="src" destdir="build/classes" source="1.8" target="1.8" includeantruntime="false" debug="true">
         <compilerarg value="-version" />
         <classpath>
            <fileset refid="classpath" />
         </classpath>
      </javac>
   </target>

   <target name="check-signature">
      <typedef uri="antlib:org.codehaus.mojo.animal_sniffer">
         <classpath path="ant/animal-sniffer/animal-sniffer-ant-tasks-1.16.jar" />
      </typedef>
      <as:check-signature signature="ant/animal-sniffer/java18-1.0.signature">
         <path path="build/classes" />
         <classpath>
            <fileset refid="classpath" />
         </classpath>
      </as:check-signature>
   </target>

</project>

Notare la riga evidenziata in cui è presente il riferimento alle firme del runtime Java 1.8.

Eseguendo Ant con Java 9 o versioni successive (anche Ant, come Maven, utilizza il JDK referenziato dalla variabile di ambiente JAVA_HOME) si ha:

$ ant clean compile check-signature
Buildfile: /tmp/animal-sniffer-example-ant/build.xml

clean:

compile:
    [mkdir] Created dir: /tmp/animal-sniffer-example-ant/build/classes
    [javac] Compiling 1 source file to /tmp/animal-sniffer-example-ant/build/classes
    [javac] javac 11.0.1-BellSoft
    [javac] warning: [options] bootstrap class path not set in conjunction with -source 8
    [javac] 1 warning

check-signature:
[as:check-signature] In createClasspath
[as:check-signature] Checking unresolved references to /tmp/animal-sniffer-example-ant/ant/animal-sniffer/java18-1.0.signature
[as:check-signature] Ignoring the signatures from file to be checked: /tmp/animal-sniffer-example-ant/build/classes
[as:check-signature] /tmp/animal-sniffer-example-ant/build/classes/AnimalSnifferExample.class:4: Undefined reference: java.util.Set java.util.Set.of(Object, Object, Object)

BUILD FAILED
/tmp/animal-sniffer-example-ant/build.xml:26: Signature errors found. Verify them and ignore them with the proper annotation if needed.

Come nel precedente esempio con Maven, anche in questo caso la build fallisce segnalando le classi che contengono riferimenti non validi, inclusi i numeri di riga, se è stato impostato a true l’attributo debug del task javac di Ant.

Ulteriori informazioni sono disponibili sul sito di MojoHaus, nella pagina dedicata agli Animal Sniffer ANT Tasks.

Raspberry Pi – Java

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Le versioni di Java presenti nei repository APT di Raspbian possono essere obsolete o comunque inadeguate alle proprie esigenze, pertanto può essere desiderabile installarne altre. In questi casi, per consentire di volta in volta una selezione agevole della versione attiva, occorre tenere opportunamente aggiornate le alternative dei vari eseguibili di ciascun Java Development Kit (JDK) in modo da poter poi utilizzare l’utility update-java-alternatives, inclusa in Raspbian.

Oracle JDK

Le istruzioni che seguono si riferiscono alla versione 7u75 che è l’ultima versione di Java 7 rilasciata pubblicamente da Oracle per ARM, ma possono essere adattate ad altre versioni sostituendo 7 e 1.7.0_75 con la versione di interesse.

  1. Scaricare dal sito oracle.com una versione del JDK per Linux ARM in formato tar.gz. La versione JDK 7 più recente è la 7u75 Linux ARM v6/v7 Hard Float ABI. Per effettuare il download può essere richiesto un account Oracle gratuito.
  2. Decomprimere l’archivio nel percorso /usr/lib/jvm/ in modo che ne risulti un’alberatura del tipo /usr/lib/jvm/jdk1.7.0_75/:
    1. Recarsi nella directory contenente l’archivio tar.gz scaricato.
    2. sudo tar xzvf jdk-7u75-linux-arm-vfp-hflt.tar.gz -C /usr/lib/jvm
  3. Creare un link simbolico (alias) per questa versione di Java:
    1. cd /usr/lib/jvm
    2. sudo ln -nsf jdk1.7.0_75 java-7-oracle
  4. Creare il file jinfo (richiesto da update-java-alternatives) e le alternative per gli eseguibili; notare che il valore 1071 rappresenta la priorità e può essere modificato a piacimento (valori tipici sono: 1071 per JDK 7, 1081 per JDK 8):
    1. cd java-7-oracle
    2. sudo mkdir .alternatives && cd .alternatives
    3. sudo wget https://git.io/fAv4G -O alternatives.java
    4. sudo ../bin/javac alternatives.java
    5. sudo ../bin/java alternatives .. /usr/lib/jvm/java-7-oracle 1071
    6. sudo cp -i .java-*-oracle.jinfo /usr/lib/jvm/
    7. sudo ./install-alternatives.sh
  5. In caso di disinstallazione ovvero prima di installare una versione aggiornata, al fine di rimuovere le alternative che altrimenti resterebbero appese (consigliato), si può eseguire il seguente comando che elimina le stesse alternative precedentemente create da install-alternatives.sh:
    sudo /usr/lib/jvm/java-7-oracle/.alternatives/remove-alternatives.sh

BellSoft Liberica JDK

Liberica è un’implementazione di Java per Raspberry Pi totalmente open source. È compilata a partire da OpenJDK, cui BellSoft contribuisce. Le istruzioni che seguono si riferiscono alla versione 9.0.4 ma possono essere adattate ad altre versioni sostituendo 9 e 9.0.4 con la versione di interesse.

  1. Scaricare dal sito bell-sw.com > Get Liberica JDK una versione del JDK per Linux ARMv7&8 32 Bit HardFloat in formato deb:
    1. cd ~/Downloads
    2. wget https://github.com/bell-sw/Liberica/releases/download/9.0.4/bellsoft-jdk9.0.4-linux-arm32-vfp-hflt.deb
  2. sudo apt install ./bellsoft-jdk9.0.4-linux-arm32-vfp-hflt.deb
  3. Creare un link simbolico (alias) per questa versione di Java:
    1. cd /usr/lib/jvm
    2. sudo ln -nsf jdk-9.0.4-bellsoft-arm32-vfp-hflt java-9-bellsoft
  4. Determinare la priorità delle alternative create durante l’installazione:
    1. update-alternatives --display java
    2. Identificare la riga corrispondente al JDK appena installato e la relativa priorità, in questo caso:
      /usr/lib/jvm/jdk-9.0.4-bellsoft-arm32-vfp-hflt/bin/java - priority 1901
  5. Creare il file jinfo (richiesto da update-java-alternatives) utilizzando la priorità predeterminata:
    1. cd java-9-bellsoft
    2. sudo mkdir .alternatives && cd .alternatives
    3. sudo wget https://git.io/fAv4G -O alternatives.java
    4. sudo ../bin/javac alternatives.java
    5. sudo ../bin/java alternatives .. /usr/lib/jvm/java-9-bellsoft 1901
    6. sudo cp -i .java-*-bellsoft.jinfo /usr/lib/jvm/
  6. Cancellare l’archivio originale per recuperare spazio (facoltativo): rm ~/Downloads/bellsoft-jdk9.0.4-linux-arm32-vfp-hflt.deb

Per impostare la versione di Java attiva, utilizzare update-java-alternatives:

  1. Elenco versioni installate: update-java-alternatives -l
    java-10-bellsoft               1904       /usr/lib/jvm/java-10-bellsoft
    java-1.6.0-openjdk-armhf       1057       /usr/lib/jvm/java-1.6.0-openjdk-armhf
    java-7-oracle                  1071       /usr/lib/jvm/java-7-oracle
    java-8-oracle                  1081       /usr/lib/jvm/java-8-oracle
    java-9-bellsoft                1901       /usr/lib/jvm/java-9-bellsoft
    jdk-7-oracle-arm-vfp-hflt      317        /usr/lib/jvm/jdk-7-oracle-arm-vfp-hflt
    jdk-8-oracle-arm32-vfp-hflt    318        /usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt
  2. Selezione della versione desiderata: sudo update-java-alternatives -s java-7-oracle
  3. Eventuali errori non pregiudicano il buon esito dell’operazione:
    update-alternatives: error: no alternatives for jmc
    update-alternatives: error: no alternatives for jvisualvm
    update-alternatives: error: no alternatives for mozilla-javaplugin.so
  4. Per verificare quale versione è stata effettivamente impostata: java -version
    java version "1.7.0_75"
    Java(TM) SE Runtime Environment (build 1.7.0_75-b13)
    Java HotSpot(TM) Client VM (build 24.75-b04, mixed mode)
    

È consigliabile creare un link simbolico al JDK preferito e impostare la variabile d’ambiente JAVA_HOME, ad esempio:

  1. cd /usr/lib/jvm
  2. sudo ln -nsf java-7-oracle default-java
  3. echo 'JAVA_HOME="/usr/lib/jvm/default-java"' | sudo tee -a /etc/environment

Raspberry Pi – Tomcat

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Prima di iniziare, accertarsi di disporre di una versione di Java compatibile con la versione di Apache Tomcat che si intende installare.

  1. Scaricare una versione Binary Distributions – Core – tar.gz dal sito tomcat.apache.org > Download:
    1. cd /opt
    2. sudo wget http://mirrors.ibiblio.org/apache/tomcat/tomcat-9/v9.0.12/bin/apache-tomcat-9.0.12.tar.gz
  2. Decomprimere l’archivio in /opt/:
    1. sudo tar xzvf apache-tomcat-9.0.12.tar.gz
    2. sudo ln -nsf apache-tomcat-9.0.12 tomcat
    3. sudo rm apache-tomcat-9.0.12.tar.gz
  3. Creare l’utente Linux tomcat:
    1. sudo adduser --disabled-login --disabled-password --gecos "" --home /var/lib/tomcat tomcat
    2. sudo addgroup pi tomcat (efficace solo dopo disconnessione e riconnessione)
    3. sudo chown -R tomcat:tomcat tomcat/
  4. Impostare la variabile di ambiente CATALINA_HOME: echo 'CATALINA_HOME="/opt/tomcat"' | sudo tee -a /etc/environment
  5. Aggiungere gli utenti per le applicazioni manager e host-manager:
    1. sudo nano /opt/tomcat/conf/tomcat-users.xml
    2. Aggiungere le seguenti righe nell’elemento <tomcat-users> per definire gli utenti admin e manager, impostando opportunamente le password:
      <role rolename="manager-gui" />
      <role rolename="admin-gui" />
      <user username="admin" password="ADMIN" roles="manager-gui,admin-gui" />
      <user username="manager" password="MANAGER" roles="manager-gui" />
  6. Configurare la sicurezza di rete per le applicazioni manager e host-manager (verificare e adeguare opportunamente l’espressione regolare degli indirizzi autorizzati, in questo caso tutta la rete locale):
    1. sudo mkdir -p /opt/tomcat/conf/Catalina/localhost
    2. printf '<Context privileged="true" antiResourceLocking="false" docBase="${catalina.home}/webapps/manager">\n\t<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="%s" />\n</Context>\n' '127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[01])\.\d+\.\d+' | sudo tee /opt/tomcat/conf/Catalina/localhost/manager.xml
    3. printf '<Context privileged="true" antiResourceLocking="false" docBase="${catalina.home}/webapps/host-manager">\n\t<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="%s" />\n</Context>\n' '127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[01])\.\d+\.\d+' | sudo tee /opt/tomcat/conf/Catalina/localhost/host-manager.xml
  7. Per limitare l’uso di RAM (raccomandato), prima di avviare: export JAVA_OPTS="-Xms8m -Xmx128m -XX:MaxMetaspaceSize=128m" o meglio scrivere questa riga all’interno di setenv.sh in /opt/tomcat/bin/:
    1. printf '#!/bin/sh\nexport JAVA_OPTS="$JAVA_OPTS -Xms8m -Xmx128m -XX:MaxMetaspaceSize=128m"\n' | sudo tee /opt/tomcat/bin/setenv.sh
    2. sudo chmod 755 /opt/tomcat/bin/setenv.sh
  8. Compilare e installare la libreria Tomcat Native APR per migliorare le prestazioni (facoltativo, richiede OpenSSL >= 1.0.2):
    1. sudo -i
    2. cd /opt/tomcat/bin
    3. wget http://it.apache.contactlab.it/tomcat/tomcat-connectors/native/1.2.18/source/tomcat-native-1.2.18-src.tar.gz
    4. tar xzvf tomcat-native-1.2.18-src.tar.gz
    5. rm tomcat-native-1.2.18-src.tar.gz
    6. apt-get install libapr1-dev libssl-dev
    7. cd tomcat-native-1.2.18-src/native
    8. ./configure (se la variabile d’ambiente JAVA_HOME non è impostata, aggiungere --with-java-home=/usr/lib/jvm/...; per utilizzare una versione particolare di OpenSSL, aggiungere --with-ssl=/usr/local/ssl)
    9. make
    10. make install
    11. cd /usr/local/apr/lib
    12. rm -R /opt/tomcat/bin/tomcat-native-1.2.18-src
    13. mkdir -p /usr/java/packages/lib/arm
    14. cp libtcnative* /usr/java/packages/lib/arm/
    15. exit
  9. Installare haveged per velocizzare l’avvio del server (raccomandato): sudo apt-get install haveged
  10. Per avviare e arrestare Tomcat manualmente, utilizzare i seguenti comandi:
    1. sudo su tomcat -c "/opt/tomcat/bin/startup.sh"
    2. sudo su tomcat -c "/opt/tomcat/bin/shutdown.sh"
  11. Configurare Tomcat come servizio (consigliato):
    1. sudo wget https://git.io/fNhfK -O /etc/init.d/tomcat
    2. sudo chmod 755 /etc/init.d/tomcat
    3. sudo mkdir -p /etc/default
    4. sudo wget https://git.io/fNhf5 -O /etc/default/tomcat
    5. sudo update-rc.d tomcat defaults
    6. sudo systemctl daemon-reload (solo Raspbian Jessie e versioni successive)
  12. Il servizio viene preconfigurato per l’avvio automatico; per disabilitare o abilitare l’avvio automatico (facoltativo):
    • systemd (Raspbian Jessie e versioni successive):
      1. sudo systemctl disable tomcat
      2. sudo systemctl enable tomcat
    • SysV (Raspbian Wheezy):
      1. sudo update-rc.d tomcat disable
      2. sudo update-rc.d tomcat enable

Raspberry Pi – Maven

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Prima di iniziare, accertarsi di disporre di una versione di Java compatibile con la versione di Apache Maven che si intende installare.

  1. Scaricare una release dal sito maven.apache.org > Download > Files > Binary tar.gz archive:
    1. cd /opt
    2. sudo wget http://mirrors.ibiblio.org/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz
  2. sudo tar xzvf apache-maven-3.5.4-bin.tar.gz
  3. sudo ln -nsf apache-maven-3.5.4 maven
  4. sudo rm apache-maven-3.5.4-bin.tar.gz
  5. Creare le alternative (la versione associata al link simbolico sarà di norma quella preferita perché a priorità massima):
    1. sudo update-alternatives --install /usr/bin/mvn mvn /opt/maven/bin/mvn 2147483647
    2. sudo update-alternatives --install /usr/bin/mvn mvn /opt/apache-maven-3.5.4/bin/mvn 30504
    3. Per selezionare la versione da utilizzare (in presenza di molteplici versioni): sudo update-alternatives --config mvn
  6. Per limitare l’uso di RAM, impostare la variable di ambiente MAVEN_OPTS prima di eseguire mvn: export MAVEN_OPTS="-client -Xms8m -Xmx96m -DargLine=-Xmx192m"
    • È possibile rendere permanenti queste impostazioni (raccomandato):
      echo 'MAVEN_OPTS="-client -Xms8m -Xmx96m -DargLine=-Xmx192m"' | sudo tee -a /etc/environment
  7. Installare lo script bash di autocompletamento (facoltativo): sudo wget https://git.io/fpNqc -O /etc/bash_completion.d/mvn

Verificare l’avvenuta installazione: mvn -version

Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T20:33:14+02:00)
Maven home: /opt/maven
Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /usr/lib/jvm/jdk1.8.0_181/jre
Default locale: en_GB, platform encoding: UTF-8
OS name: "linux", version: "4.14.69-v7+", arch: "arm", family: "unix"

Raspberry Pi – Apache Ant

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Prima di iniziare, accertarsi di disporre di una versione di Java compatibile con la versione di Apache Ant che si intende installare.

  1. Scaricare una release dal sito ant.apache.org > Download > Binary Distributions in formato .tar.gz archive:
    1. cd /opt
    2. sudo wget http://mirrors.ibiblio.org/apache/ant/binaries/apache-ant-1.10.5-bin.tar.gz
  2. sudo tar xzvf apache-ant-1.10.5-bin.tar.gz
  3. sudo ln -nsf apache-ant-1.10.5 ant
  4. sudo rm apache-ant-1.10.5-bin.tar.gz
  5. Creare le alternative (la versione associata al link simbolico sarà di norma quella preferita perché a priorità massima):
    1. sudo update-alternatives --install /usr/bin/ant ant /opt/ant/bin/ant 2147483647
    2. sudo update-alternatives --install /usr/bin/ant ant /opt/apache-ant-1.10.5/bin/ant 11005
    3. Per selezionare la versione da utilizzare (in presenza di molteplici versioni): sudo update-alternatives --config ant
  6. Creare la variabile d’ambiente ANT_HOME (facoltativo): echo 'ANT_HOME="/opt/ant"' | sudo tee -a /etc/environment
  7. Per limitare l’uso di RAM, impostare la variable di ambiente ANT_OPTS prima di eseguire ant: export ANT_OPTS="-client -Xms8m -Xmx96m"
    • È possibile rendere permanenti queste impostazioni (raccomandato):
      echo 'ANT_OPTS="-client -Xms8m -Xmx96m"' | sudo tee -a /etc/environment

Verificare l’avvenuta installazione: ant -version

Apache Ant(TM) version 1.10.5 compiled on July 10 2018

Raspberry Pi – VisualVM

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Installazione mediante APT:

  1. sudo apt-get install visualvm

Installazione manuale (per ottenere una versione specifica o quella più recente):

  1. Scaricare il programma dal sito visualvm.github.io > Download > Binaries:
    1. cd /opt
    2. sudo wget https://github.com/visualvm/visualvm.src/releases/download/1.4.1/visualvm_141.zip
  2. sudo unzip visualvm_141.zip
  3. sudo ln -nsf visualvm_141 visualvm
  4. sudo rm visualvm_141.zip
  5. sudo chown -R pi:pi visualvm/
  6. Creare la variabile d’ambiente VISUALVM_HOME (facoltativo):
    echo 'VISUALVM_HOME="/opt/visualvm"' | sudo tee -a /etc/environment
  7. Creare un collegamento nel menù delle applicazioni:
    printf '[Desktop Entry]\nCategories=Application;System;\nExec=/opt/visualvm/bin/visualvm -J-Xmx96m\nType=Application\nIcon=/opt/visualvm/etc/visualvm.icns\nName=VisualVM\n' | sudo tee /usr/share/applications/visualvm.desktop

Raspberry Pi – WildFly

WildFly screenshot

L’autore declina ogni responsabilità per eventuali danni di qualsiasi genere derivanti direttamente o indirettamente dall’uso delle informazioni contenute in quest’articolo.

Prima di iniziare, accertarsi di disporre di una versione di Java compatibile con la versione di WildFly che si intende installare.

  1. Scaricare una release dal sito wildfly.org > Downloads:
    1. cd /opt
    2. sudo wget http://download.jboss.org/wildfly/14.0.1.Final/wildfly-14.0.1.Final.tar.gz
  2. Espandere l’archivio in /opt/:
    1. sudo tar xzvf wildfly-14.0.1.Final.tar.gz
    2. sudo ln -nsf wildfly-14.0.1.Final wildfly
    3. sudo rm wildfly-14.0.1.Final.tar.gz
  3. Creare l’utente Linux wildfly:
    1. sudo adduser --disabled-login --disabled-password --gecos "" --home /var/lib/wildfly wildfly
    2. sudo chown -R wildfly:wildfly wildfly/
  4. Creare gli utenti WildFly management (a) e application (b): sudo /opt/wildfly/bin/add-user.sh
  5. Per limitare l’uso di RAM (raccomandato), modificare il file /opt/wildfly/bin/standalone.conf variando i valori della riga JAVA_OPTS che contiene le opzioni -Xms e -Xmx:
    1. sudo nano /opt/wildfly/bin/standalone.conf
    2. JAVA_OPTS="-Xms16m -Xmx192m -XX:MaxMetaspaceSize=128m -Djava.net.preferIPv4Stack=true"
  6. Definire variabili di ambiente (raccomandato):
    1. echo 'WILDFLY_HOME="/opt/wildfly"' | sudo tee -a /etc/environment
    2. echo 'JBOSS_HOME="/opt/wildfly"' | sudo tee -a /etc/environment
  7. Per avviare il server manualmente: sudo su wildfly -c "/opt/wildfly/bin/standalone.sh"
    • Per consentire l’accesso da indirizzi diversi da localhost (facoltativo), eseguire in alternativa: sudo su wildfly -c "/opt/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0"
    • In caso di errore “Server VM is only supported on ARMv7+ VFP”, modificare il file /opt/wildfly/bin/standalone.conf aggiungendo alla riga JAVA_OPTS l’opzione -client prima di -Xms
  8. Per arrestare il server manualmente: sudo su wildfly -c "/opt/wildfly/bin/jboss-cli.sh --connect command=:shutdown"
    • In caso di timeout (WFLYPRT0023), aggiungere le seguenti righe nell’elemento <jboss-cli> del file jboss-cli.xml: sudo nano /opt/wildfly/bin/jboss-cli.xml
      <command-timeout>90</command-timeout>
      <connection-timeout>90000</connection-timeout>
  9. Configurare WildFly come servizio (consigliato):
    1. sudo wget https://raw.githubusercontent.com/wildfly/wildfly-core/master/core-feature-pack/src/main/resources/content/docs/contrib/scripts/init.d/wildfly-init-debian.sh -O /etc/init.d/wildfly
    2. sudo chmod 755 /etc/init.d/wildfly
    3. sudo mkdir -p /etc/default
    4. sudo wget https://raw.githubusercontent.com/wildfly/wildfly-core/master/core-feature-pack/src/main/resources/content/docs/contrib/scripts/init.d/wildfly.conf -O /etc/default/wildfly
    5. Per consentire l’accesso da indirizzi diversi da localhost (facoltativo):
      echo 'JBOSS_OPTS="-b 0.0.0.0 -bmanagement 0.0.0.0"' | sudo tee -a /etc/default/wildfly
    6. sudo update-rc.d wildfly defaults
    7. sudo systemctl daemon-reload (solo Raspbian Jessie e versioni successive)
  10. Il servizio viene preconfigurato per l’avvio automatico; per disabilitare o abilitare l’avvio automatico del servizio (facoltativo):
    • systemd (Raspbian Jessie e versioni successive)
      1. sudo systemctl disable wildfly
      2. sudo systemctl enable wildfly
    • SysV (Raspbian Wheezy)
      1. sudo update-rc.d wildfly disable
      2. sudo update-rc.d wildfly enable

Bug della Java Virtual Machine

Non mi ero mai imbattuto in un bug della Java Virtual Machine fino alla scorsa estate. Stavo collaudando l’applicazione RouterLogger, la quale basa il suo funzionamento su un ciclo di richieste di rete che può essere infinito oppure limitato ad un numero di iterazioni configurabile. La prima implementazione prevedeva un ciclo quasi infinito:

private void loop() throws IOException, InterruptedException {

    /* Determinazione numero di iterazioni... */
    int iterations = configuration.getInt("logger.iterations", -1);
    if (iterations <= 0) {
        iterations = Integer.MAX_VALUE;
    }

    for (int iteration = 1; iteration <= iterations && !exit; iteration++) {
        /* Richiesta di rete... */

        if (iteration != iterations) {
            /* Thread.sleep(waitTimeInMillis); */
        }
    }
}

Nel caso in cui la proprietà di configurazione logger.iterations fosse stata impostata ad un valore negativo (tipicamente -1), il programma avrebbe impostato come numero di iterazioni da eseguire Integer.MAX_VALUE (ossia 2.147.483.647), determinando un ciclo praticamente infinito considerando che, nel caso di RouterLogger, un’iterazione dura normalmente qualche secondo.

Ebbene, eseguendo un test con logger.iterations=-1, notavo che il loop terminava dopo un certo numero di iterazioni (intorno a sedicimila), sempre lo stesso, e senza alcun errore o eccezione, come se la condizione di permanenza nel ciclo iteration <= iterations && !exit non fosse più verificata, cosa evidentemente falsa. Controllato il codice e verificatane la correttezza, iniziavo a non capire cosa stesse succedendo. La confusione è aumentata quando ho notato che facendo girare l’applicazione direttamente da Eclipse, quindi utilizzando il compilatore integrato in Eclipse (JDT) invece di quello del JDK 6u45, il problema non si presentava affatto. Decidevo quindi fare il reverse engineering dei due compilati (.class) per scongiurare un eventuale difetto del compilatore del JDK, ma anche quest’indagine non portava da nessuna parte: i due bytecode, pur essendo differenti, non presentavano anomalie. Tanto per curiosità, pubblico il bytecode generato prima con il JDK e poi con Eclipse, da me reinterpretato in C per maggiore chiarezza:

JDK 6u45:

14:  int iterations = 2147483647;
20:  int iteration = 0;
22:  if (iteration > iterations) goto 152;
28:  if (exit) goto 152;
35:  /* Richiesta di rete... */
62:  if (iteration == iterations) goto 146;
143: /* Thread.sleep(waitTimeInMillis); */
146: iteration++;
149: goto 22;
152: return;

Eclipse JDT:

18:  int iterations = 2147483647;
22:  int iteration = 0;
24:  goto 144;
27:  /* Richiesta di rete... */
54:  if (iteration == iterations) goto 141;
138: /* Thread.sleep(waitTimeInMillis); */
141: iteration++;
144: if (iteration > iterations) goto 157;
154: if (!exit) goto 27;
157: return;

Seppur diversi, entrambi i codici sono logicamente corretti. Escluso un problema legato al compilatore, resta quasi solo l’ipotesi di un bug della JVM. Effettivamente aggiornando il JRE alla versione 7u80, il problema scompariva del tutto, indipendentemente dal compilatore utilizzato, tuttavia mi faceva piacere mantenere la compatibilità con il JRE 6. Inoltre la cosa è inquietante perché il codice è di una semplicità unica e il comportamento della JVM risulta totalmente illogico.

Dopo alcune ricerche, ho individuato che esiste effettivamente un bug della JVM (JDK-5091921) legato ad un’ottimizzazione che viene eseguita a runtime. In pratica, a un certo momento non ben precisato durante l’esecuzione del programma, la JVM decide di ottimizzare il loop modificando la condizione di permanenza nel seguente modo: i <= j diventa i < j + 1, probabilmente perché per il processore è più semplice eseguire un controllo di minoranza stretta; la cosa non sarebbe un problema se non fosse che, nel caso j = Integer.MAX_VALUE, la somma Integer.MAX_VALUE + 1, a causa di un overflow, risulti in un bel numerone negativo (per la precisione Integer.MIN_VALUE ossia -2.147.483.648). Per questo motivo l’indice dell’iterazione, qualsiasi esso fosse, diventava immediatamente maggiore di -2.147.483.648, e il loop terminava come se niente fosse e senza alcun errore.

Per rendere il mio codice compatibile con il JRE 6, ho deciso di modificarlo rimuovendo il riferimento ad Integer.MAX_VALUE, pervenendo tra l’altro ad una soluzione più semplice e pulita:

private void loop() throws IOException, InterruptedException {

    /* Determinazione numero di iterazioni... */
    int iterations = configuration.getInt("logger.iterations", -1);

    for (int iteration = 1; (iterations <= 0 || iteration <= iterations) && !exit; iteration++) {
        /* Richiesta di rete... */

        if (iteration != iterations) {
            /* Thread.sleep(waitTimeInMillis); */
        }
    }
}

Questo bug, scoperto nel 2004 e risegnalato numerose volte alla Sun (acquisita nel frattempo da Oracle nel 2010), era stato classificato a bassa priorità (no comment), ed è stato risolto solo nel 2011 con l’aggiunta di un controllo sull’overflow. In ogni caso, per maggiore sicurezza, conviene prestare particolare attenzione quando si ha a che fare con i MAX_VALUE.

RouterLogger

Non sarò responsabile di danni di alcun genere derivanti dall’uso dei file scaricabili da questa pagina; li utilizzate a vostro rischio.
I will be in no way liable for any loss or damage caused by the use of the software available on this site.

Descrizione

Quanto a disponibilità della banda larga, il nostro paese non eccelle di certo; al contrario non è raro che in alcune zone, anche a pochi chilometri dai grossi centri urbani, si possano riscontrare grossi problemi di connettività, e far funzionare correttamente una semplice ADSL può diventare un’impresa.

Proprio per permettere una più accurata diagnosi di eventuali problemi sulla linea, ho voluto creare questa piccola applicazione che registra periodicamente lo stato della linea interrogando direttamente il modem. Il programma comunica a intervalli più o meno regolari con il dispositivo mediante il protocollo Telnet, supportato dalla maggior parte degli apparati attualmente in commercio, e salva le informazioni in formato CSV, consultabile con Excel o altro foglio elettronico.

Screenshot dell'applicazione

L’applicazione è disponibile su GitHub e attualmente include il supporto per i seguenti modem:

Ogni dispositivo ha il suo particolare “linguaggio”, ad esempio per ottenere informazioni sullo stato della linea ADSL possono essere necessari comandi differenti a seconda della marca e del modello, ma qualsiasi programmatore Java può certamente estendere l’applicazione realizzando nuove implementazioni basate sulla classe astratta Reader, limitandosi quindi a sviluppare solo le poche parti di codice che variano a seconda dello specifico dispositivo.

Essendo sviluppata in Java, l’applicazione può essere eseguita su praticamente ogni sistema operativo e richiede JRE 6 (1.6) o versioni successive.

Download

RouterLogger 8.0.0: binarisorgenti (24/02/2018) – GitLicenza GPL.

È uscito il Sierra-Bates per la certificazione Java SE 7 Programmer

Dopo un’attesa durata diversi anni, ora che è già possibile certificarsi addirittura per la Java Standard Edition 8 (esame 1Z1-808), è stata finalmente data alle stampe la Study Guide (il libro di testo) per le certificazioni Oracle Java Standard Edition 7 Programmer di Kathy Sierra e Bert Bates, gli storici autori di molte delle precedenti guide ufficiali alle certificazioni Java di Sun e, successivamente, Oracle.
Nella pagina del prodotto sul sito della Oracle Press sono disponibili per il download gratuito il sommario e il capitolo 6 “Flow Control and Exceptions” in formato PDF.

Copertina del libro "OCA/OCP Java SE 7 Programmer I & II Study Guide (Exams 1Z0-803 & 1Z0-804)"

Non sono ancora disponibili i Practice Exams (simulazioni d’esame non al calcolatore, con soluzioni dettagliate) curati dagli stessi autori, la cui uscita è prevista non prima della nostra prossima vita (pare per il 2016, ma la data è già stata spostata più volte, ovviamente sempre in avanti). Si può comunque rimediare a questa mancanza con le numerose simulazioni d’esame disponibili online; tra le migliori si segnalano quelle curate da Enthuware, che ben ricalcano i quesiti e la grafica dell’esame reale.

A seguito dell’acquisizione di Sun Microsystems da parte di Oracle, è stata messa in atto una ristrutturazione del sistema di certificazioni Sun; Oracle ha infatti ereditato da Sun le certificazioni Java e le ha inserite nel proprio catalogo attribuendovi i livelli e, ultimamente, alcune propedeuticità già in uso per le altre certificazioni Oracle esistenti.

Oracle Certification Program

Nel programma di certificazione Oracle vi sono attualmente cinque categorie di certificazione: Associate, Professional, Expert, Master (in ordine di rilevanza) e Specialist (non presente tra le certificazioni Java). Sovente si può accedere al livello successivo solo se si possiede già un titolo del livello precedente, ma esistono delle eccezioni. Attualmente infatti è possibile diventare Oracle Certified Professional Java SE 5 o 6 sostenendo un solo esame, tuttavia Oracle ha stabilito che, a partire dalla versione 7, occorre superarne due: prima quello per il livello Associate e poi quello per il livello Professional.
Detto ciò, e constatato che certificarsi per la vecchia versione 5 è inutile, un’idea per chi non possiede nessuna certificazione potrebbe anche essere quella di orientarsi inizialmente sulla versione 6, in modo da ottenere, con un solo esame, un attestato Professional subito spendibile, sostenendo eventualmente successivamente solo l’esame di aggiornamento alla versione 7 (quello per la versione 8 è ancora in versione beta e solo per il livello Associate).

Ricapitolando, gli esami per la versione 7 sono dunque 1Z0-803 (Associate), 1Z0-804 (Professional) e 1Z0-805 (Professional, aggiornamento per candidati con precedente certificazione Java SE, anche rilasciata da Sun). Per la versione 6, invece, l’esame unico è 1Z0-851 (Professional), che corrisponde al vecchio 310-065 (numerazione Sun), e il materiale ufficiale di Sierra e Bates per prepararlo è ampiamente disponibile e collaudato da anni, si tratta della Study Guide (libro + CD) e dei Practice Exams.

Copertina della Study Guide per l'esame 1Z0-851 Copertina dei Practice Exams per l'esame 1Z0-851

Ultimo suggerimento: le edizioni in formato eBook dei manuali citati sono da evitare per via di possibili problemi di visualizzazione che potrebbero complicarne la fruizione; sono infatti presenti diverse immagini, molto codice Java e numerose tabelle che poco si prestano ad essere traslate in formati diversi dal PDF.

Link utili: