Premières manipulations de modèles

Ce premier TP a pour but de voir en pratique comment créer un méta-modèle et éditer des modèles conformes à ce méta-modèle. Pour cela, nous nous appuyerons sur le framework EMF et son méta-méta-modèle Ecore.

Le méta-modèle ECore est défini ci-dessus :

MM Ecore

Il s'agit concrètement dans cette partie de réaliser le diagramme de classe Client/Compte défini ci-dessus en s'appuyant sur ECore. Les méta-éléments à instancier pour définir ce modèle sont :

Langage de Description de Processus

Nous allons dans ce TP implémenter un DSML (Domain Specific Modeling Language) pour définir des processus : LDP pour Langage de Définition de Processus. Pour cela, nous définirons son méta-modèle en Ecore.

Un processus est formé d'une séquence ordonnée d'activités, avec un début et une fin. A titre d'exemple, la figure ci-dessous donne le processus qui correspond au début du module d'ingénierie des modèles que vous être en train de suivre. Chaque carré aux coins arrondis correspond à une activité, ici à une partie de l'enseignement. Le point de départ du processus est précisé par un rond noir plein tandis que le point d'arrivée est un même rond noir mais entouré en plus d'un cercle. Nous ne développerons pas de syntaxe graphique concrète pour ce DSML mais nous pourrons définir un tel modèle de manière abstraite.





La figure ci-dessous représente le méta-modèle de LDP. Il contient un méta-élément Processus qui permet de définir les éléments du processus, à savoir sa liste d'activités et ses deux pseudo-états de Debut et Fin (la méta-classe PseudoEtat étant abstraite), chaque pseudo état référençant une activité (soit l'activité de début, soit la dernière du processus). Une Activite, à l'exception bien sur de la dernière du processus, possède une activité suivante, ce qui permet de définir la séquence d'activités du processus. De même, une activité, sauf la première, possède une activité précédente. Si l'on est en train d'exécuter ce processus, activiteCourante référence, parmi les activités du processus, l'activité en cours du processus, sinon, elle n'est pas positionnée.





Création d'un méta-modèle Ecore

  1. Créer un nouveau projet EMF vide : File -> New -> Other -> Eclipse Modeling Framework -> Empty EMF Project . Nommer le tp1 pour reprendre directement les extraits de code de ce TP et du suivant.
  2. Dans la hiérarchie du projet, créer un nouveau répertoire que vous nommerez metamodels
  3. Sélectionner metamodels dans la hiérarchie et via le menu contextuel, créer un méta-modèle ECore : New -> Other -> Eclipse Modeling Framework -> Ecore Model . Nommer le fichier LDP.ecore. Un onglet central s'ouvre alors avec 2 lignes de texte qui génèrent des erreurs. Fermer l'onglet, sélectionner dans la hiérarchie le fichier Ecore et via le menu contextuel : Open With -> Sample Ecore Model Editor . Cela ouvre l'éditeur de méta-modèle en mode arbre qui est l'éditeur par défaut.
  4. Dans la fenêtre centrale, ouvrir l'onglet du méta-modèle Ecore créé et remplir les champs du package créé par défaut et sans nom :
  5. Ajouter, via New Child du menu contextuel, tous les éléments un à un et leurs sous-éléments pour créer le méta-modèle. Le méta-modèle à obtenir sous forme d'un arbre est celui-ci et correspond exactement au méta-modèle présenté plus haut :

    modèle compte Ecore

    Attention à bien positionner les cardinalités des références (un " * " se note par la valeur -1) et que les 2 références precedente et suivante soient bien l'opposée l'une de l'autre (réalisant une association bi-directionnelle). De même, attention à bien positionner la caractéristique de composition quand cela est requis. Les deux figures suivantes montrent précisément les propriétés à éditer pour tout cela :

        
  6. Il est possible d'afficher et d'éditer le modèle via une syntaxe graphique (à la UML). Pour cela : sélectionner le nom du modèle Ecore réalisé et choisir Initialize ecore_diagram diagram files dans le menu contextuel. On peut alors modifier le modèle selon l'une et l'autre des vues (arbre ou graphe), les modifications sont prises en compte dans l'autre vue.

Note : les relations de composition sont essentielles pour définir un méta-modèle Ecore et il faut respecter deux contraintes :

Création et édition basique d'un modéle

