Interroger un modèle EMF avec le projet EMF Model Query

Serge Bachmann

Cet article s'intéresse à l'interrogation des instances d'un modèle EMF via le projet EMF Model Query. Ce projet de la fondation Eclipse fournit un ensemble d'outils pour construire et exécuter des requêtes similaires à ce que l'on peut trouver au niveau des requêtes pour les bases de données.

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

- Titre : Interroger un modèle EMF avec le projet EMF Model Query

- Auteur : Serge Bachmann

- Parution : 9 juillet 2012

- Licence : Le contenu de cet article est rédigé par Serge Bachmann et est mis à disposition selon les termes de la Licence Creative Commons Attribution 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. Spécification

- I-B. Lancement de la plate-forme Eclipse

- I-C. Passage en perspective « Ecore »

- II. Création projet « EMF »

- II-A. Création projet « Empty EMF Project »

- III. Édition du modèle « Ecore »

- III-A. Édition du diagramme

- IV. Génération du code

- IV-A. Création du fichier de génération

- V. Création d'une requête

- V-A. Création du projet « exemple.graphe.emf.statement »

- VI. Requête dans le « menuBar »

- VI-A. Création « package » destiné à recevoir le code

- VI-B. Dependencies

- VI-C. Définition des extensions

- VI-C-1. Création du point d'extension « org.eclipse.ui.editorActions »

- VI-C-2. Ajouter un menu

- VI-C-3. Ajouter un « separator »

- VI-C-4. Ajouter une action

- VI-C-5. Définition « enablement »

- VI-C-6. Définition « objectClass »

- VI-D. Le fichier « plugin.xml »

- VI-E. Le code

- VI-E-1. La classe « AbstractQueryDelegate »

- VI-E-2. La classe « SelectBigCostNodesDelegate »

- VII. Test query dans « Menu Bar »

- VII-A. Exécution « Query »

- VIII. Requête dans le « popup » menu de l'éditeur

- IX. Test requête dans le « popup » menu

- X. Conclusions et remerciements

- XI. Licence

I - Introduction

I-A - Spécification

Un modèle de graphe développé avec EMF a été proposé précédemment, nous souhaitons pouvoir interroger les instances de ce modèle via des requêtes.

Pour satisfaire les besoins de cet article, le modèle de graphe a été modifié. Un attribut coût sous la forme d'un entier est ajouté à chaque nœud.

Nous désirons implémenter la requête suivante : tout nœud dont le coût est supérieur à 5 est sélectionné.

I-B - Lancement de la plate-forme Eclipse

Double-cliquer : ou le raccourci vers cet exécutable si vous l'avez créé dans le répertoire destiné à recevoir les « workspaces ». La plate-forme « Eclipse » est lancée :

Choisir un workspace qui contiendra vos projets :

Cliquer sur le bouton « OK », fermer la fenêtre « Welcome » :

I-C - Passage en perspective « Ecore »

Faire « Open Perspecitve -> Other » :

Choisir la perspective « Ecore » afin d'ouvrir les vues dédiées à la construction de modèles EMF :

Cliquer sur le bouton « OK » puis fermer certaines vues afin d'obtenir le résultat suivant :

II - Création projet « EMF »

II-A - Création projet « Empty EMF Project »

Faire « New -> Project... » :

Sélectionner l'élément « Empty EMF Project » puis cliquer sur le bouton « Next > » :

Nommer le projet « exemple.graphe.emf », puis cliquer sur le bouton « Finish » :

Le projet est créé et vous devriez obtenir le résultat suivant :

III - Édition du modèle « Ecore »

Faire un clic droit sur « model » et sélectionner la commande :

Nommer le diagramme en saisissant la valeur « Graphe.ecore » dans le champ « Domain file name » :

Cliquer sur le bouton « Finish » :

III-A - Édition du diagramme

Partir du modèle EMF réalisé dans l'article précédent Génération d'un éditeur arborescent pour l'éditiondes instances d'un modèle EMF.

Pour les besoins de notre exemple, un « EAttribute » « cout » de type « EInt » est ajouté à chaque nœud.

