I. Introduction

Les développements pour cet article ont été faits sous Eclipse-Xtext 2.2.1 avec la version du JDK 1.7.1 (Java 7).
Reprenons le projet Xtext Madsl développé dans : Introduction à Xtext : créer son propre DSL. Ouvrons ce projet dans la version Eclipse-Xtext dont nous disposons.

les plugins projects créer lors du développement du        DSL

Cette fois, nous ne travaillerons que dans le sous-projet org.xtext.maDsl.ui. C'est dans ce sous-projet qu'il est indiqué d'ajouter tous les éléments graphiques relatifs au DSL.

II. Cahier des charges

L'objectif de notre travail dans ce tutoriel reste très académique et est double :
- premièrement, nous voulons développer et embarquer dans Eclipse-Xtext, du code Java permettant d'afficher dans une fenêtre graphique (à développer également) des programmes Madsl dont le fichier a été sélectionné dans la vue Package Explorer d'Eclipse ;
- deuxièmement, nous voulons de même développer et embarquer dans Eclipse-Xtext, du code Java devant exécuter des programmes madsl dont le fichier a été sélectionné dans la vue Package Explorer, et afficher le résultat dans une fenêtre graphique.

Il ressort deux choses principales des objectifs précédents : l'une destinée au développement du code Java pour la manipulation du langage et l'autre destinée au développement d'une interface graphique devant afficher les résultats des exécutions.
Nous choisissons de développer comme interface graphique, une vue, car celle-ci est facilement intégrable dans l'IDE Eclipse via le développement de plugin. Cette vue doit contenir deux boutons :

1) l'un qui lorsqu'il est appuyé déclenche une action permettant d'afficher dans un compartiment de la même vue le programme Madsl, sélectionné dans le package Explorer d'Eclipse ;
2) l'autre qui, lorsqu'il est appuyé déclenche une action permettant d'exécuter et d'afficher dans le compartiment de la même vue, le résultat du programme Madsl sélectionné dans le package Explorer d'Eclipse.

En somme, ce travail demande donc que l'on puisse :

a) premièrement, développer l'interface graphique (vue et boutons) destinée à la manipulation du langage ;
b) deuxièmement, développer un Display (Afficheur) et un Runner (Exécuteur) pour notre langage. Nous nous allégeons du développement d'un compilateur (qui est très lourd à développer) dans ce tutoriel et nous supposons que le programme Madsl sélectionné pour exécution par l'utilisateur ne comporte pas d'erreur (les colorations syntaxiques peuvent aider à détecter les erreurs dans le programme).

III. Développement de la vue MadslView du DSL Madsl

Le développement d'interface graphique pour les applications Java peut se faire de manière très rapide en utilisant le plugin WindowBuildertéléchareger Windowbuilder. Ce plugin intégré à Eclipse permet de dessiner des interfaces graphiques et de récupérer le code généré pour personnalisation (customize). Pour installer sa version 3.7 par exemple (celle utilisée pour les développements de cet article), copiez le lien suivant : http://download.eclipse.org/windowbuilder/WB/integration/3.7. Dans le menu help d'eclipse-Xtext, sélectionnez Install New Software. Une fenêtre s'ouvre : recopier ce lien dans le champ work with et appuyez Add et suivre l'installation.

Considérons à présent notre sous-projet plugin org.xtext.maDsl.ui. Dans ce sous-projet, créons un package avec un nom de notre choix (ex. : org.xtext.run) dans lequel nous allons ajouter toutes les classes développées pour la manipulation du langage. Dans ce package, créons la classe MadslView étendant la superclasse Viewpart destinée à la gestion des vues. MadslView a pour but de réaliser la partie graphique de notre travail.

MadslView class

