L’objet du niveau 3 est de découvrir l’API qui permet de faire ces choses que seuls les processeurs d’annotations peuvent faire: lever de nouvelles erreurs ou avertissements de compilation et/ou créer de nouveaux fichiers sources, classes ou ressources pendant la compilation.
L’objectif de cet exercice est de créer un processor qui lèvera une erreur de compilation s’il existe un champs annotée avec javax.inject.Inject
.
Dans le répertoire exo1
, vous trouverez deux projets Maven:
-
dans le sous-répertoire
processor
, le projet comporte le processorInjectedPropertyProcessor
et un test unitaire pour chacune des étapes ci-dessous -
dans le sous-répertoire
subject
, le projet comporte quelques sources utilisant l’annotation@Inject
ce qui vous permettra de constater le comportement deInjectedPropertyProcessor
Ainsi, si vous compilez le projet processor
sans les tests unitaires ((cd processor && mvn clean install -DskipTests)
) et qu’ensuite vous compilez le projet subject
((cd subject && mvn clean install)
), vous constaterez que ce dernier compile sans problème.
Comme il a été vu au niveau 1, le type de diagnostic Diagnostic.Kind.ERROR
passé en argument des méthodes javax.annotation.processing.Messager#printMessage
permet de remonter un diagnostique qui se traduira par une erreur de compilation et l’affichage d’un message dans les logs.
L’objectif de cette étape est d’implémenter la méthode process
du processor fr.devoxx.niveau3.exo1.InjectedPropertyProcessor
de sorte que la compilation s’arrète avec le message "Fields annotated with @Inject are not allowed".
Important
|
Dans la classe Pour cela, vous devrez:
|
Si votre implémentation est correcte, le test unitaire Etape1Test
dans le projet processor
sera passant. Si ensuite vous compilez le projet subject
, la compilation échouera avec le message suivant:
[ERROR] Fields annotated with @Inject are not allowed
Comme il est frustrant de voir s’afficher une erreur de compilation sans aucune indication de l’endroit où se trouve l’erreur !
Important
|
Dans la classe |
Pour vérifier votre implémentation, activez les tests de la classe Etape2Test
(mettez à true
le membre enable
des annotations @Test
) et constatez que celui-ci est passant.
Si ensuite vous compilez le projet subject
, la compilation échouera avec le message suivant (indique une erreur à la ligne 12 de la classe SomeService
) :
[ERROR] [some path]/niveau_3/exo1/subject/src/main/java/fr/devoxx/niveau3/exo1/SomeService.java:[12,23] Fields annotated with @Inject are not allowed
Il est possible, très simplement, d’être encore plus précis. Si l’erreur provient d’une annotation (comme c’est le cas ici), il est possible d’indiquer la ligne sur laquelle se trouve cette annotation.
Tip
|
Pour trouver l' |
Important
|
Dans la classe |
Pour vérifier votre implémentation, activez les tests de la classe Etape3Test
(mettez à true
le membre enable
des annotations @Test
) et constatez que celui-ci est passant.
Si ensuite vous compilez le projet subject
, la compilation échouera avec le message suivant (indique une erreur à la ligne 11, cad. celle de l’annotation) :
[ERROR] [some path]/niveau_3/exo1/subject/src/main/java/fr/devoxx/niveau3/exo1/SomeService.java:[11,3] Fields annotated with @Inject are not allowed
L’objectif de cet exercice est de générer un fichier dans lequel figurera le prénom des personnages décédés du livre Game of Thrones.
Dans le répertoire exo2
, vous trouverez un projet Maven constitué de deux sous modules:
-
dans le module
processor
se trouvent l’annotationCharacter
et le processorUndertakerProcessor
-
dans le module
subject
se trouvent les membres des familles Baratheon et Stark. Chaque personnage est représenté par une classe, dont le nom est le prénom du personnage, annotée avec l’annotationCharacter
.
L’annotation Character
définie un membre dead
de type boolean
qui permet de savoir si le personnage est mort (ou pas… encore).
La première étape de l’exercice consiste à lire les informations contenues dans les sources dans le contexte d’un processor.
Les deux informations qui nous intéressent: la valeur du membre dead
de l’annotation Character
et le nom de la classe annotée.
Important
|
Dans la classe Pour cela, vous devrez:
Si votre implémentation est correcte, le test unitaire |
La seconde étape de l’exercice se consacre à la génération du fichier dead_characters.txt
dans le package fr.devoxx.niveau3.exo2
.
Dans le cadre du traitement d’annotations, l’écriture de fichiers (fichier source, classe ou ressource quelconque) se fait par le biais de l’interface javax.annotation.processing.Filer
. Une instance peut être récupérée via la méthode getFiler()
de l’instance de javax.annotation.processing.ProcessingEnvironment
fournie en paramètre de la méthode init(ProcessingEnvironment)
du processor.
Important
|
Dans la classe Pour cela, vous devrez:
Si votre implémentation est correcte, le test unitaire |
Le but du jeu ici consiste à prendre en main l’API d’écriture de sources Java avec l’API JavaPoet dans le contexte d’un processor d'`@Erbfeind` que l’on va également écrire.
Important
|
|
À ce stade, "What’s going on here?" aurait du apparaître dans les logs affichés lors de la compilation.
Ou peut-être pas. Remémorez-vous l’ensemble des déclarations auxquelles doit procéder un processor.
Vous ne voyez toujours pas ?
Nous avons prétendu jusqu’ici qu’il s’agissait d’un processor d'`@Erbfeind` mais le processor l’a-t-il correctement déclaré ?
Important
|
Complétez la classe |
Il est temps de générer à tout berzingue avec JavaPoet.
L’idée à cette étape est de générer la ou les classes définies par l’annotation @Erbfeind
.
En d’autres termes, si une classe com.acme.A
est annotée @Erbfeind({"B", "C"})
, alors les classes com.acme.B
et com.acme.C
, pour le moment vides, doivent être générées.
N’hésitez pas à regarder le répertoire src/test/resources
du processor pour bien comprendre ce qui est demandé.
Important
|
Complétez la classe |
Bon, bon, bon, c’est bien sympa de générer des classes vides, mais c’est un peu limité, non ?
Corsons un peu l’affaire avec cette difficulté supplémentaire : chaque "Erbfeind" généré doit lui-même être annoté avec son Erbfeind correspondant.
Pour reprendre l’exemple précédent, si com.acme.A
est annotée @Erbfeind({"B", "C"})
alors les classes com.acme.B
et com.acme.C
, maintenant correctement générées, doivent être annotées @Erbfeind("A")
.
Important
|
|
Important
|
Complétez Petite subtilité : si vous vous référez à la documentation de JavaPoet, vous devez générer comme valeur pour Constatez le problème ;) |
À ce stade, vous devriez obtenir une sortie similaire à la suivante :
What's going on here?
What's going on here?
java.lang.RuntimeException: java.lang.RuntimeException: Attempt to recreate a file for type i.can.haz.generation.A
at com.sun.tools.javac.main.Main.compile(Main.java:553)
at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138)
[...]
Avant de lire la suite, prenez le temps de réfléchir à la cause du problème.
Comme l’indique la présence double du log "What’s going on here?", ErbfeindGenerator
a visiblement été invoqué deux fois avant de crasher lamentablement (vous pouvez activer les logs de compilation pour avoir plus de détails).
Le premier round de compilation est logique : Etape3Test
inclut d’entrée une classe A
annotée Erbfeind
. À cette étape, le processor génère donc les classes B
et C
, maintenant annotées elles aussi @Erbfeind
.
Le second round est donc simplement initiée parce qu’il est nécessaire de traiter les classes B
et C
puisqu’elles sont annotées Erbfeind
. C’est là que le bât blesse : ErbfeindGenerator
va maintenant essayer de générer le fichier source de A
. Problème de taille : ce fichier existe déjà et la spécification interdit formellement de modifier (écraser…) des classes existantes.
Important
|
Corrigez la méthode write d’ErbfeindGenerator. Indice : si une classe donnée existe, alors c’est que l’on peut récupérer son |
Vous pouvez maintenant compiler le projet subject
et admirer les sources générées (même Scala
compile!).