Afficher « Graphe.ecore » pour obtenir le résultat suivant :

Vérifier que le modèle EMF est valide (menu contextuel de l'éditeur de « Graphe.ecore »).

IV - Génération du code

IV-A - Création du fichier de génération

Faire un clic droit sur « exemple.graphe.emf » et sélectionner « New -> Other... » :

Sélectionner l'élément « EMF Generator Model » et cliquer sur le bouton « Next > » :

Sélectionner le répertoire « model » comme répertoire parent, nommer le fichier de génération « Graphe.genmodel » puis cliquer sur le bouton « Next > » :

La génération se fait à partir d'un modèle « Ecore » et cliquer sur le bouton « Next > » :

Utiliser « Browse Workspace... » dans la fenêtre « New EMF Generator Model - Ecore Import » pour sélectionner notre modèle EMF et cliquer sur le bouton « OK » pour obtenir :

Laisser la valeur proposée puis cliquer sur le bouton « Next > » :

Ne rien modifier et cliquer sur le bouton « Finish » :

Le fichier « Graphe.genmodel » de génération est créé :

Faire un clic droit dans l'éditeur de « Graphe.genmodel » et sélectionner la commande « Generate Model Code » :

Faire de même avec « Generate Edit Code », « Generate Editor Code » pour obtenir le résultat suivant :

V - Création d'une requête

Nous allons créer un projet réalisant une requête simple sur le modèle en cours d'édition.

La requête porte sur l'attribut coût des nœuds : « Sélectionner tous les nœuds dont le coût est supérieur à 5 ». Nous allons créer un nouveau projet pour traiter la requête.

V-A - Création du projet « exemple.graphe.emf.statement »

À partir du menu « File -> Project... » :

Sélectionner l'élément « Plug-in Project », puis cliquer sur le bouton « Next > » :

Nommer le projet « exemple.graphe.emf.statement » puis cliquer sur le bouton « Next > » :

Renommer l'« Activator » en « exemple.graphe.emf.statement.QueryPlugin » puis cliquer sur le bouton « Finish » :

Accepter le changement de perspective en validant sur le bouton « Yes ».

VI - Requête dans le « menuBar »

Nous souhaitons ajouter une commande dans la barre de menus qui permettra d'exécuter la requête.

VI-A - Création « package » destiné à recevoir le code

Dans le répertoire « src », créer le package via le menu « New -> Package » :

Nommer le « package » via la valeur « monCode », puis cliquer sur le bouton « Finish » :

Le résultat attendu est le suivant :

VI-B - Dependencies

Le « plug-in » « exemple.graphe.emf.statement » fait référence à d'autres plug-ins. Dans le « Plugin Manifest Editor » du projet sélectionner l'onglet : « Dependencies ». Dans la zone « Required Plug-ins », cliquer sur le bouton « Add... » :

Sélectionner le plugin « org.eclipse.emf.query » puis valider :

Cliquer sur le bouton « Add... » et sélectionner les plugins « exemple.graphe.emf », « exemple.graphe.emf.edit » et « exemple.graphe.emf.editor » puis valider :

Cliquer sur le bouton « Add... » et sélectionner le plugin « org.eclipse.emf » :

Finalement nous obtenons le résultat suivant :

VI-C - Définition des extensions

VI-C-1 - Création du point d'extension « org.eclipse.ui.editorActions »

Dans le « Plugin Manifest Editor », sélectionner l'onglet « Extensions » puis dans la zone « All Extensions » cliquer sur le bouton « Add... » :

Depuis l'assistant « New Extension - Extension Point Selection » sélectionner le point d'extension « org.eclipse.ui.editorActions », puis cliquer sur le bouton « Finish » :

Le point d'extension est créé avec un sous-élément (editor Contribution) :

Sélectionner le sous-élément et initialiser ses « Extension Element Details » :

Remarque : Utiliser le bouton « Browse... » pour sélectionner le « targetID* » :

Ne pas oublier de faire une sauvegarde afin de mettre à jour la zone « All Extensions » :

VI-C-2 - Ajouter un menu

Faire un clic droit sur « exemple.graphe.editor... » et sélectionner « New > menu » :

Le menu est alors créé :

Initialiser les champs comme indiqué ci-dessous :

Après sauvegarde, vous obtiendrez le résultat suivant :

VI-C-3 - Ajouter un « separator »

Faire :

Le séparateur est créé :

L'initialiser :

Puis faire une sauvegarde pour obtenir :

VI-C-4 - Ajouter une action

Faire :

Pour obtenir le résultat suivant :

Initialiser les champs avec les valeurs suivantes :

Faire une sauvegarde pour obtenir :

Sélectionner « Select Big Nodes (action) » dans « Extension Element Details » et cliquer sur : « class*: » :

Définir la classe :

Cliquer sur le bouton « Finish », la classe est alors créée :

Nous l'éditerons par la suite. Revenons à la construction des extensions.

VI-C-5 - Définition « enablement »

Retour dans le « Plugin Manifest Editor » onglet « Extensions » et faire :

VI-C-6 - Définition « objectClass »

Faire :

Ce qui permet d'obtenir :

Utiliser le bouton « Browse... » dans « Extension Element Details » pour initialiser « name*: » puis cliquer sur le bouton « OK » :

Après sauvegarde vous obtiendrez :

VI-D - Le fichier « plugin.xml »

Sélectionner l'onglet « plugin.xml » pour retrouver la définition complète de l'extension « org.eclipse.ui.editorActions » dans le fichier « plugin.xml » :

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.editorActions">
<editorContribution
id="exemple.graphe.editorContribution"
targetID="graphe.presentation.GrapheEditorID">
<menu
id="exemple.graphe.queryMenuID"
label="Query"
path="grapheMenuID/additions">
<separator
name="additions">
</separator>
</menu>
<action
class="monCode.SelectBigCostNodesDelegate"
id="exemple.graphe.SelectBigCostNode"
label="Select Big Cost Nodes"
menubarPath="grapheMenuID/exemple.graphe.queryMenuID/additions"
style="push">
<enablement>
<objectClass
name="org.eclipse.emf.ecore.EObject">
</objectClass>
</enablement>
</action>
</editorContribution>
</extension>

</plugin>

VI-E - Le code

L'action définie dans l'extension « org.eclipse.ui.editorAction » fait référence à la classe « monCode.SelectBigCostNodesDelegate » :

C'est à dire :

Cette classe sera définie comme sous-classe de la classe « AbstactQueryDelegate » classe qui est définie dans le paragraphe ci-dessous.

VI-E-1 - La classe « AbstractQueryDelegate »

Faire :

Définir la classe :

Remarque : utiliser le bouton « Browse... » pour définir les interfaces :

Cliquer sur le bouton « Finish » la classe est alors créée :

package monCode;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IActionDelegate2;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;

public class AbstractQueryDelegate implements IEditorActionDelegate,
IActionDelegate2 {

@Override
public void run(IAction action) {
// TODO Auto-generated method stub

}

@Override
public void selectionChanged(IAction action, ISelection selection) {
// TODO Auto-generated method stub

}

@Override
public void init(IAction action) {
// TODO Auto-generated method stub

}

@Override
public void dispose() {
// TODO Auto-generated method stub

}

@Override
public void runWithEvent(IAction action, Event event) {
// TODO Auto-generated method stub

}

@Override
public void setActiveEditor(IAction action, IEditorPart targetEditor) {
// TODO Auto-generated method stub

}

}

L'éditer :

package monCode;

import graphe.presentation.GrapheEditor;

import java.util.Collection;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate2;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;

/**
* This action delegate queries the user for the name of a class. Then it uses
* the query APIs to find the class and selects it in the editor.
*
* @see IEditorActionDelegate
*/
publicabstractclass AbstractQueryDelegate
implements IEditorActionDelegate, IActionDelegate2 {

/**
* Error message to display when an exception occurred
*/
protectedstaticfinal String MESSAGE_EXCEPTION =
"QueryStatementsMessages.message_exception";

/**
* The shell this action is hosted in
*/
protected Shell shell = null;

/**
* The active editor
*/
protected GrapheEditor editor = null;

/**
* Selected EObjects
*/
protected Collection<EObject> selectedEObjects = null;

/**
* The InputDialog title
*/
protected String title;

/**
* The InputDialog message
*/
protected String message;

/**
* The message to output when query result set is empty
*/
protected String notFoundMessage;

/**
* Constructor
*/
public AbstractQueryDelegate(String titleIn,
String messageIn, String notFoundMessageIn) {
title = titleIn;
message = messageIn;
notFoundMessage = notFoundMessageIn;
}

/**
* Perform a query that returns a set of objects.
*
* @param value
*            The value used to drive the query
* @return The set of objects returned by the query
*/
protectedabstract Collection<EObject> performQuery(Object value)
throws Exception;

/*
* @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction,
*      org.eclipse.jface.viewers.ISelection)
*/
publicvoid selectionChanged(IAction action,
final ISelection selection) {
this.selectedEObjects = null;
try {
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection =
(IStructuredSelection) selection;
this.selectedEObjects =
new java.util.ArrayList<EObject>();
for (Object next : structuredSelection.toList()) {
if (next instanceof EObject) {
selectedEObjects.add((EObject) next);
}
}
}
} catch (Exception e) {
// Exceptions are not expected
MessageDialog.openInformation(shell,
title, MESSAGE_EXCEPTION);
thrownew RuntimeException(e);
} finally {
action.setEnabled((null != selectedEObjects));
}
}

/*
* @see org.eclipse.ui.IActionDelegate2#dispose()
*/
publicvoid dispose() {
//No-op
}

/*
* @see org.eclipse.ui.IEditorActionDelegate#setActiveEditor(org.eclipse.jface.action.IAction,
*      org.eclipse.ui.IEditorPart)
*/
publicvoid setActiveEditor(IAction action,
IEditorPart targetEditor) {
this.editor = (GrapheEditor) targetEditor;
if ( targetEditor != null ) {
this.shell = targetEditor.getSite().getShell();
}
}

/*
* @see org.eclipse.ui.IActionDelegate2#init(org.eclipse.jface.action.IAction)
*/
publicvoid init(IAction action) {
// No-op
}

/*
* @see org.eclipse.ui.IActionDelegate2#runWithEvent(org.eclipse.jface.action.IAction,
*      org.eclipse.swt.widgets.Event)
*/
publicvoid runWithEvent(IAction action, Event event) {
run(action);
}
}