Pour la créer via windowbuilder, cliquez droit sur org.xtext.run-->New-->Other-->SWT Designer-->Forms-->ViewPart. Une fenêtre s'ouvre. Dans cette fenêtre, indiquez le nom de votre classe (MadslView). Une fois la classe créée, elle vient avec un squelette de code qu'il faudra compléter. Il se peut qu'elle indique des erreurs dans ce code squelette : supprimer les objets provoquant ces erreurs, car ils ne nous serviront pas. Ouvrir cette classe, en bas à gauche en dessous de l'éditeur Eclipse, cliquez sur Design pour dessiner votre vue et ajouter les boutons. Voici un exemple de vue que nous avons proposée :

Madslview

Ouvrons maintenant le code source de cette vue. Pour implémenter les actions liées aux boutons de la vue, nous allons compléter la méthode widgetSelected(SelectionEvent e) de chaque bouton. Pour le bouton " Display The Selected Madsl Program", le code proposé doit permettre de détecter le fichier Madsl sélectionné par l'utilisateur dans le Package Explorer et afficher le contenu intégral de ce fichier dans la vue. Pour le bouton " Execute The Selected Madsl Program" le code proposé doit permettre de détecter le fichier Madsl sélectionné par l'utilisateur dans le Package Explorer et faire appel à la classe MadslRunner représentant la classe chargée d'exécuter un programme Madsl.

Nous proposons ci-dessous le code de la classe MadslView que nous avons développé :

Code Java de la vue MadslView
Sélectionnez

package org.xtext.run;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;

import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.wb.swt.SWTResourceManager;

public class MadslView extends ViewPart {

    public static final String ID = "run.maDslView"; //$NON-NLS-1$
    Label resultLabel;
    String[] relativeUriOfTheSelectedFile;

    public MadslView() {
    }

    /**
     * Create contents of the view part.
     * @param parent
     */
    //@Override
    public void createPartControl(Composite parent) {
        parent.setLayout(null);

        ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        scrolledComposite.setBounds(197, 0, 580, 170);
        scrolledComposite.setExpandHorizontal(true);  
        scrolledComposite.setExpandVertical(true);

        resultLabel = new Label(scrolledComposite, SWT.NONE);
        resultLabel.setBackground(SWTResourceManager.getColor(SWT.COLOR_WHITE));
        scrolledComposite.setContent(resultLabel);
        scrolledComposite.setMinSize(resultLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT));

