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. Choisissez la représentation Entities in a Class Diagram et dans la palette à droite, cliquez sur Add dans Existing elements. Sélectionner toutes les classes et valider. Vous pouvez ensuite modifier la disposition des classes et de leurs relations.

    On peut alors modifier le modèle selon l'une et l'autre des vues (arbre ou graphe UML) mais attention, c'est le même ficher Ecore derrière, il donc faut sauvegarder une vue avant d'éditer par une autre sinon il y aura des incohérences de contenu.

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-modè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).

Manipulation des modèles en Java

Le framework EMF permet dans un programme Java de manipuler des modèles définis à partir d'un méta-modèle Ecore. Pour cela, il faut générer le code des classes Java qui correspondent aux méta-classes d'un méta-modèle Ecore ainsi que quelques classes utilitaires. Cela se fait de la manière suivante :

Une fois ce code généré pour le méta-modèle LDP, vous trouverez 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 concernant le chargement et la sauvegarde d'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 imports 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 LDP 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/Processus2.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.

Pour vous aider à implémenter tout cela, vous pouvez reprendre le fichier LDPManipulation.java qui contient tout le code de chargement/enregistrement des modèles et les signatures des méthodes à implémenter.