Faire une sauvegarde, la structure de la classe est la suivante :

VI-E-2 - La classe « SelectBigCostNodesDelegate »

Dans le « Package Explorer » double-cliquer « SelectBigCostNodesDelegate » :

Éditer la classe :

package monCode;

import graphe.GraphePackage;

import java.util.Collection;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.query.conditions.eobjects.EObjectCondition;
import org.eclipse.emf.query.conditions.eobjects.structuralfeatures.EObjectAttributeValueCondition;
import org.eclipse.emf.query.conditions.numbers.NumberCondition;
import org.eclipse.emf.query.statements.FROM;
import org.eclipse.emf.query.statements.SELECT;
import org.eclipse.emf.query.statements.WHERE;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;

publicclass SelectBigCostNodesDelegate extends AbstractQueryDelegate {

public SelectBigCostNodesDelegate() {
super("Sélection nœuds de cout supérieur ou égal à 5",
null,
"Pas de nœud de cout supérieur ou égal à 5");
}

@Override
publicvoid run(IAction action) {
try {
Collection<EObject> result = performQuery(null);
if (result.isEmpty()) {
MessageDialog
.openInformation(shell, title, notFoundMessage);
} else {
this.editor.setSelectionToViewer(result);
}
} catch (Exception e) {
// Exceptions are not expected
MessageDialog.openInformation(shell, title, MESSAGE_EXCEPTION);
thrownew RuntimeException(e);
}

}

@Override
protected Collection<EObject> performQuery(Object value) throws Exception {

/*
* Looking for nodes whose "cout" > 5
*/
EObjectCondition condition =
new EObjectAttributeValueCondition(
GraphePackage.eINSTANCE.getNoeuds_Cout(),
NumberCondition.between(5,Integer.MAX_VALUE));

// Build the select query statement
SELECT select = new SELECT(
new FROM(selectedEObjects),
new WHERE(condition));

// Execute query
return select.execute();

}
}

