Génération de code avec Acceleo

Acceleo est un générateur de texte à partir de modèles (transformation "model to text") que l'on va utiliser pour générer du code Java à partir de nos modèles de diagrammes de classes simplifiés.

Acceleo utilise des templates qui permettent de parcourir le modèle et de générer du texte correspondant à du contenu dans le modèle. Pour cela, il se base sur OCL pour sélectionner des éléments du modèle ou calculer des valeurs à placer dans le texte.

Création d'un projet Acceleo

La mise en place d'un projet Acceleo nécessite quelques configurations techniques pour faire le lien avec le projet EMF où se trouve le méta-modèle du DSL considéré avec son code Java/EMF généré. Voici les étapes à réaliser :

  1. Dans le projet "ClassDiagram" du TP sur les transformations de modèles, créez un package Java nommé "generate". C'est dans ce package que nous allons générer le code Java à partir des modèles de diagramme de classes. Comme il se trouve dans le répertoire de sources Java du projet, cela permettra directement de savoir si le code Java généré est valide car il sera automatiquement compilé après sa génération.
  2. Créez un nouveau projet Acceleo : New -> Acceleo Model To Text -> Acceleo Projet. Choisissez comme nom "acceleo.ClassDiagram", cliquez sur Next et dans la fenêtre suivante :
  3. Cliquez sur Finish pour créer le projet. Dans le projet "acceelo.ClassDiagram", il y a un package Java dans "src" qui s'appelle "acceleo.ClassDiagram.main". C'est là que se trouve le code de la génération Acceleo, dans le fichier "GenerateClassDiagram.mtl" qui s'appuie sur une classe "GenerateClassDiagram.java" dans le même package. Ouvrez le fichier d'extention ".mtl" pour afficher la transformation qui pour l'instant ne contient rien.
  4. Le fichier "GenerateClassDiagram.java" est auto-généré à chaque fois que l'on édite la transformation Acceleo. Néanmoins, il va falloir le modifier pour faire le lien avec les classes Java/EMF générées à partir du méta-modèle ClassDiagram.ecore. Pour cela, éditez ce fichier et modifiez la méthode "registerPackages" comme suit en prenant bien soin de rajouter un "NOT" derrière l'annotation @generated pour ne pas écraser à chaque génération le code que vous avez modifié. Les deux bouts de code à rajouter sont en bleu :

    /**
     * This can be used to update the resource set's package registry with all needed EPackages.
     *
     * @param resourceSet The resource set which registry has to be updated.
     * @generated NOT
     */
    @Override
    public void registerPackages(ResourceSet resourceSet) {
       super.registerPackages(resourceSet);
       EPackage.Registry.INSTANCE.put("platform:/resource/ClassDiagram/model/ClassDiagram.ecore", ClassDiagramPackage.eINSTANCE);
       ...

  5. Une fois l'ajout effectué, corrigez la première erreur de compilation en rajoutant l'import de org.eclipse.emf.ecore.EPackage dans la classe. Pour corriger la seconde erreur correspondant à l'accès au package de ClassDiagram, il faut modifier les propriétés du projet pour référencer les classes Java/EMF du méta-modèle de l'autre projet. Dans le répertoire "META-INF" du projet Acceleo, ouvrez le fichier "MANIFEST.MF", affichez l'onglet "Dependencies" et dans la liste "Imported Packages", ajoutez, en cliquant sur "Add...", les 3 packages de ClassDiagram. Vous devez avoir ceci :

    Import de ClassDiagram
    dans le projet Acceleo

    Sauvegardez le fichier MANIFEST.MF, revenez dans l'édition de la classe "GenerateClassDiagram.java" et rajoutez l'import du package ClassDiagram.ClassDiagramPackage. Il n'y a normalement plus d'erreurs dans votre projet.
  6. En l'état, votre générateur de code ne fait rien. Editez le fichier "generationClassDiagram.mtl" et rajoutez les lignes suivantes après la ligne [comment @main/] :

    [for (cl : Class | aModelBase.allClasses)]
    [file (cl.name.concat('.java'), false, 'UTF-8')]
    package generate;

    public class [ cl.name /] {
    }
    [/file]
    [/for]

    Le générateur Acceleo va parcourir l'ensemble des classes du modèle et pour chacune d'entre-elle, va générer un fichier Java qui contient uniquement la déclaration de la classe vide.
  7. La dernière étape est de lancer la génération définie. Pour cela, sélectionnez le fichier ".mtl" dans l'arborescence du projet puis Clic droit -> Run as -> Launch Acceleo Application. Commme il n'existe pas de configuration d'exécution associée à votre transformation, Eclipse vous ouvre la fenêtre de configuration de cette exécution (pour retrouver et modifier ultérieuemen la configuration : clic droit -> Run as -> Run configurations...). Dans le champ "Model:", sélectionnez un modèle XMI de diagramme de classes et dans le champ "Target:", précisez le répertoire "generate" de "src" du projet ClassDiagram, comme ceci :

    Configuration
d'exécution Acceleo

    Cliquez sur Apply pour sauvegarder la configuration puis sur Run pour l'exécuter. Vérifiez que vous avez bien les classes Java de votre modèle générées dans le packcage "generate" du projet ClassDiagram.

Définition de la génération de code

Vous avez désormais un projet Acceleo fonctionnel qui sait traiter des modèles de diagramme de classes mais ne fait pas grand chose. Vous devez rajouter les règles qui permettent de générer les interfaces, les attributs, les méthodes (avec une valeur de retour valide pour compiler), les constructeurs, les accesseurs ...

La documentation des fonctionnalités d'Acceleo se trouve sur cette page : https://wiki.eclipse.org/Acceleo/User_Guide.

Le template marqué par @main est celui qui est exécuté au lancement de la génération Acceleo. Ce template peut faire appel à d'autres templates qui ont des paramètres ou à des requêtes (query) écrites en OCL et qui retournent une valeur calculée sur le contenu du modèle. Dans un template, il y a des constructeurs algorithmiques comme la boucle [for] pour parcourir une collection, les tests [if] ou la déclaration de variable comme en OCL avec [let].

Voici par exemple une query OCL qui retourne le nom du type Java d'un type du méta-modèle de ClassDiagram :

[comment Retourne le type Java d'un type du modèle /]
[query public javaType(type : Type) : String =
if (type.oclIsTypeOf(VoidType)) then 'void'
else if (type.oclIsTypeOf(StringType)) then 'String'
else if (type.oclIsTypeOf(IntegerType)) then 'int'
else if (type.oclIsTypeOf(BooleanType)) then 'boolean'
else type.name
endif
endif
endif
endif
/]

Voici un template qui permet de définir la liste des interfaces d'une classe via la construction implements lors de la définition d'une classe (notez le parcours de la collection qui n'écrit le mot "implements" que si la collection n'est pas vide et qui rajoute une virgule automatiquement entre les éléments sauf pour le dernier de la collection) :

[comment Template pour générer la liste des interfaces implémentées par une classe /]
[template public interfaceList(cl : Class)]
[for (inter : Interface | cl.interfaces) before('implements ') separator (', ')][inter.name/][/for]
[/template]

En utilisant ce template et la query OCL, on peut enrichir la génération d'une classe pour y ajouter la liste des interfaces implémentées et la liste des attributs :

[for (cl : Class | aModelBase.allClasses)]
[file (cl.name.concat('.java'), false, 'UTF-8')]
package generate;

public class [ cl.name /] [interfaceList(cl)/] {

[for (att : Attribute | cl.attributes) ]
   private [javaType(att.type)/] [att.name/];

[/for]
}
[/file]
[/for]


Eric Cariou, dernière modification : 10/02/24