Tutoriel sur la création et l'instanciation de modèles avec Eclipse Modeling Framework (EMF)

Mickael Baron

Le but de cette série d'exercices est d'apprendre à manipuler le framework de modélisation d'Eclipse appelé EMF. Nous couvrirons tous les aspects liés aux développements dirigés par les modèles avec les briques logiciels fournies par la plateforme Eclipse. La version Eclipse avec le package modeling sera utilisée à cet effet.

- conception de modèles EMF ;

- génération de code Java ;

- manipulation d'API EMF pour instancier le modèle ;

- manipulation du métamodèle Ecore ;

- sauvegarde et chargement de fichiers XMI.

Les sources des exemples sont disponibles ici.

Une version sans illustration est disponible sur Github : https://github.com/mickaelbaron/emf-tutorial.

Pour réagir à cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel sur la création et l'instanciation de modèles avec Eclipse Modeling Framework (EMF)

- Auteur : Mickael Baron

- Parution : 17 juin 2013

- Mise à jour : 18 novembre 2014

- Public visé :

- Licence : Le contenu de cet article est rédigé par Mickael BARON et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Introduction

- I-A. Connaissances requises

- I-B. Prérequis logiciel

- II. Exercice 1 : Création du modèle EMF d'un carnet d'adresses

- II-A. But

- II-B. Description

- II-C. Étapes à suivre

- III. Exercice 2 : Génération de codes Java

- III-A. But

- III-B. Description

- III-C. Etapes à suivre

- IV. Exercice 3 : Création d'instances via l'éditeur généré

- IV-A. But

- IV-B. Description

- IV-C. Étapes à suivre

- V. Exercice 4 : Création d'instances via l'API EMF : EarlyBinding

- V-A. But

- V-B. Description

- V-C. Étapes à suivre

- VI. Exercice 5 : Manipulation du métamodèle Ecore : LateBinding

- VI-A. But

- VI-B. Description

- VI-C. Interroger le métamodèle Ecore

- VI-D. Création d'instances via le métamodèle Ecore

- VII. Conclusion et remerciements

I - Introduction

Cet article se présente sous la forme d'un atelier composé de cinq exercices guidés :

- un premier exercice s'intéresse à la construction d'un modèle EMF ;

- un deuxième exercice montre comment générer du code Java et des outils d'assistance à partir du modèle EMF ;

- le troisième exercice explique comment instancier le modèle EMF à partir d'un éditeur graphique généré ;

- le quatrième exercice présente l'instanciation du modèle EMF en utilisant directement les codes Java générés (technique appelée EarlyBinding) ;

- le dernier exercice présente l'instanciation du modèle EMF en utilisant le métamodèle Ecore (technique appelée LateBinding).

I-A - Connaissances requises

Cet article suppose que vous possédez quelques notions théoriques sur les aspects liés à l'Ingénierie Dirigée par les Modèles (IDM). Vous pouvez parcourir cet article pour vous familiariser sur les concepts de modèles et des métamodèles.

De même sur les aspects pratiques, je vous recommande de parcourir mon support de cours consacré aux technologies EMF dédiées à l'IDM.

Enfin, quelques notions sur la programmation Java et sur la construction de plugins avec la plate-forme Eclipse seraient un plus.

I-B - Prérequis logiciel

Pour reproduire les exemples de cet article, vous aurez besoin d'une version Eclipse contenant la plupart des plugins dédiés à la modélisation. Vous pourrez ainsi vous baser sur la distribution Eclipse Modeling Tools disponible sur le site de la fondation Eclipse.

II - Exercice 1 : Création du modèle EMF d'un carnet d'adresses

II-A - But

Trois principaux objectifs seront visés :

- démarrer un projet EMF ;

- créer un modèle EMF avec les outils d'Eclipse ;

- manipuler les différents éditeurs.

II-B - Description

L'exemple qui illustre cet article est un carnet d'adresses. Ce dernier est identifié par un nom et contient une liste de personnes (contains). Une personne est identifiée par un prénom, un nom et un âge. Une personne contient obligatoirement une adresse (location). Une adresse est identifiée par un numéro et un nom de rue.