Faire une sauvegarde, la structure de la classe est la suivante :

VII - Test query dans « Menu Bar »

Exécuter le plug-in sur une nouvelle plateforme :

Dans « Run Configurations » double-cliquer sur « Eclipse Application ». Mettre à jour le nom de la configuration en « EMFModelQuery ». Cliquer « Apply » :

Suivre la procédure d'exécution suivante :

1. Faire « Run », fermer la fenêtre « Welcome » et quelques vues afin de garder celles essentielles ;

2. Ouvrir un projet « General > Project », le nommer « testQuery » ;

3. Faire un clic droit sur le projet et sélectionner la commande : « New > Other... » ;

4. Sélectionner « Example EMF Model Creation Wizards > Graphe Model » ;

5. Nommer le Fichier « G1.graphe » ;

6. Sélectionner l'objet du modèle à créer : « Graphe ».

Ouvrir la vue « Properties » puis éditer le modèle :

Ouvrir le modèle textuel :

Vous pouvez visualiser tous les attributs « Cout » associés aux nœuds :

VII-A - Exécution « Query »

Dans l'éditeur de « G1.graphe » sélectionner « Graphe » puis faire :

ce qui donne :

les nœuds N5 et N6 qui ont un coût supérieur ou égal à 5 sont sélectionnés.

