Manipulation de modèles avec Kermeta

Kermeta est un atelier de méta-modélisation développé par l'équipe Triskell de l'IRISA. Kermeta est utilisable sous forme d'un plugin Eclipse.

En quelques mots, Kermeta permet de définir des méta-modèles via sa syntaxe propre ou en réutilisant des méta-modèles Ecore et possède un langage d'action permettant de manipuler des modèles conformes à ces méta-modèles. Kermeta intégre également une approche par contrat, permettant de définir des invariants structurels ainsi que des pré et postconditions sur les opérations. Dans ce module, pour diverses raisons, nous utiliserons Kermeta dans sa première version (version 1.4.1).

Kermeta est un langage orienté-modèle. Il intègre donc des principes et des primitives dédiés à la manipulation de modèles similaires à ce qu'on peut trouver en OCL, ce qui rend la programmation de manipulation de modèles plus simple qu'en Java via EMF. Par exemple, la navigation sur un modèle se fait en suivant directement les associations et attributs, comme en OCL, évitant ainsi la lourdeur de l'enchainement de getter et setter en Java. De plus, pour les collections, Kermeta offre un ensemble de primitives, permettant de parcourir aisément tous les éléments d'une collection, de vérifier si un élément respectant une contrainte existe dans la collection, de récupérer une sous-collection via un filtre, etc. Kermeta offre également une approche par aspect, permettant d'étendre un méta-modèl existant (ajout de structure ou d'opérations) à un méta-modèle existant ce qui permet d'implémenter de manière efficace et élégante des manipulations de modèles. Au delà de tout cela, Kermeta offre toute la puissance et l'expressivité d'un langage de programmation classique. Sa syntaxe est basée sur celle du langage Eiffel à qui il reprend aussi la conception par contrat.

Dans ce TP nous allons refaire les manipulations de modèles du TP1 qui avaient été implementées en Java.

Lien vers le manuel de référence de Kermeta v1

Un programme Kermeta à trou

Pour écrire du Kermeta, il suffit de créer un fichier Kermeta via l'assistant Eclipse dédié. Ce fichier Kermeta peut-être aussi bien créé dans un projet Kermeta ou dans n'importe quel projet sous Eclipse. Ici, on créera donc un fichier Kermeta dans votre projet du TP1 (dans un répertoire dédié par exemple). Pour créer un fichier Kermeta, exécuter New -> Other -> Kermeta -> Kermeta -> Kermeta File. Ensuite, pour lancer le programme implémenté, il suffit de sélectionner le fichier Kermeta puis via le menu contextuel de lancer Run As -> Run As Kermeta Application.

Créer un fichier Kermeta et placez-y le code suivant :

// précise où se trouve l'opération "main" (son nom, sa classe et son package)
@mainClass "LDP::Main"
@mainOperation "main"

// on se place dans le même package que celui du méta-modèle, ça permettra de l'étendre
package LDP;

require kermeta

// importation du méta-modèle Ecore
require "platform:/resource/tp1/metamodels/LDP.ecore"

// nom du package du méta-modèle, éviter de l'utiliser pour préfixer chaque nom de méta-classe
using LDP

using kermeta::persistence
using kermeta::standard
using kermeta::exceptions

// un aspect qui permet d'étendre la classe Processus du méta-modèle pour y ajouter trois opérations
@aspect "true"
class Processus {
   operation executerProchaineActivite() : Void is do
      // à implémenter : passe à la prochaine activité
   end
   
   operation executerProcessus() : Void is do
      // à implémenter : exécute le processus pas à pas et l'enregistre à chaque pas
   end
   
   operation ajouterActivite(actExistante : String, actAjoutee : String)
   pre paramValides is do
      true // à remplacer par la vraie contrainte à vérifier
   end
   is do
      // à implémenter
   end
}

// une classe utilitaire avec des méthodes de chargement, sauvegarde et affichage de modèle
class Util {

   operation chargerModele(nom : String) : Processus is do
      var repository : EMFRepository init EMFRepository.new
      var resource : EMFResource
      var proc : Processus
      resource ?= repository.createResource(nom, "platform:/resource/tp1/metamodels/LDP.ecore")
      resource.load
      proc ?= resource.one
      result := proc
   end
   
   operation enregistrerModele(nom : String, proc : Processus) : Void is do
      var repository : EMFRepository init EMFRepository.new
      var resource : EMFResource
      resource ?= repository.createResource(nom, "platform:/resource/tp1/metamodels/LDP.ecore")
      resource.add(proc)
      resource.save
   end
   
   operation afficherProcessus(proc : Processus) : Void is do
      var act : Activite init proc.debut.~reference
      stdio.writeln(" Processus : ")
      from var fini : Boolean init false
      until fini
      loop
         stdio.write(" - "+act.nom)
         if act == proc.activiteCourante then stdio.writeln(" [*]")
         else stdio.writeln("")
         end
         fini := act.suivante.isVoid
         if not fini then act := act.suivante end
      end
   end
}

// la classe Main qui contient l'opération à exécuter au lancement du programme Kermeta
class Main
{
   operation main() : Void is do
   
      // instanciation de la classe utilitaire
      var util : Util init Util.new
      
      // chargement du modèle du fichier "Processus.xmi"
      var p : Processus
      p := util.chargerModele("/models/Processus.xmi")
      
      // affichage du modèle
      util.afficherProcessus(p)
      
      // ajout d'une activité avec vérification de la précondition et gestion de son non respect
      do
         p.ajouterActivite("TP Kermeta", "TD metamodelisation")
      rescue (err : ConstraintViolatedPre)
         stdio.writeln("** Violation precondition **")
         stdio.writeln(err.message)
      end
   
      // affichage puis enregistrement du modèle (possiblement) modifié
      util.afficherProcessus(p)
      util.enregistrerModele("/models/Processus2.xmi", p)
   end
}

Implémentation d'une opération d'ajout d'activité avec précondition

Implémenter l'opération ajouterActivite qui a été ajoutée par aspect à la classe Processus. La précondition vérifiera qu'il existe bien une activité dont le nom est dans actExistante et qu'il n'existe pas une activité dont le nom est dans actAjoutee.

La primitive "exists" de Kermeta vous sera utile ainsi que de savoir qu'on compare le contenu de deux chaines avec "equals".

Note : la vérification des contraintes (ici la précondition de l'opération) ne se fait que si le programme a été lancé en mode Run As -> Run as Kermeta Constrained Application.

Implémentation d'une opération d'exécution du modèle

Implémenter l'opération executerProcessus qui exécute pas à pas un processus en enregistrant à chaque pas le modèle dans un nouveau fichier. Cette opération se basera sur l'opération executerProchaineActivite qui passe à l'activité suivante (soit réalise un pas du processus) et qui est également à implémenter. Ces deux opérations ont été ajoutées par aspect à la classe Processus.