Creazione nuova Annotazione per Eclipse (ConditionAnnotations)
Proveremo ora a creare una nuova annotazione per Eclipse con l'inserimento di un plugin che ci permetterà di analizzare il codice in fase di creazione, segnalando eventuali errori di compilazione riguardanti la nostra annotazione.
Il progetto consiste nel creare le annotazioni @Precondition("condition") e @Postcondition("condition") le quali (come si deduce dal nome) dovranno verificare la condizione specificata al loro interno o al momento della chiamata di un metodo (pre) o all'uscita (post). In caso d'errore genereremo una eccezione.
Perchè Eclipse?
Il servizio APT può essere usato in Eclipse in modo semplice: si può usare il sito di aggiornamento per installare APT in una preinstallata istanza di Eclipse.
Ecco l'elenco dei prerequisiti minimi necessari per essere operativi con le annotazioni in Eclipse:
3. Installare e configurare l'APT in Eclipse
Per installare l'APT in Eclipse bisogna eseguire le seguenti operazioni:
Nota: questa è una release ALPHA e tutte le API contenute sono provvisorie. I soggetti possono variare con i futuri aggiornamenti.
4. Realizzazione del progetto: ConditionAnnotation (Precondition e Postcondition)
Eccoci giunti al momento di iniziare la realizzazione del nostro progetto.
Creeremo un' unica annotazione che al suo interno conterrà i 2 metodi Pre("condition") e Post("condition"), poi la classe necessaria perchè questa possa essere processata da APT (Factory) e d infine la classe Processor che eseguirà tutte le azioni che ci serviranno per raggiungere il nostro obbiettivo.
Per creare il progetto in Eclipse però, non creeremo un nuovo "Java project", ma un "Plug-in project" che ci semplificherà la vita al momento della creazione del Plugin.
Per cui, da Eclipse:







Creiamo ora le classi fondamentali per poter creare l'annotazione.
Prima di creare le classi è necessario dire al compilatore di Eclipse di usare la versione del JDK 5.0.
Per cui selezionare il progetto dal "Package Explorer" e tramite il tasto funzione del mouse (secondo tasto) selezionare " Properties". Nella finestra di dialogo aperta selezionare "javaCompiler", abilitare la voce "enable project specific settings" scegliere 5.0 dal menu a tendina della voce "compiler compliance level".
Premere "Apply" ed in seguito "Ok".
Per creare una nuova classe con Eclipse, selezionare il package (conditionAnnotation) del progetto dal "Package Explorer" e tramite il tasto funzione del mouse (secondo tasto) selezionare " New -> Class " .
Ecco le 3 classi principali che creeremo.
( Nota: La classe ConditionAnnotationPlugin creata in modo automatico non deve essere modificata. )
In questa classe andiamo a definire l'Annotazione vera e propria come abbiamo visto nella parte introduttiva della guida. Per cui:
package conditionAnnotation;public @interface ConditionAnnotation {
String Pre() default "TRUE";
String Post() default "TRUE";
}
In questo semplice modo abbiamo definito l'interfaccia.
5.2. ConditionAnnottationProcessorFactory.java
L' AnnotationProcessorFactory è una interfaccia da implementare per creare una "fabbrica" utilizzabile dall'APT. Questa classe necessita dell'implementazione di questi 3 metodi:
Ecco il codice da inserire:
package conditionAnnotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
public class ConditionAnnotationProcessorFactory implements
AnnotationProcessorFactory
{
public Collection<String> supportedOptions() {
return Collections.emptyList();
}
public Collection<String> supportedAnnotationTypes() {
return annotations;
}
public AnnotationProcessor getProcessorFor (
Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
return new ConditionAnnotationProcessor (env);
}
private static ArrayList<String> annotations = new ArrayList<String>();
{
annotations.add( ConditionAnnotation.class.getName() );
}
}
Verrà segnalato un errore sul tipo di ritorno "ConditionAnnotationProcessor" , problema che si risolverà con l'implementazione della seguente classe.
5.3. ConditionAnnottationProcessor.java
L'AnnotationProcessor è una semplice interfaccia che comporta un unico essenziale metodo:
public
void process();
Questo metodo sarà chiamato una volta da APT per la gestione delle anontazioni. Non possiede alcun parametro e esegue quello che l'ambiente APT gli passa attraverso il Factory. Ecco un esempio della classe:
package conditionAnnotation;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
public class ConditionAnnotationProcessor implements AnnotationProcessor {
public ConditionAnnotationProcessor(AnnotationProcessorEnvironment env) {
_env = env;
}
public void process() {
//....da fare
}
public AnnotationProcessorEnvironment getEnvironment() {
return _env;
} AnnotationProcessorEnvironment _env;
}
Andiamo ora a implementare il metodo process(), il cui contenuto saranno appunto le azioni che vorremmo far compiere relativamente all'annotazione.
Le azioni (scelte come esempio) sono principalmente 2:
Punto 1: visualizza il codice punto1.html
Per il corretto funzionamento la nuova lista di import sarà la seguente:
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Messager;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.Declaration;
In questo modo se scriveremo prima di un metodo di una classe di prova: @ConditionAnnotation(Pre=5) , Eclipse segnalerà l'errore e indicherà come messaggio: "Insert a string condition: Condition(Pre=String)".
Punto 2:
Sicuramente molto più impegnativo è generare una nuova classe copia della classe stessa che contiene l'annotazione e poi modificarla.
Per potere fare ciò dovremo:
Tutte queste operazioni (se non per la creazione della nuova classe) andranno eseguite durante l'analisi delle annotazioni presenti, per cui andremo a modificare il codice precedente e aggiungeremo delle variabili per la memorizzazione dei path e dei nomi ed un nuovo tipo (nuova classe) ConditionStatment in cui memorizzeremo tutte le info riguardanti la presenza di Pre e/o Post condition, il loro effettivo valore ("condition") e il numero di riga del file di testo .java per poi poterne andare a modificare il codice.
Per cui, la nuova classe sarà : ConditionStatement.java e conterrà il seguente codice:
package conditionAnnotation;
public class ConditionStatement {
public ConditionStatement() {
preCondition = "";
postCondition = "";
lineNumber = 0;
}
public void setPreCondition(Object preCondition) {
this.preCondition += preCondition;
}
public String getPreCondition() {
return preCondition;
}
public void setPostCondition(Object postCondition) {
this.postCondition += postCondition;
}
public String getPostCondition() {
return postCondition;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int getLineNumber() {
return lineNumber;
}
private String preCondition;
private String postCondition;
private int lineNumber;
}
Mentre il process() della classe ConditionAnnotationProcessor conterrà il seguente:
visualizza il codice CondAnnotProc1.htmlEcco la lista degli import aggiornata:
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Messager;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.Declaration;
import java.io.*;
Ora abbiamo tutte le informazioni necessarie per poter creare la nuova classe. APT mette a disposizione il tipo Filer, con il quale crea file con estensione .java nelle directory specificate ed in più (nel caso non vi siano errori di compilazione) ne crea anche il file oggetto in automatico sempre in una directory specificata e con estensione .class.
Prima di inserire il codice relativo alla creazione del file devo esporre un probabile "bug" dell'APT di Eclipse, ovvero non sempre viene rispettato l'ordine di lettura delle annotazioni, creando non piccoli problemi in fase di modifica della nuova classe a causa del sbagliato ordine dei numeri di riga. Motivo per cui, prima di modificare il tutto si precede ad un ordinamento della lista (sort) ed ad un overloading del metodo Comparator().
Per comodità inserisco tutto il contenuto della classe ConditionAnnotationProcessor:
A questo punto (anche se sono presenti dei warning non è un problema) siamo pronti a configurare il plugin e poi a generare la libreria.
6. Configurare il Meta-INF services ed il Plugin.xml
Perchè l'APT funzioni correttamente ed esegua il Factory è necessario creare un nuovo file in Meta-inf.
Per fare ciò prima di tutto creiamo una nuova directory in Meta-inf di nome "services" ed in questa creiamo un nuovo file di nome "com.sun.mirror.apt.AnnotationProcessorFactory" contenente l'unica riga "conditionAnnotation.ConditionAnnotationProcessorFactory" .

Per una corretta esecuzione del tutto occorre anche modificare il file plugin.xml abilitando il "factories".
Per cui il nuovo plugin.xml sarà il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
point="org.eclipse.jdt.apt.core.annotationProcessorFactory">
<factories enableDefault="true">
<factory
class="conditionAnnotation.ConditionAnnotationProcessorFactory">
</factory>
</factories>
</extension>
</plugin>
Ora la nostra annotazione è completa...generiamo la libreria per poi poterla usare tranquillamente in altre applicazioni.
Per generare la libreria occorre selezionare il progetto nella "package explorer" con il tasto funzione del mouse e selezionare "Export..." .
Dalla finestra di dialogo seguente selezionare "Jar file" e premere "Next".
Nella finestra nuova finestra di dialogo immettere il nome che si vuole dare alla libreria e lasciare invariate le altre opzioni.

Premere Next sia qua che nella finestra successiva intitolata "Jar packaging Options".
Nella finestra "Jar Manifest Specifications" selezionare "Using existing manifest from workspace".

Poi premere "Browse.." e selezionare il Manifest del progetto appena creato.
Premere "Ok" , "Finish" e nuovamente "Ok" all'avviso di warning contenuti nel progetto.
Eccoci quindi giunti alla prova della verità.
Per verificare il corretto funzionamento della nostra ConditionAnnotation creiamo un nuovo progetto java contenente qualche metodo (tra cui anche il metodo main per l'esecuzione).
Come prima cosa Importiamo la libreria conditionannotation.jar che per comodità potremmo includere in una cartella del progetto corrente (ad esempio /lib).
Per importarla selezioniamo le proprietà del progetto (tasto funzione del mouse sul "progetto" nel "package Explorer"), selezioniamo la scheda "Java Build Path" e poi "Libraries" . Premiamo "Add Jars.." e importiamo la libreria.
Poi specifichiamo al progetto che il compilatore dovrà usare il JDK 5.0 e abilitiamo l'Annotation Processing.

Per fare ciò espandiamo la voce "Java compiler" nelle proprietà.
Selezioniamo "Annotation processing" e selezioniamo "Enable Project specific settings" e poi "enable annotation processing". La directory (di default "__generated_src")
presente in "Generated source directory" è la directory dove verranno create le nuove classi generate in modo automatico dalla nostra annotazione.

Espandiamo ora la voce "Annotation processing" , selezioniamo "Factory path" e abilitiamo "Enable project specific settings".
Premere "Add jar" e selezionare la nostra libreria "conditionannotation.jar". Premere "Apply" e in seguito "Ok".

Generiamo ora una classe di Test (Test1.java) e inseriamo questo codice:
import conditionAnnotation.*;
public class test1 {
@ConditionAnnotation(Pre=3)
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
Salviamo il tutto e proviamo a compilare. Ci si accorge subito di come viene evidenziato l'errore nella condizione e di come venga creata (vuota per via degli errori di compilazione) una nuova directory (__generated_src/).

Creiamo ora un metodo che vuole in ingresso una variabile, per esempio: "public void setVar(int v)" e facciamo in modo che la variabile "var" possa essere settata solamente se "v>10".
import conditionAnnotation.*;
public class test1 {
@ConditionAnnotation(Pre="v>10")
public void setVal(int v) {
val = v;
}
public static void main(String[] args) {
test1 t = new test1();
t.setVal(5);
}
int val;
}
Salviamo e compiliamo. Si creerà in automatico una nuova classe la quale conterrà la classe test1.java modificata. Ora basterà eseguire quella e si noterà che verrà generata una eccezione.
Da notare come con il codice implemetato nel ConditionAnnotationProcessor si mantengano fedeli anche le corrispondenze tra package.
La stessa prova la si può eseguire con altri valoridi condition, con valori di Post.
E' stata gestita persino l'utilizzo delle annotazioni in più classi contemporaneamente, la quale porterà alla creazioni di più classi generate in automatico.
Il progetto è sicuramente una ottima base su cui partire, ma vi sono sicuramente migliorie che possono essere introdotte. Ecco qui un elenco delle principali: