Introduzione torna su
Proveremo ora a creare una nuova annotazione per Eclipse con l'inserimento di un plugin che ci permetterà di processsare al volo ulteriori plugin con funzione di APT e quindi 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: puoi usare il sito di aggiornamento per installare APT in una preinstallata istanza di Eclipse.
Prerequisiti minimi torna su
Ecco l'elenco dei prerequisiti minimi necessari per essere operativi con le annotazioni:
Installare e configurare l'APT in Eclipse torna su
Ecco le istruzioni per aggiornare Eclipse con l'APT :
- Avviare Eclipse e aprire la finestra di dialogo "open the Install/Update"
(Help/SoftwareUpdates >/Find and Install...).
- Selezionare "Search for new updates" e premere "Finish"
- Aggiungere il sito remoto (Add a new remote site): http://www.eclipse.org/jdt/apt/JdtAptUpdateSite
- Installare la JdtAptFeature.
- IMPORTANTE: dopo aver terminato l'installazione e prima di riavviare Eclipse è fondamentale copiare il file tools.jar:
da: "
[your 1.5 JDK root] /lib/tools.jar "
a: "
[your eclipse root] /plugins/org.eclipse.jdt.apt.core_ [the installed release] /tools.jar "
Nota:
questa è una release ALPHA e tutte le API contenute sono provvisorie. I soggetti possono variare con i futuri aggiornamenti. Realizzazione del progetto: Condition Annotation (Pre e Post)torna su
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 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:
- File -> New -> Other...
- Plug-in project

- Premere "Next"
- Nella finestra di dialogo che appare immettere un nome per il progetto (es: ConditionAnnotation) e settare gli altri parametri come in figura.
- Premere Next. Nella seguente finestra di dialogo deselezionare "This plu-in will make..." come in figura:

- Premere Finish e rispondere in modo affermativo alla sucessiva richiesta.
- Si aprirà ora il nuovo "plug-in project" sulla pagina del Manifest.
- Selezionare dal pannello sottostante "Dependencies" e , premendo su Add, aggiungere il plugin org.eclipse.jdt.apt.core .
- Selezionare la scheda "Runtime" e nella sezione "Classpath" specificare il nome che daremo alla libreria, ad esempio nel nostro caso "conditionannotation.jar"
- Nella scheda Extensions -> All Extensions aggiungere (Add..) l'extension point "org.eclipse.jdt.apt.core.annotationProcessorFactory " e premere su finish.
- Nella scheda "Build" -> Binary build, espandere "classes" (o la directoy in cui sono contenuti i file .class) e selezionare "conditionAnnotation"
- Il resto lo lasciamo così com'è, anche se poi torneremo sulla scheda "plug-in" generata in automatico nei passaggi precedenti.
- Salvare i cambiamenti al progetto.
Creiamo ora le classi fondamentali per poter creare l'annotazione.
Le classi fondamentalitorna su
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. ) ConditionAnnottation.java
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. ConditionAnnottationProcessorFactory.java
L' AnnotationProcessorFactory è una interfaccia da implementare per creare una "fabbrica" utilizzabile dall'APT. Questa classe necessita dell'implementazione di questi 3 metodi:
- public Collection<String> supportedAnnotationTypes();
Fornisce la lista delle annotazioni supportate e ritorna una Collection contenente il nome completo dell'annotaioine.
- public Collection<String> supportedOptions()
Fornisce la lista delle opzioni che la "fabbrica" accetta e ceh possono essere passate sulla riga di comando con -Akey[=value] (il prefisso "-A" deve essere presente nelle catene di ritorno per questo metodo). Al momento, questo metodo non è usato dall'APT, ma si stà pensando ad un suo utilizzo futuro...
- public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env)
Questo metodo sarà chiamata dall'APT al fine di ottenere un oggetto AnnotationProcessor si occuperà del trattamento delle Annotazioni. Il paramtro adts contiene la lista delle annotazioni trovate e env permette di interagire con l'ampbiente di APT.
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.
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:
- Controllare che gli argomenti inseriti nell'annotazione abbiano parametri corretti e in caso contrario "evidenziare e segnalare" l'errore nell'editor di Eclipse (nel nostro caso a semplice esempio illustrativo controlleremo che il valore inserito nel Pre() o/e Post() siano tipi String e non altri tipi).
- Creare una una copia della classe che contiene l'annotazione andando a verificare le condizioni specificate nel metodo annotato (inseriremo un if("condition") all'inizio in caso di un Pre("condition") ed un if("condition") alla fine in caso di Post("condition") ).
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:
- sapere che classe contiene l'annotazione
- sapere dove risiede fisicamente la classe.java per poterne eseguire una copia
- sapere che metodo è preceduto dall'annotazione
- capire che tipo di valore assume l'annotazione (Pre, Post o entrambe)
- andare a sostituire nella classe generata le condizioni
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.html
Ecco 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:
visualizza il codice CondAnnotProcFinito.html
A questo punto (anche se sono presenti dei warning non è un problema) siamo pronti a configurare il plugin e poi a generare la libreria.
Configurare il Meta-INF services ed il Plugin.xmltorna su
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.TypeGeneratingAnnotationProcessorFactory"> </factory> <factory class="conditionAnnotation.ConditionAnnotationProcessorFactory"> </factory> </factories> </extension> </plugin>
Ora la nostra annotazione è completa...generiamo la libreria per poi poterla usare tranquillamente in altre applicazioni.
Generare la libreriatorna su
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.
Pronti per il testtorna su
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.
Migliorie al progetto torna su
Il progetto è sicuramente una ottima base su cui partire, ma vi sono sicuramente migliorie che possono essere introdotte. Ecco qui un elenco delle principali:
- migliorare i controlli sulle condizioni (esaminare meglio le stringhe "condition").
- gestire la presenza di un return all'interno di un metodo annotato.
- fare in modo che le classi generate (.class) in automatico sostituiscano le classi (.class) del progetto originale.
|