Le modèle UML donné ci-dessous représente graphiquement la modélisation attendue pour cet exercice. Nous nous intéressons ici à créer le modèle UML présenté via les outils d'Eclipse.

Modélisation UML d'un carnet d'adresses

II-C - Étapes à suivre

- Démarrer l'environnement de développement Eclipse contenant les plugins de modélisation puis créer un nouveau Workspace (workspaceEMF) afin de disposer d'un répertoire spécifique à la modélisation.

- Pour afficher les vues Eclipse spécifiques à la modélisation EMF, ouvrir la perspective Ecore.

- Créer un nouveau projet EMF vide (File -> New -> Project … -> Eclipse Modeling Framework - > Empty EMF Project) et nommer le projet eclipse.emf.addressbook.

- Sélectionner le répertoire model et créer un diagramme Ecore (Ecore Diagram). Nommer le fichier ecore addressbook.ecore.

- Construire les trois classes, définir tous les attributs et créer les associations entre les classes. Veuillez respecter les contraintes de cardinalités exprimées sur le modèle UML précédent. Aidez-vous de la vue Properties afin de spécifier les cardinalités voulues.

Vous pouvez visualiser votre modèle sous différentes représentations via l'utilisation d'éditeurs adaptés : OCLinEcore (Ecore) Editor et Sample Ecore Model Editor. Bien entendu, cette liste n'est pas exhaustive et d'autres éditeurs sont également disponibles. Il suffira juste d'installer les plugins adéquates si nécessaires.

Visualisation du modèle EMF Carnet d'adresses via l'éditeur OCLinEcore

Visualisation du modèle EMF Carnet d'adresses via l'éditeur Sample Ecore Model Editor

- Visualiser finalement votre modèle au format XML, vous remarquerez qu'il s'agit d'un fichier XMI dont les données correspondent à des instances du métamodèle Ecore. Nous reviendrons sur cette notion dans les prochaines sections.

1. <?xml version="1.0" encoding="UTF-8"?>

2. <ecore:EPackage xmi:version="2.0"

3. xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

4. xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="addressbook"

5. nsURI="http://addressbook/1.0" nsPrefix="addressbook">

6. <eClassifiers xsi:type="ecore:EClass" name="AddressBook">

7. <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>

8. <eStructuralFeatures xsi:type="ecore:EReference" name="contains" upperBound="-1"

9. eType="#//Person" containment="true"/>

10. </eClassifiers>

11. <eClassifiers xsi:type="ecore:EClass" name="Person">

12. <eOperations name="display" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>

13. <eStructuralFeatures xsi:type="ecore:EAttribute" name="familyName" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>

14. <eStructuralFeatures xsi:type="ecore:EAttribute" name="firstName" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>

15. <eStructuralFeatures xsi:type="ecore:EAttribute" name="age" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>

16. <eStructuralFeatures xsi:type="ecore:EReference" name="location" lowerBound="1"

17. eType="#//Address" containment="true"/>

18. <eStructuralFeatures xsi:type="ecore:EAttribute" name="identifier" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"

19. changeable="false" volatile="true" transient="true" derived="true"/>

20. </eClassifiers>

21. <eClassifiers xsi:type="ecore:EClass" name="Address">

22. <eStructuralFeatures xsi:type="ecore:EAttribute" name="number" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>

23. <eStructuralFeatures xsi:type="ecore:EAttribute" name="street" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>

24. </eClassifiers>

25. </ecore:EPackage>

III - Exercice 2 : Génération de codes Java

III-A - But

Quatre principaux objectifs seront visés :

- Créer un modèle de génération à partir d'un Ecore ;

- Paramétrer le modèle de génération de code ;

- Générer un code Java correspondant au modèle EMF ;

- Mettre à jour modèle, re-générer le code et modifier le code généré.

III-B - Description