        Button btnDisplayCurrentMadsl = new Button(parent, SWT.NONE);
        btnDisplayCurrentMadsl.setAlignment(SWT.RIGHT);
        btnDisplayCurrentMadsl.setBounds(0, 10, 198, 32);
        btnDisplayCurrentMadsl.setText("Display The Selected Madsl Program");
        btnDisplayCurrentMadsl.addSelectionListener(new SelectionListener() {
        //@Override
        public void widgetSelected(SelectionEvent e) {
            // Add
            //IWorkbench workbench = PlatformUI.getWorkbench();
            //IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();

            IWorkbenchPage page = getSite().getPage(); //recupération de la page active de la fenêtre eclipse
            //récupération de l'objet sélectionné dans la vue Package Explorer; l'objet est tracé par un chemin relatif le localisant, indiquant
            //si c'est un fichier ou un dossier
            ISelection currentSelectedFile = page.getSelection("org.eclipse.jdt.ui.PackageExplorer"); 
            int relativePathLength = currentSelectedFile.toString().length();
            relativeUriOfTheSelectedFile = currentSelectedFile.toString().substring(1, relativePathLength - 1).split("/");
            if(relativeUriOfTheSelectedFile[0].equals("P")){ //si l'élément sélectionné est un dossier
                Display display = new Shell().getDisplay();
                resultLabel.setForeground(display.getSystemColor(SWT.COLOR_RED)); // afficher le texte avec la couleur rouge
                resultLabel.setText("");
                resultLabel.setText("Un projet ou un package a été sélectionné. Veuillez plutôt sélectionner un fichier .madsl");
            }
            else{ 
                if(relativeUriOfTheSelectedFile[0].equals("L")){ //si l'élément sélectionné est un fichier
                    IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
                     //récupération du projet auquel appartient le fichier sélectionné
                    //IProject currentSelectedProject =
                    // workspaceRoot.getProject(relativeUriOfTheSelectedFile[relativeUriOfTheSelectedFile.length - 1]);
                    URI absoluteUriWorkspace = workspaceRoot.getLocationURI();
                    String absoluteUriOfTheSelectedFile = absoluteUriWorkspace.toString();
                    for(int i = 1; i< relativeUriOfTheSelectedFile.length; i++){
                        absoluteUriOfTheSelectedFile += "/" + relativeUriOfTheSelectedFile[i]; 
                    }
                    int absoluteUriOfSelectedFileLength = absoluteUriOfTheSelectedFile.length();
                    // le préfixe constitué des 6 premiers caractères ne font pas partie du chemin considéré
                    absoluteUriOfTheSelectedFile = absoluteUriOfTheSelectedFile.substring(6, absoluteUriOfSelectedFileLength); 
                    String absolutePathOfTheSelectedFile = absoluteUriOfTheSelectedFile.replace('/', '\\');

                    //lecture du fichier .madsl sélectionné et affichage dans la vue
                    String sourceCode = "";
                    try{
                        BufferedReader bfr = new BufferedReader (new FileReader(absolutePathOfTheSelectedFile));
                        String line = bfr.readLine(); 
                        while(line != null){
                            sourceCode += line + "\n";
                            line = bfr.readLine();
                        }
                        Display display = new Shell().getDisplay();
                        resultLabel.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); // afficher le texte avec la couleur noire
                        resultLabel.setText(""); //effacer ce qui était déjà écrit dans la vue
                        resultLabel.setText(sourceCode);
                    }
                    catch(IOException ex){
                        Display display = new Shell().getDisplay();
                        resultLabel.setForeground(display.getSystemColor(SWT.COLOR_RED)); // afficher le texte avec la couleur rouge
                        resultLabel.setText("");
                        resultLabel.setText("Impossible de lire le fichier sélectionné");
                    }     

                }
            }
        }

        //@Override
        public void widgetDefaultSelected(SelectionEvent e) {
        }
        });

        Button btnExecuteCurrentMadsl = new Button(parent, SWT.NONE);
        btnExecuteCurrentMadsl.setBounds(0, 60, 198, 32);
        btnExecuteCurrentMadsl.setText("Execute The Selected Madsl Program");
        btnExecuteCurrentMadsl.addSelectionListener(new SelectionListener() {
        //@Override
        public void widgetSelected(SelectionEvent se) {
            // Add
            IWorkbenchPage page = getSite().getPage(); //récupération de la page active de la fenêtre eclipse
            //récupération de l'objet sélectionné dans la vue Package Explorer; l'objet est tracé par un chemin relatif le localisant, 
            //indiquant si c'est un fichier ou un dossier
            ISelection currentSelectedFile = page.getSelection("org.eclipse.jdt.ui.PackageExplorer"); 
            int relativePathLength = currentSelectedFile.toString().length();
            relativeUriOfTheSelectedFile = currentSelectedFile.toString().substring(1, relativePathLength - 1).split("/");
            if(relativeUriOfTheSelectedFile[0].equals("P")){
                Display display = new Shell().getDisplay();
                // afficher le texte avec la couleur rouge
                resultLabel.setForeground(display.getSystemColor(SWT.COLOR_RED)); 
                resultLabel.setText("");
                resultLabel.setText("Un projet ou un package a été sélectionné. 
                                Veuillez plutôt sélectionner un fichier .madsl");
            }
            else{ 
                if(relativeUriOfTheSelectedFile[0].equals("L")){
                    //IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
                    //URI absoluteUriWorkspace = workspaceRoot.getLocationURI();
                    String UriOfTheSelectedFile = currentSelectedFile.toString().substring(2, relativePathLength - 1);

                    MadslRunner madslrunner = new MadslRunner();
                    double result = madslrunner.runMadslProgram(UriOfTheSelectedFile);

                    Display display = new Shell().getDisplay();
                    // afficher le résultat avec la couleur noire
                    resultLabel.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); 
                    resultLabel.setText("");
                    resultLabel.setText(" " + result);
                }
            }
        }

        //@Override
        public void widgetDefaultSelected(SelectionEvent e) {
        }
        });

        Label oneLabel = new Label(parent, SWT.NONE);
        oneLabel.setBackground(SWTResourceManager.getColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND));
        oneLabel.setBounds(0, 0, 198, 170);

        createActions();
        initializeToolBar();
        initializeMenu();
    }

    /**
     * Create the actions.
     */
    private void createActions() {
        // Create the actions
    }

    /**
     * Initialize the toolbar.
     */
    private void initializeToolBar() {
        IToolBarManager toolbarManager = getViewSite().getActionBars()
                .getToolBarManager();
    }

    /**
     * Initialize the menu.
     */
    private void initializeMenu() {
        IMenuManager menuManager = getViewSite().getActionBars()
                .getMenuManager();
    }

    //@Override
    public void setFocus() {
        // Set the focus
    }
}