La création d'un modèle se fait en instanciant le méta-élément racine puis en créant ensuite les éléments qu'il contient :

  1. Dans le méta-modèle, sélectionner Processus et via le menu contextuel, exécuter Create Dynamic Instance. Cela crée un fichier XMI que vous pouvez placer dans le répertoire models de votre projet.
  2. Sélectionner le processus créé dans le fichier XMI et constater via le menu contextuel qu'il y a trois choix dans le menu New Child : Activites, Debut et Fin, chacun correspondant à une des trois associations en mode composition.
  3. Créer un élément début, un élément fin et plusieurs activités. Positionner les propriétés de chacun des éléments pour former un modèle de processus, par exemple celui proposé ci-dessus. Noter que quand vous positionnez l'activité B comme suivante de A, A devient automatiquement l'activité précédente de B. Cela est du à l'aspect bi-directionnelle de l'association.

Validation de modèle et de méta-mdodèle

Lors de l'édition d'un méta-modèle ou d'un modèle, le menu contextuel propose une option Validation. Celle-ci permet de vérifier structurellement qu'un modèle ou un méta-modèle est bien valide. Par exemple pour un méta-modèle, qu'on n'a pas oublié de préciser le type d'un attribut. Pour un modèle, les contraintes d'associations et de cardinalités entre éléments sont vérifiées. Par exemple pour le langage de processus, l'association reference de PseudoEtat a une cardinalité de 1, elle doit donc être forcément positionnée pour tout élément d'une sous-classe concrète de PseudoEtat. Noter également que comme les associations debut et fin de Processusont une cardinalité de 1, l'éditeur de modèle ne permet de créer qu'un exemplaire de chaque à partir de l'instance du processus.

Pour les modèles, il est également possible de rajouter des contraintes OCL sur les méta-éléments. La validation sur un modèle vérifiera alors en plus des contraintes structurelles que les invariants OCL sont bien respectés. Pour ajouter de l'OCL dans un méta-modèle, le plus simple est d'éditer le méta-modèle de manière textuelle vu qu'OCL est un langage textuel. Pour cela, sélectionner le fichier Ecore dans l'arborescence de fichiers de votre projet et l'ouvrir via Open with -> OclInEcore Editor

Vous obtenez alors la vue suivante sur le méta-modèle où on retrouve la même définition des méta-éléments mais dans une syntaxe textuelle :



  1. Ajouter le contenu des deux cadres en rouge qui sont des invariants vérifiant qu'une activité a un nom unique et n'est pas dans un cycle (une activité ne se trouve pas elle-même dans la séquence de ses suivantes) ainsi que le pseudo-état de début référence bien une activité n'ayant pas de précédente. Ajouter aussi le "body" (corps de la méthode) de l'opération pasDansSuivant tel que présenté dans la ligne surlignée en bleu. Vérifier en modifiant votre modèle que les contraintes sont bien vérifiées en lançant la validation.
  2. Ajouter l'invariant dans Fin qui assure que le pseudo-état de fin référence une activité sans suivante.
  3. Ajouter un invariant qui assure qu'il n'existe qu'une seule activité qui n'a pas de suivante et qu'une seule activité qui n'a pas de précédente.
  4. Ecrire le corps de la méthode pasDansSuivant pour qu'elle vérifie qu'une activité n'a pas dans ses suivantes celle qui est passée en paramètre.

Pour plus d'informations sur l'intégration des contraintes OCL en Ecore, voir ce tutoriel. On y trouve notamment la définition de références ou attributs dérivés et de corps d'opération via une expression OCL (permettant de réaliser l'équivalent des "def" OCL).

Visualisateur de fichier XMI en syntaxe abstraite

Des projets tutorés réalisés par Julien Hadba, Bastien Sabos, Nicolas Domec, Romain Heurtevent et Jean-Romain Grangé (et intégrant du code de moi-même et de Nidal Djemam) ont consisté à développer un éditeur permettant d'afficher n'importe quel modèle sous la forme d'un diagramme d'instances à la UML. Cette visualisation est donc indépendante du méta-modèle Ecore considéré et peut être utilisée à la place de l'éditeur sous forme d'arbre.

La restriction courante est que l'outil ne fait actuellement que de l'affichage, on ne peut qu'éditer partiellement le modèle. Cela dit, l'affichage est (généralement) plus clair qu'avec l'éditeur par défaut en liste ; il permet notamment de bien voir les relations entre les éléments.

Pour pouvoir afficher le modèle, l'outil doit disposer du code d'édition dédié au méta-modèle. Ce code est généré automatiquement de la manière suivante :

Dans son état actuel, l'utilisation de l'outil se fait en plaçant l'archive XMIVisualizer.jar quelque part dans votre projet. Créer de préférence un répertoire dédié dans lequel vous placerez l'archive. Ensuite, il faut configurer le fichier MANIFEST.MF du répertoire META_INF du projet :