Nous nous intéressons maintenant à toutes les étapes de génération de code à partir d'un modèle EMF. Nous nous intéressons également aux étapes de re-génération et de protection des codes modifiés par le développeur. Un modèle supplémentaire est requis et dédié à cette tâche. Il contient les informations dédiées uniquement à la génération et qui ne pourraient pas être intégrées au modèle EMF (chemin de génération, package, préfixe…). Ce modèle appelé genmodel est également un modèle EMF et chaque classe du modèle de génération est un décorateur des classes Ecore.

III-C - Etapes à suivre

- Créer un modèle de génération (New -> Other… -> Eclipse Modeling Framework -> EMF Generator Model), sélectionner ensuite le répertoire model du projet eclipse.emf.addressbook puis nommer le fichier addressbook.genmodel.

- Sélectionner ensuite Ecore model comme type de modèle utilisé pour créer ce modèle de génération.

- Sélectionner enfin le fichier addressbook.ecore (à partir de la navigation du Workspace courant Browse Workspace…) puis terminer.

- Modifier le contenu du fichier genmodel pour que le package de génération soit eclipse.emf.addressbook.model (propriétés : Base Package). Utiliser pour cela la vue Properties en modifiant l'attribut Base Package.

- Sélectionner depuis le fichier genmodel le package racine Addressbook et générer le code Java correspondant au modèle (Generate Model Code). Un ensemble de classes Java doivent être générées dans le package eclipse.emf.addressbook.model.addressbook.

- Examiner les classes générées et remarquer le découpage en trois catégories qui font apparaître une programmation par contrats : interfaces, implémentations et classes utilitaires.

Nous décidons par la suite de modifier le modèle de façon à :

- ajouter un attribut dérivé dans Person appelé identifier de type String qui retourne une chaîne de type (firstName + familyName + age),

- ajouter une opération String display() qui se chargera d'effectuer un affichage complet d'une instance de Person.

Le schéma ci-dessous représente graphiquement la modélisation attendue par cette modification.