Pour intégrer cette vue à l'éditeur eclipse-Xtext, ouvrez le fichier plugin.xml du sous-projet org.xtext.maDsl.ui. Sélectionnez l'onglet Extensions. Dans cette fenêtre nous allons lier notre vue MadslView au projet. Cliquez sur Add. La fenêtre New Extension s'ouvre. Dans cette fenêtre, saisissez org.eclipse.ui.view dans le champ texte Extension Point Filter et sélectionnez le plugin correspondant. Puis terminez en cliquant sur Finish.

New Extension

Le plugin est alors ajouté dans la fenêtre Extensions :

Extensions

Cliquez droit sur ce plugin, puis New, puis View. Complétez en suite les champs texte comme sur la capture d'écran :

déclaration des propriétés de la vue MadslView

Le développement et l'intégration de la vue sont désormais terminés. Il ne nous reste plus qu'à proposer le code du Runner de notre DSL.

IV. IV-Développement de l'exécuteur MadslRunner des programmes du DSL Madsl

Revenons dans le package org.xtext.run créé précédemment. Ajoutons une classe Java nommée MadslRunner à ce package.

Madsl Runner

Pour exécuter une à une les instructions du programme de notre DSL, nous devons utiliser les classes Java qui avaient été générées pour l'occasion dans le sous-projet org.xtext.maDsl, lors de la création du langage.

Les classes liées à la grammaire du langage

Ces classes implémentent généralement les méthodes getters/setters permettant de récupérer les données incluses dans les instructions du programme de la DSL. Ces données suivent le format de ce qui est indiqué dans la grammaire du DSL.

Pour le cas du DSL Madsl, nous voulions écrire de petits programmes permettant de récupérer des données statistiques dans des instructions dédiées et de renvoyer sa moyenne, sa variance… Ce sont ces fonctions que nous devons coder dans le Runner.

La spécificité de ce Runner est qu'il doit d'abord pouvoir fonctionner en mode standalone. C'est-à-dire indépendamment de l'exécution du projet Xtext en question. La classe permettant de réaliser cela est générée par Xtext. Le Runner doit pouvoir récupérer le fichier contenant le code Madsl à exécuter, sous forme de ressource unique situé sur le disque dur de votre machine. Pour cela, l'URI (Uniform Resource Identifier) indiquant le chemin du fichier doit être indiqué à l'objet Java ResourceSet. Cet objet renvoie un autre objet EObject représentant l'arbre syntaxique du programme contenu dans le fichier sélectionné. Pour se placer à la racine de l'arbre (correspondant à la première règle de production de la grammaire du DSL), nous utilisons la méthode get(0) de ResourceSet. Voici l'entête de la classe MadslRunner réalisant tout le processus sus-cité :

 
Sélectionnez