L'exécution du visualisateur se fait alors en sélectionnant le fichier XMIVisualizer.jar puis via le menu contextuel, exécuter Run As Java Application. Sélectionner ensuite le fichier XMI voulu et une nouvelle fenêtre se lance qui affiche l'ensemble des éléments du modèle, via un placement par défaut. Il est possible de les déplacer à la souris et également de griser certains éléments en double cliquant dessus.

Note : si le fichier XMIVisualizer.jar n'apparait pas dans votre arborescence et qu'il n'est donc pas possible de le sélectionner, passer par le menu général Run -> Run Configurations et créer une nouvelle configuration Application Java.

Manipulation des modèles en Java

Le code du méta-modèle généré dans la partie sur la visualisation en syntaxe abstraite permet également de manipuler le contenu d'un modèle XMI en Java via le framework EMF. Vous trouverez par exemple dans le répertoire src un package LDP contenant les interfaces Java Processus, Activite, PseudoEtat, Debut et Fin. Ouvrez les et constatez que vous y retrouvez des getters et setters pour les attributs et références de votre méta-modèle Ecore. Les deux autres interfaces LDPFactory et LDPPackage serviront au chargement de modèles et à la gestion de leur contenu.

La manipulation de modèles XMI directement en Java est assez complexe, notamment pour charger un modèle. Voici le code générique (indépendamment d'un méta-modèle particulier) qui permet de charger et d'enregistrer un modèle XMI avec les import requis :

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.XMLResource.XMLMap;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLMapImpl;

public void sauverModele(String uri, EObject root) {
   Resource resource = null;
   try {
      URI uriUri = URI.createURI(uri);
      Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());
      resource = (new ResourceSetImpl()).createResource(uriUri);
      resource.getContents().add(root);
      resource.save(null);
   } catch (Exception e) {
      System.err.println("ERREUR sauvegarde du modèle : "+e);
      e.printStackTrace();
   }
}

public Resource chargerModele(String uri, EPackage pack) {
   Resource resource = null;
   try {
      URI uriUri = URI.createURI(uri);
      Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());
      resource = (new ResourceSetImpl()).createResource(uriUri);
      XMLResource.XMLMap xmlMap = new XMLMapImpl();
      xmlMap.setNoNamespacePackage(pack);
      java.util.Map options = new java.util.HashMap();
      options.put(XMLResource.OPTION_XML_MAP, xmlMap);
      resource.load(options);
   }
   catch(Exception e) {
      System.err.println("ERREUR chargement du modèle : "+e);
      e.printStackTrace();
   }
   return resource;
}

Ensuite, pour charger le modèle d'un fichier "Processus.xmi" se trouvant dans le répertoire "models" du projet courant, on exécutera le code suivant qui va récupérer l'instance (supposée unique) d'un processus :

Resource resource = chargerModele("models/Processus.xmi", LDPPackage.eINSTANCE);
if (resource == null) System.err.println(" Erreur de chargement du modèle");

TreeIterator it = resource.getAllContents();

Processus proc = null;
while(it.hasNext()) {
   EObject obj = it.next();
   if (obj instanceof Processus) {
      proc = (Processus)obj;
      break;
   }
}

Pour sauver une instance "proc" de Processus dans un fichier "Processus2.xmi" du répertoire "models", on exécutera le code suivant :

sauverModele("models/Toto.xmi",(EObject)proc);

Une fois un processus chargé, on peut le manipuler, récupérer les éléments lui étant associés et changer les propriétés (attributs et associations) des éléments via l'appel des getter et setter dédiés. Si vous avez besoin de créer des instances de nouveaux éléments, il faut passer par la factory générée pour le méta-modèle. Par exemple, le code suivant crée une nouvelle instance de Activite :

Activite act = LDPFactory.eINSTANCE.createActivite();

  1. Créer un programme Java qui affiche dans la console le contenu d'un modèle. Ne pas oublier de marquer l'éventuelle activité courante du processus.
  2. Ajouter dans votre programme une méthode qui "exécute" le modèle pas-à-pas, c'est-à-dire qui modifie l'activité courante en la passant à l'activité suivante. Sauver le modèle après chaque pas d'exécution. Regarder le contenu des fichiers XMI générés pour constater que l'activité courante est positionnée ou modifiée.
  3. Ajouter dans votre programme une méthode qui prend en paramètre deux noms et dont le but est de rajouter une activité dans la séquence du processus. Le premier nom est celui d'une activité existante et le second celui de l'activité à ajouter après celle existante.