VIII - Requête dans le « popup » menu de l'éditeur

Pour créer rapidement cette requête, sélectionner l'onglet « plugin.xml » du « plug-in Manifest Editor », éditer pour ajouter l'extension « org.eclipse.ui.popupMenus » :

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.editorActions">
<editorContribution
id="exemple.graphe.editorContribution"
targetID="graphe.presentation.GrapheEditorID">
<menu
id="exemple.graphe.queryMenuID"
label="Query"
path="grapheMenuID/additions">
<separator
name="additions">
</separator>
</menu>
<action
class="monCode.SelectBigCostNodesDelegate"
id="exemple.graphe.SelectBigCostNode"
label="Select Big Cost Nodes"
menubarPath="grapheMenuID/exemple.graphe.queryMenuID/additions"
style="push">
<enablement>
<objectClass
name="org.eclipse.emf.ecore.EObject">
</objectClass>
</enablement>
</action>
</editorContribution>
</extension>
<extension
point="org.eclipse.ui.popupMenus">
<viewerContribution
id="exemple.graphe.viewerContribution"
targetID="graphe.presentation.GrapheEditorID">
<menu
id="exemple.graphe.queryMenuID"
label="Query"
path="additions">
<separator
name="separator">
</separator>
</menu>
<action
class="monCode.SelectBigCostNodesDelegate"
id="exemple.graphe.SelectBigCostNodes"
label="Select Big Cost Nodes"
menubarPath="exemple.graphe.queryMenuID/additions">
<enablement>
<objectClass
name="org.eclipse.emf.ecore.EObject">
</objectClass>
</enablement>
</action>
</viewerContribution>
</extension>

</plugin>

Ce qui donne si l'on sélectionne l'onglet « Extension » :

IX - Test requête dans le « popup » menu

Relancer le test avec à partir de la barre des outils. Une nouvelle plateforme Eclipse est lancée, nous retrouvons le graphe précédemment édité.

Sélectionner « Graphe », faire un clic droit dans la zone d'édition. Dans le menu popup menu sélectionner « Query > Select Big Cost Nodes »

Le résultat attendu est le suivant :

X - Conclusions et remerciements

La programmation des « Query » peut paraître complexe. Le lecteur se reportera utilement au « help » grâce à la commande « Help > Help Contents ».

Nous tenons à remercier ClaudeLELOUP pour sa relecture attentive de cet article puis djibril et Keulkeul pour la mise au gabarit de l'article original.

XI - Licence

La licence « Creative Commons » s'applique à ce document, veuillez-vous référer à ce site pour de plus amples informations : http://creativecommons.org/licenses/by-nc-nd/2.0/fr/.