new MaDslStandaloneSetup();
MaDslStandaloneSetup.doSetup();
ResourceSet resourceSet = new ResourceSetImpl(); 
Resource resource = resourceSet.getResource(URI.createURI(uri, true), true);
// récupération de la racine de l'arbre syntaxique du programme
EObject eobject = resource.getContents().get(0); 

Nous proposons maintenant le code complet de la classe MadslRunner :

Code de la classe MadslRunner
Sélectionnez

package org.xtext.run;

import java.util.Iterator;
import java.util.Vector;

import org.xtext.MaDslStandaloneSetup;
import org.xtext.maDsl.EFFECTIFS;
import org.xtext.maDsl.MODALITES;
import org.xtext.maDsl.PROGRAMME;
import org.xtext.maDsl.REEL;
import org.xtext.maDsl.RETURN;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;

public class MadslRunner {

    public MadslRunner(){

    }

    public double runMadslProgram(String uri){

        double result = 0.0;

        new MaDslStandaloneSetup();
        MaDslStandaloneSetup.doSetup();
        ResourceSet resourceSet = new ResourceSetImpl(); 
        Resource resource = resourceSet.getResource(URI.createURI(uri, true), true);
        EObject eobject = resource.getContents().get(0); // récupération de la racine de l'arbre syntaxique du programme

        PROGRAMME madslProgram = (PROGRAMME) eobject;
        // récupération de la classe contenant les effectifs de la série statistique saisie dans le programme madsl
        EFFECTIFS effClass = madslProgram.getEff(); 
        // récupération du premier effectif de la série statistique
        REEL firstEff = effClass.getPremierEffectif(); 
        // récupération des autres effectifs de la série statistique
        EList<REEL> otherEffs = effClass.getAutreEffectif(); 
        Vector<Double> listOfEffs = new Vector<Double>();
        listOfEffs.add(firstEff.getReel());
        for(REEL eff : otherEffs){
            listOfEffs.add(eff.getReel());
        }

        // récupération de la classe contenant les modalités de la série statistique saisie dans le programme madsl
        MODALITES modClass = madslProgram.getMod(); 
        // récupération de la première modalité de la série statistique   
        REEL firstMod = modClass.getPremiereModalite(); 
        // récupération des autres modalités de la série statistique
        EList<REEL> otherModalitiesClasses = modClass.getAutreModalite(); 
        Vector<Double> listOfModalities = new Vector<Double>(); 
        listOfModalities.add(firstMod.getReel());
        for(REEL mod : otherModalitiesClasses){
            listOfModalities.add(mod.getReel());
        }

        // récupération de la class RETURN, pour savoir quelle opération statistique l'utilisateur veut effectuer
        RETURN ret = madslProgram.getRet(); 
        String operation = ret.getResultat();
        int op = 0;
        if(operation.equals("moyenne"))   op = 1;
        if(operation.equals("variance"))  op = 2;
        if(operation.equals("ecarttype")) op = 3;
        if(operation.equals("mode"))     op = 4;

        switch(op){                   //java 7 permet aussi de faire du switch avec des strings
            case 1 :
                result = this.moyenne(listOfEffs, listOfModalities); 
                 break;
            case 2 :
                result = this.variance(listOfEffs, listOfModalities); 
                 break;
            case 3 :
                result = this.ecarttype(listOfEffs, listOfModalities);
                 break;
            case 4 :
                result = this.mode(listOfEffs, listOfModalities); 
                 break;
        }

        return result;
    }


    private double moyenne(Vector<Double> eff, Vector<Double> mod){

        double totalEff = 0.0;
        double e = 0.0 , m = 0.0;
        Vector<Double> v = new Vector<Double>();
        Iterator<Double> itEff = eff.iterator();
        Iterator<Double> itMod = mod.iterator();
        while(itEff.hasNext() && itMod.hasNext()){// la taille des deux vecteurs est sensée être la même
            e = itEff.next();
            m = itMod.next();
            totalEff += e;
            v.add(e*m);
        }
        Iterator<Double> it = v.iterator();
        double sum = 0.0;
        while(it.hasNext()){
            sum += it.next();
        }
        return (sum/totalEff);
    }

