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-méta-modèle ECore est défini ci-dessus :
Il s'agit concrètement dans cette partie de réaliser le méta-modèle défini ci-dessous en s'appuyant sur ECore. Les méta-éléments à instancier pour définir ce modèle sont :
EClass
pour définir une classeEAttribute
pour définir un attribut d'une classe (un
attribut est d'un type primitif entier, booléen, chaine...)EReference
pour définir une association entre 2
classesEOperation
pour définir une opération d'une
classeEParameter
pour définir un paramètre d'opérationNous 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.
tp1
pour reprendre directement les extraits de code de
ce TP et du suivant.metamodels
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. platform:/resource/tp1/metamodels/LDP.ecore
si
votre projet s'appelle "tp1" et votre ficher Ecore
"LDP.ecore". Cet URI est l'adressage absolu de votre méta-modèle
au sein de votre workspace Eclipse.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 :Note : les relations de composition sont essentielles pour définir un méta-modèle Ecore et il faut respecter deux contraintes :
Processus
qui contiendra toutes les
activités et les deux pseudo-états du processus.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 :
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.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 Processus
ont 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 :
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.Fin
qui assure que le
pseudo-état de fin référence une activité sans suivante.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).
Pour un méta-modèle Ecore quelconque, EMF peut générer un ensemble de code Java permettant d'écrire du code manipulant des modèles conformes à ce méta-modèle. Pour cela, il faut :
Le code est généré dans le répertoire src
. On y
trouvera par exemple 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.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
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();
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.