- Compléter votre modèle EMF (via l'éditeur Ecore Diagram Editing par exemple) de façon à intégrer les modifications demandées. Pour l'attribut identifier, déclarer le Derived, Volatile, Transient et non Changeable.

Cela a pour effet pour l'attribut identifier :

- derived : calculé à partir d'autres attributs,

- volatile : ne génère par l'attribut pour stocker l'état, le corps de la méthode est également laissé à vide,

- transient : ne sera pas sauvegardé,

- changeable : valeur pouvant changer

- Le fichier addressbook.ecore est automatiquement impacté. Toutefois, le fichier genmodel doit être explicitement mis à jour. Sélectionner le fichier addressbook.genmodel puis cliquer sur Reload (via le menu contextuel). Sélectionner ensuite Ecore model et laisser les valeurs par défaut puis valider. Vous remarquerez que les nouveaux attributs ont été ajoutés et que les anciennes valeurs de configuration de génération (Base Package en l'occurrence) n'ont pas été supprimées.

- Impacter la classe eclipse.emf.addressbook.model.addressbook.impl.PersonImpl de façon à implémenter les méthodes getIdentifier() et display(), voir le code ci-dessous.

1. /**

2. * <!-- begin-user-doc -->

3. * <!-- end-user-doc -->

4. * @generated NOT

5. */

6. public String getIdentifier() {

7. return this.getFirstName() + this.getFamilyName() + this.getAge();

8. }

10. /**

11. * <!-- begin-user-doc -->

12. * <!-- end-user-doc -->

13. * @generated NOT

14. */

15. public String display() {

16. StringBuffer sb = new StringBuffer();

17. sb.append("FirstName:").append(this.getFirstName())

18. .append(" FamilyName:").append(this.getFamilyName())

19. .append(" Age:").append(this.getAge())

20. .append(" Address:").append(this.getLocation());

21. return sb.toString();

22. }

Remarquer que l'en-tête des méthodes getIdentifier() et display() contient l'annotation @generated NOT. Cette dernière annotation permet d'empêcher que le code modifié par le développeur soit écrasé par la génération.

- Re-générer les codes Java (Generate Model Code) et s'assurer que le code saisi n'a pas été modifié.

IV - Exercice 3 : Création d'instances via l'éditeur généré

IV-A - But

Quatre principaux objectifs seront visés :

- Générer un éditeur graphique ;

- Exécuter une configuration d'exécution ;

- Créer des instances via l'éditeur généré ;

- Valider des contraintes.

IV-B - Description

Nous allons maintenant générer le code correspondant à un éditeur graphique. Cet éditeur sera utilisé pour créer graphiquement des instances de notre modèle. Nous vérifierons par ailleurs la validité de notre modèle par rapport à un jeu d'instances.

IV-C - Étapes à suivre

- À partir du modèle de génération (genmodel), ouvrir l'éditeur EMF Generator et générer le code de l'éditeur (Generate Edit Code et Generate Editor Code).

Deux plugins doivent être créés (eclipse.emf.addressbook.edit et eclipse.emf.addressbook.editor).

- Passer en perspective Java et créer une configuration d'exécution (Run -> Run Configurations…) à partir d'un type Eclipse Application. Nommer cette configuration AddressBookConfiguration, puis modifier la valeur de son chemin avec cette valeur (${workspace_loc}/runtime-AddressBookConfiguration).

- Ajouter à cette configuration d'exécution les trois plugins (addressbook, edit et editor).

- Décocher Target Platform puis faites Add Required Plug-ins.

- Ajouter enfin le plugin org.eclipse.ui.ide.application et org.eclipse.ui.navigator.resources et faites une nouvelle fois Add Required Plug-ins.

- Exécuter cette configuration d'exécution. Une nouvelle instance d'Eclipse s'exécute en intégrant votre éditeur de modèle de carnet d'adresse.

- Créer un simple projet (File -> New -> Project… -> General -> Project) que vous appellerez AddressBookSampleInstances.

- À partir de cette nouvelle instance, créer une instance du modèle AddressBook (File -> New -> Other… -> Example EMF Model Creation Wizards -> Addressbook Model) que vous appellerez Sample.addressbook. Choisir ensuite Address Book comme modèle objet à créer.

- Construire les instances via l'éditeur associé à votre modèle en s'appuyant sur les instances données ci-dessous.

1. <addressbook:AddressBook ...>

2. <contains familyName="DUPONT" firstName="Raoul" age="37">

3. <location number="1" street="Rue DotNet"/>

4. </contains>

5. <contains familyName="BARON" firstName="Mickael" age="36">

6. <location number="50" street="Place de Java"/>

7. </contains>

8. <contains familyName="SARPOL" firstName="John" age="38">

9. <location number="50" street="Square Express"/>

10. </contains>

11. </addressbook:AddressBook>

- Sélectionner le nœud racine de vos instances et valider ces instances en cliquant sur Validate (via le menu contextuel).

V - Exercice 4 : Création d'instances via l'API EMF : EarlyBinding

V-A - But

Trois principaux objectifs seront visés :

- Créer des instances via l'API EMF ;

- Créer un plugin (fragment) de test ;

- Sauvegarder et charger via l'API EMF des instances via un XMI.

V-B - Description

Nous allons, dans cet exercice, créer des instances d'un modèle de manière programmatique. Dans ce cas les plugins générés précédemment (Edit et Editor) ne seront pas utilisés. Nous utiliserons un plugin spécifique appelé fragment (utilisé pour enrichir un plugin existant) pour créer des classes de tests.

V-C - Étapes à suivre

- Créer un nouveau plugin de type fragment (File -> New -> Other…-> Plug-in Development -> Fragment Project) nommé eclipse.emf.addressbook.test. Choisir comme plugin hôte eclipse.emf.addressbook (créé dans la section II). Une fois le fragment créé, ajouter la dépendance vers le plugin org.junit (4.8.2) (onglet Dependencies quand le fichier MANIFEST.MF est en cours d'édition).

- Créer un package eclipse.emf.addressbook.model.test et créer une classe appelée AddressBookTest.

- Depuis AddressBookTest compléter la méthode de tests createAddressBookTest en s'assurant que les assertions associées soient vraies.

1. public class AddressBookTest {

2. @Test

3. public void createAddressBookTest() {

4. AddressBook createAddressBook = AddressbookFactory.eINSTANCE.createAddressBook();

5. createAddressBook.setName("Mon Carnet d'Adresses");

7. Address createMBAddress = AddressbookFactory.eINSTANCE.createAddress();

8. createMBAddress.setNumber(50);

9. createMBAddress.setStreet("Place de Java");

10. Person mickaelBaron = AddressbookFactory.eINSTANCE.createPerson();

11. mickaelBaron.setAge(36);

12. mickaelBaron.setFamilyName("BARON");

13. mickaelBaron.setFirstName("Mickael");

14. mickaelBaron.setLocation(createMBAddress);

16. Address createDRAddress = AddressbookFactory.eINSTANCE.createAddress();

17. createDRAddress.setNumber(1);

18. createDRAddress.setStreet("Rue DotNet");

19. Person raoulDupont = AddressbookFactory.eINSTANCE.createPerson();

20. raoulDupont.setAge(37);

21. raoulDupont.setFamilyName("DUPONT");

22. raoulDupont.setFirstName("Raoul");

23. raoulDupont.setLocation(createDRAddress);

25. Address createSJAddress = AddressbookFactory.eINSTANCE.createAddress();

26. createSJAddress.setNumber(50);

27. createSJAddress.setStreet("Square Express");

28. Person johnSarpol = AddressbookFactory.eINSTANCE.createPerson();

29. johnSarpol.setAge(38);

30. johnSarpol.setFamilyName("SARPOL");

31. johnSarpol.setFirstName("John");

32. johnSarpol.setLocation(createSJAddress);

34. createAddressBook.getContains().add(mickaelBaron);

35. createAddressBook.getContains().add(raoulDupont);

36. createAddressBook.getContains().add(johnSarpol);

38. Assert.assertEquals(3, createAddressBook.getContains().size());

39. Assert.assertEquals("Mon Carnet d'Adresses", createAddressBook.getName());

40. Assert.assertEquals("BARON", mickaelBaron.getFamilyName());

41. Assert.assertEquals("Raoul", raoulDupont.getFirstName());

42. Assert.assertEquals("JohnSARPOL38", johnSarpol.getIdentifier());

43. ...

44. }

45. }

Les instances des classes sont obtenues par l'utilisation de la fabrique AddressbookFactory (ligne 4). Le reste des modifications ne vous sera pas étranger.

Pour l'exécution du test unitaire, il doit se faire obligatoirement dans un environnement de plugins (Run As JUnit Plug-in Test).

On s'intéresse maintenant à sauvegarder et charger le contenu des instances depuis un fichier XMI.

- Compléter la méthode de tests de manière à sauvegarder les instances créées précédemment (voir code ci-dessous). Vous adapterez le chemin de sauvegarde du fichier APISample.addressbook (ligne 6) en rapport avec celui utilisé pour sauvegarder Sample.addressbook. Le fichier d'instances sera stocké dans le répertoire utilisé par la configuration d'exécution de l'exercice 3.

1. ...

2. ResourceSet resourceSet = new ResourceSetImpl();

3. resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("addressbook",

4. new XMIResourceFactoryImpl());

6. final String apiSamplePath = "D://workspaceEMF_BARON_Mickael//runtime-

7. AddressBookConfiguration//AddressBookSampleInstances//";

8. URI uri = URI.createURI("file:/" + apiSamplePath + "APISample.addressbook");

9. Resource resource = resourceSet.createResource(uri);

10. resource.getContents().add(createAddressBook);

11. try {

12. resource.save(null);

13. } catch (IOException e) {

14. e.printStackTrace();

15. }

16. Assert.assertTrue(new File(apiSamplePath + "APISample.addressbook").exists());

17. ...

18. }

19. }

Faites attention à importer le bon package pour la classe URI (import org.eclipse.emf.common.util.URI; et non import java.net.URI;).

- Ajouter dans votre plugin la dépendance vers le plugin org.eclipse.emf.ecore.xmi et exécuter le test unitaire.

- Compléter la méthode de tests de manière à charger les instances (fichier Sample.addressbook) créées au début de l'exercice 3, voir ci-dessous le résultat du code.

1. ...

2. resourceSet = new ResourceSetImpl();

3. uri = URI.createURI("file:/" + apiSamplePath + "Sample.addressbook");

4. resource = resourceSet.getResource(uri, true);

5. createAddressBook = (AddressBook) resource.getContents().get(0);

6. Assert.assertEquals("Mon Carnet d'Adresses", createAddressBook.getName());

- Compléter en début de la méthode de façon à ajouter pour les instances de type AddressBook un écouteur sur les changements. Ainsi à chaque changement opéré sur une instance d'AddressBook, le notifieur affichera l'ancienne et la nouvelle valeur.

1. public class AddressBookTest {

2. @Test

3. public void createAddressBookTest() {

4. AddressBook createAddressBook = AddressbookFactory.eINSTANCE.createAddressBook();

5. createAddressBook.eAdapters().add(new EContentAdapter() {

7. @Override

8. public void notifyChanged(Notification notification) {

9. System.out.print("Ancienne Valeur : " + notification.getOldValue());

10. System.out.println(" Nouvelle Valeur : " + notification.getNewValue());

11. }

12. });

13. createAddressBook.setName("Mon Carnet d'Adresses");

14. ...

15. }

VI - Exercice 5 : Manipulation du métamodèle Ecore : LateBinding

VI-A - But

Trois principaux objectifs seront visés :

- Manipuler le métamodèle Ecore ;

- Créer des instances via le métamodèle Ecore ;

- Créer des instances via les outils d'Eclipse.

VI-B - Description

Nous allons dans cet exercice manipuler le métamodèle Ecore afin de connaître la structure de notre modèle (puisque le modèle AddressBook est une instance du métamodèle Ecore). Nous allons également créer et modifier des instances de notre modèle via le métamodèle Ecore. Finalement nous sauvegarderons et chargerons ces instances afin d'obtenir un fichier XMI identique à l'exercice 4. L'intérêt de cet exercice est d'utiliser une API de type LateBinding où le métamodèle Ecore est manipulé. Nous présentons à titre indicatif le métamodèle Ecore.

VI-C - Interroger le métamodèle Ecore

Dans la classe AddressBookTest du fragment eclipse.emf.addressbook.test ajouter une méthode appelée queryAddressBookStructure. L'objectif de cette méthode est d'afficher la structure complète de votre modèle en interrogeant le métamodèle. Le résultat attendu est donné par la capture d'écran ci-dessous.

Le code donné ci-dessous montre comment obtenir un tel résultat. Vous noterez que le point d'accès au métamodèle se fait par l'intermédiaire du package AddressbookPackage.

1. @Test

2. public void queryAddressBookStructure() {

3. AddressbookPackage addressbookPackage = AddressbookPackage.eINSTANCE;

4. EList<EClassifier> eClassifiers = addressbookPackage.getEClassifiers();

6. for (EClassifier eClassifier : eClassifiers) {

7. System.out.println(eClassifier.getName());

8. System.out.print("  ");

10. if (eClassifier instanceof EClass) {

11. EClass eClass = (EClass) eClassifier;

12. EList<EAttribute> eAttributes = eClass.getEAttributes();

13. for (EAttribute eAttribute : eAttributes) {

14. System.out.print(eAttribute.getName() + "("

15. + eAttribute.getEAttributeType().getName() + ") ");

16. }

18. if (!eClass.getEAttributes().isEmpty()

19. && !eClass.getEReferences().isEmpty()) {

20. System.out.println();

21. System.out.print("  Références : ");

23. EList<EReference> eReferences = eClass.getEReferences();

24. for (EReference eReference : eReferences) {

25. System.out.print(eReference.getName() + "("

26. + eReference.getEReferenceType().getName() + "["

27. + eReference.getLowerBound() + ".."

28. + eReference.getUpperBound() + "])");

29. }

30. }

32. if (!eClass.getEOperations().isEmpty()) {

33. System.out.println();

34. System.out.print("  Opérations : ");

35. for (EOperation eOperation : eClass.getEOperations()) {

36. System.out.println(eOperation.getEType().getName()

37. + " " + eOperation.getName());

38. }

39. }

40. }

41. System.out.println();

42. }

43. }

VI-D - Création d'instances via le métamodèle Ecore

Pour l'instant, nous avons vu que pour créer des instances du modèle nous devions utiliser les classes générées, approche dite EarlyBinding. Par réflexivité, il est possible de créer et modifier des instances du modèle sans avoir à manipuler explicitement les classes générées. Nous allons montrer comment réaliser cela.

- Construire un projet EMF vide (File -> Project … -> Eclipse Modeling Framework -> Empty EMF Project) que vous appellerez eclipse.emf.addressbook.latebinding ;

- Ajouter la dépendance vers les plugin org.junit (4.8.2) et org.eclipse.emf.ecore.xmi ;

- Créer ensuite le package eclipse.emf.addressbook.latebinding et finalement créer la classe eclipse.emf.addressbook.latebinding.AddressBookLateBinding ;

- Copier votre fichier addressbook.ecore réalisé dans le premier exercice dans le répertoire model de votre nouveau projet ;

Ce nouveau plugin ne contient aucune dépendance vers les plugins créés précédents.

- Créer une méthode de test appelée queryAddressBookStructureWithoutGeneratedCode dont l'objectif est :

- de charger le fichier addressbook.ecore afin de charger le modèle (accessible via le package racine),

- d'afficher la structure du modèle comme précisée dans la question précédente.

1. @Test

2. public void queryAddressBookStructureWithoutGeneratedCode() {

3. Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;

4. Map<String, Object> m = reg.getExtensionToFactoryMap();

5. m.put("ecore", new XMIResourceFactoryImpl());

6. ResourceSet resourceSet = new ResourceSetImpl();

7. URI fileURI = URI.createFileURI("model/addressbook.ecore");

8. Resource resource = resourceSet.getResource(fileURI, true);

10. EPackage ePackage = (EPackage) resource.getContents().get(0);

12. EList<EClassifier> eClassifiers = ePackage.getEClassifiers();

14. for (EClassifier eClassifier : eClassifiers) {

15. System.out.println(eClassifier.getName());

16. System.out.print("  ");

18. if (eClassifier instanceof EClass) {

19. EClass eClass = (EClass) eClassifier;

20. EList<EAttribute> eAttributes = eClass.getEAttributes();

21. for (EAttribute eAttribute : eAttributes) {

22. System.out.print(eAttribute.getName() + "("

23. + eAttribute.getEAttributeType().getName() + ") ");

24. }

26. if (!eClass.getEAttributes().isEmpty()

27. && !eClass.getEReferences().isEmpty()) {

28. System.out.println();

29. System.out.print("  Références : ");

30. }

32. EList<EReference> eReferences = eClass.getEReferences();

33. for (EReference eReference : eReferences) {

34. System.out.print(eReference.getName() + "("

35. + eReference.getEReferenceType().getName() + "["

36. + eReference.getLowerBound() + ".."

37. + eReference.getUpperBound() + "])");

38. }

40. if (!eClass.getEOperations().isEmpty()) {

41. System.out.println();

42. System.out.print("  Opérations : ");

44. for (EOperation eOperation : eClass.getEOperations()) {

45. System.out.println(eOperation.getEType().getName()

46. + " " + eOperation.getName());

47. }

48. }

49. }

50. System.out.println();

51. }

52. }

- Créer une méthode appelée createAndSaveAddressBookWithMetaModel (voir code ci-dessous) dont l'objectif est :

- de charger le fichier addressbook.ecore correspondant à notre modèle (accessible via le package racine),

- de créer des instances identiques à celles créées pendant l'exercice 4 sans avoir à manipuler explicitement les classes Java du modèle. Cette façon de procéder est dite Dynamique.

1. @Test

2. public void createAndSaveAddressBookWithMetaModel() throws IOException {

3. Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;

4. Map<String, Object> m = reg.getExtensionToFactoryMap();

5. m.put("ecore", new XMIResourceFactoryImpl());

7. ResourceSet resourceSet = new ResourceSetImpl();

8. URI fileURI = URI.createFileURI("model/addressbook.ecore");

9. Resource resource = resourceSet.createResource(fileURI);

11. resource.load(null);

12. EPackage ePackage = (EPackage) resource.getContents().get(0);

14. EClass eAddressBook = (EClass) ePackage.getEClassifier("AddressBook");

15. EReference eContains = (EReference) eAddressBook

16. .getEStructuralFeature("contains");

17. EAttribute eName = (EAttribute) eAddressBook

18. .getEStructuralFeature("name");

19. EObject addressBookInstance = ePackage.getEFactoryInstance().create(

20. eAddressBook);

21. addressBookInstance.eSet(eName, "Mon Carnet d'Adresses");

23. EClass ePerson = (EClass) ePackage.getEClassifier("Person");

24. EAttribute eFirstName = (EAttribute) ePerson

25. .getEStructuralFeature("firstName");

26. EAttribute eFamilyName = (EAttribute) ePerson

27. .getEStructuralFeature("familyName");

28. EObject personInstance = ePackage.getEFactoryInstance().create(ePerson);

29. personInstance.eSet(eFirstName, "Mickael");

30. personInstance.eSet(eFamilyName, "BARON");

32. List<EObject> containsList = new ArrayList<EObject>();

33. containsList.add(personInstance);

34. addressBookInstance.eSet(eContains, containsList);

36. resourceSet = new ResourceSetImpl();

37. resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap()

38. .put("xmi", new XMIResourceFactoryImpl());

39. URI uri = URI.createURI("file:/addressbookinstancesonlymodel.xmi");

40. resource = resourceSet.createResource(uri);

41. resource.getContents().add(addressBookInstance);

42. resource.save(null);

44. resourceSet = new ResourceSetImpl();

45. resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap()

46. .put("xmi", new XMIResourceFactoryImpl());

47. Registry packageRegistry = resourceSet.getPackageRegistry();

48. packageRegistry.put("http://addressbook/1.0", ePackage);

50. uri = URI.createURI("file:/addressbookinstancesonlymodel.xmi");

51. resource = resourceSet.getResource(uri, true);

52. resource.load(null);

53. DynamicEObjectImpl addressBookImpl = (DynamicEObjectImpl)(resource.getContents().get(0));

54. EClass addressBook = addressBookImpl.eClass();

55. EAttribute nameAttribute = (EAttribute)(addressBook.getEStructuralFeature("name"));

56. EReference containsAttribute = (EReference)(addressBook.getEStructuralFeature("contains"));

57. Assert.assertEquals("Mon Carnet d'Adresses", addressBookImpl.eGet(nameAttribute));

59. EcoreEList eGets = (EcoreEList)addressBookImpl.eGet(containsAttribute);

60. DynamicEObjectImpl personImpl = (DynamicEObjectImpl)eGets.get(0);

61. EClass person = personImpl.eClass();

62. EAttribute familyName = (EAttribute)person.getEStructuralFeature("familyName");

63. EAttribute firstName = (EAttribute)person.getEStructuralFeature("firstName");

64. Assert.assertEquals("BARON", personImpl.eGet(familyName));

65. Assert.assertEquals("Mickael", personImpl.eGet(firstName));

66. }

Noter pour la dernière partie du code (ligne 53), l'apparition d'une nouvelle classe appelée DynamicEObjectImpl. Il s'agit d'une implémentation de l'interface EObject. Cette classe est employée quand l'utilisation dynamique est utilisée pour la création des instances.

VII - Conclusion et remerciements

Cet atelier vous montre toutes les facettes de la création et de l'instanciation de modèles EMF. La plateforme Eclipse via son framework EMF fournit un outillage pour faciliter la manipulation. Toutefois, il peut être intéressant de se détacher des outils graphiques afin de mieux cerner les APIs sous-jacentes.

Dans un prochain atelier, nous ajouterons une couche graphique à notre modèle.

Je tiens à remercier Gueritarish et alain.bernard pour la relecture technique. Je tiens également à remercier ced pour sa relecture orthographique.