    private double variance(Vector<Double> eff, Vector<Double> mod){

        double totalEff = 0.0;
        double x = 0.0, m = 0.0;
        double moyenne = this.moyenne(eff, mod);
        Vector<Double> v = new Vector<Double>();
        Iterator<Double> itEff = eff.iterator();
        Iterator<Double> itMod = mod.iterator();
        while(itEff.hasNext() && itMod.hasNext()){// la taille des deux vecteurs est sensée être la même
            totalEff += itEff.next();
            x = itMod.next();
            m = (x - moyenne)*(x - moyenne);
            v.add(m);
        }
        Iterator<Double> it = v.iterator();
        double sum = 0.0;
        while(it.hasNext()){
            sum += it.next();
        }
        return (sum/totalEff);
    }

    private double ecarttype(Vector<Double> eff, Vector<Double> mod){ 

        double variance = this.variance(eff, mod);
        return Math.sqrt(variance);
    }

    private double mode(Vector<Double> eff, Vector<Double> mod){

        int i = 0;
        double tampon = 0.0, effMax = 0.0, mode = 0.0;
        Iterator<Double> itEff = eff.iterator();
        effMax = itEff.next();
        mode = mod.get(i);
        while(itEff.hasNext()){ 
            tampon = itEff.next();
            if(effMax <= tampon){
                effMax = tampon;
                i++;
                mode = mod.get(i);
            }
        }
        return mode;
    }


}

V. Tests

Il a été montré dans l'article précédent comment embarquer dans Eclipse, sous forme de plugins, le projet Xtext de notre DSL. Une fois les plugins du projet intégrés dans Eclipse-Xtext, redémarrons l'IDE. Pour afficher la vue MadslView dans la perspective Java, allez dans Window --> Show view --> Other --> Other --> MadslView.

Pour créer un projet Madsl, créons un general project dans lequel nous insérons un fichier test.madsl. Saisissons ensuite du code Madsl dans ce fichier.

Code Madsl

Si nous sélectionnons le fichier test.madsl, puis cliquons sur le bouton "Display The Selected Madsl Program" de la vue MadslView, alors on voit afficher dans la vue, le code du fichier sélectionné :

Bouton display

Si nous appuyons sur le bouton "Execute The Selected Madsl Program" de la vue MadslView, alors on voit afficher dans cette vue, la moyenne de la série statistique exposée par le code Madsl du fichier test.madsl :

Bouton Execute : Calcul de la moyenne de la serie statistique

Modifions un peu le code Madsl du fichier test.madsl pour calculer la variance de cette série statistique. Nous obtenons le résultat suivant :

Bouton Execute : Calcul de la variance de la serie statistique

VI. Conclusion et perspectives

Au terme de cet enseignement, nous avons montré par un exemple, une procédure à suivre pour rendre un langage que nous avons développé, exploitable sur un IDE existant et en l'occurrence eclipse-Xtext. Nous n'avons fait que développer des besoins très basiques dans l'optique de montrer cette possibilité. Les développeurs ayant des besoins plus complexes peuvent suivre cette piste pour réaliser leur projet.

Comme perspectives à ce travail, il est aussi possible d'ajouter à eclipse-Xtext, des composants tels que des menus et des toolbars, dédiés à la manipulation du langage (nous l'avons réalisé et l'on peut remarquer l'onglet "Madsl" sur les captures d'écran précédentes). Puis, d'attacher à ces composants, des Commandes et des Handlers leur permettant de réagir aux actions qu'ils proposent.

VII. Remerciements

Je tiens à remercier keulkeul pour sa relecture technique, ainsi qu'à ClaudeLELOUP pour sa relecture orthographique.