CodeLab IDE & Simulators

Conception d'un premier module CodeLab


En suivant ce tutoriel, vous allez réaliser le plugin "HelloModule", un petit module très simple permettant d'afficher un texte à partir d'un programme en C, Java ou Python. Le rendu ci-dessous est le résultat de l'exécution du programme Python suivant :

from HelloModule.hello import *

setFont('Times new Roman', 50)
setText('Hello World!')


La conception et le développement d'un module CodeLab "enfichable" (plugable) sont facilités par un ensemble de classes Java et d'outils qui assurent l'intégration du plugin au sein de l'architecture de CodeLab. Ce kit de développement contient :

  • Une arborescence pour préparer le développement ;
  • Des classes Java à hériter pour écrire vos classes ;
  • Des classes Java utilitaires pour faciliter certains aspects ;
  • Des scripts de compilation et de packaging.

Prérequis pour réaliser ce tutoriel :

  • Avoir installé la dernière version de CodeLab
  • Avoir utilisé CodeLab en tant qu'utilisateur final
  • Maîtriser les concepts objet et la POO en Java
  • Comprendre la syntaxe des fichiers XML


1. Arborescence de développement

Pour développer un nouveau plugin, la première chose à faire est de copier le dossier plugins à la racine de CodeLab, à côté de modules. La compilation du plugin ne pourra pas se faire si vous travaillez dans un autre emplacement.

Allez dans le répertoire plugins et dupliquez l'arborescence-modèle YourModule que vous renommerez en HelloModule. L'exécution régulière du script build.sh ou build.bat (selon votre système d'exploitation) produira un fichier .jar qui contiendra l'ensemble des fichiers et ressources de votre plugin. Ce fichier sera automatiquement copié dans le répertoire modules de CodeLab, ce qui vous permettra de tester votre module au cours de son développement.

plugins/
└── YourModule/       ← Ne modifiez pas l'arborescence-modèle
└── HelloModule/      ← Arborescence du plugin HelloModule
    ├── class/        ← Réservé à la compilation (ne pas modifier)
    ├── data/         ← Pour vos ressources (images, audio, etc.)
    ├── includes/     ← Pour les fichiers à inclure dans les programmes
    ├── lib/          ← Pour les éventuelles librairies Java
    ├── src/          ← Pour les sources Java de votre plugin    
    ├── templates/    ← Pour les éventuels templates et exercices
    ├── build.sh      ← Script Bash de compilation et de packaging
    └── build.bat     ← Script DOS de compilation et de packaging

Explorez HelloModule. Le répertoire src contient en particulier les deux fichiers Java YourModule.java et YourToolbar.java que vous renommerez et qui serviront de base au développement de votre module :

HelloModule/
├── lib/
│   └── codelab.jar                     ← Réservé à la compilation (ne pas supprimer)
├── src/
│   └── codelab/
│       └── modules/
│           └── yourmodule/             ← Renommer en "hellomodule"
│               ├── YourModule.java     ← Renommer en "HelloModule.java"
│               └── YourToolbar.java    ← Renommer en "HelloToolbar.java"
├── data/
│   ├── icon_clear.png                  ← Une icone pour un bouton
│   └── icon_hello.png                  ← Une autre icone
├── includes/
│   └── api.dtd                         ← Réservé à la compilation (ne pas supprimer)
├── templates/

Après duplication de l'arborescence-modèle, vous avez procédé aux renommages suivants :

  • Le répertoire principal YourModule est devenu HelloModule
  • Le package terminal yourmodule est devenu hellomodule
  • La classe YourModule.java est devenue HelloModule.java
  • La classe YourToolbar.java est devenue HelloToolbar.java

La classe HelloToolbar.java ne sera pas utilisée dans un premier temps.


2. Classes principales du module

La conception d'un plugin doit respecter le modèle objet suivant, les deux super-classes AbstractModule et AbstractToolbar assurant l'intégration du plugin dans l'architecture de CodeLab. La classe héritée HelloModule sera composée d'un JPanel chargé de la vue. Comme dans un premier temps nous n'ajoutons pas de boutons spécifiques à la barre d'outils, la classe DefaultToolbar sera implicitement utilisée :

Commençons par définir la classe HelloModule de la façon suivante :

package codelab.modules.hellomodule;

import codelab.AbstractModule;
import codelab.CodeLab;
import java.util.Map;
import javax.swing.JPanel;

public class HelloModule extends AbstractModule {

    ///////////////////////////////////////////////////
    // Constructor
    ///////////////////////////////////////////////////

    public HelloModule(CodeLab codelab) {
        super(codelab);
        setTitle("Hello Lab");
        setComponent(new JPanel());
    }

    ///////////////////////////////////////////////////
    // Abstract methods to implement (or not)
    ///////////////////////////////////////////////////

    public void init() {
    }
    public void exit() {
    }
    public void start() {
    }
    public void stop() {
    }
    public void reset() {
    }

    ///////////////////////////////////////////////////
    // Instruction handler
    ///////////////////////////////////////////////////

    public String handleRequest(Map<String, String> params) {
        String cmd = params.get("cmd");
        switch (cmd) {
            case "cmd1": return "Value to return";
            case "cmd2": return "Value to return";
            default: return unknownCommandError(cmd);
        }
    }
}

Vous pouvez d'ors-et-déjà générer votre plugin afin de vérifier qu'il apparaît bien en tant que nouvel onglet dans CodeLab. Pour ce faire, exécutez le script build.sh et vérifiez qu'un fichier HelloModule.jar a bien été placé dans le répertoire modules de CodeLab. Lancez CodeLab et constatez que vous avez un nouvel onglet intitulé "Hello Lab" mais rien de plus.

Voilà le contenu d'un terminal suite à l'exécution du script build.bat (si vous êtes sous Windows) ou build.sh (si vous être sous OSX ou Linux) lorsque tout s'est bien passé :


3. Méthodes "métier" de la classe principale

Nous allons maintenant compléter la classe HelloModule en ajoutant les méthodes qui devront répondre à la finalité de notre module (afficher un texte dans le JPanel) :

  • Une méthode int setText(String str) pour afficher une chaîne de caractères
  • Une méthode int setFont(String name, int size) pour définir la fonte et sa taille

Notez que ces méthodes retournent un entier. Ce n'est pas obligatoire mais cela permet de se souvenir qu'il est toujours possible de retourner une valeur. Le rendu final sera réalisé par un simple JLabel instancié et ajouté au JPanel à l'intérieur du constructeur. Voilà ci-dessous les modifications apportées à la classe HelloModule (ajoutez les imports awt et Swing nécessaires) :

public class HelloModule extends AbstractModule {

    private JPanel panel;
    private JLabel label;

    ///////////////////////////////////////////////////
    // Constructor
    ///////////////////////////////////////////////////

    public HelloModule(CodeLab codelab) {
        super(codelab);
        setTitle("Hello Lab");
        setComponent(panel = new JPanel());
        panel.setLayout(new GridBagLayout());
        panel.add(label = new JLabel());
    }

    ///////////////////////////////////////////////////
    // Public methods
    ///////////////////////////////////////////////////

    public int setText(String str) {
        label.setText(str);
        return 1; // No error
    }

    public int setFont(String name, int size) {
        label.setFont(new Font(name, 1, size));
        return 1; // No error
    }


4. Écriture du gestionnaire d'instructions

La classe HelloModule comporte un gestionnaire d'instructions qui reçoit les instructions en provenance des futurs programmes écrits en C, Java, Python, etc. Ce gestionnaire est implémenté par la méthode handleRequest qui reçoit une instruction sous la forme d'un dictionnaire Map<String, String> et qui retourne une chaîne de caractères. Le dictionnaire contient l'instruction et ses paramètres. La structure switch-case devra, pour chaque instruction à implémenter, extraire les paramètres, les caster vers les types attendus, invoquer la bonne méthode, et retourner un résultat sous la forme d'une chaîne de caractères :

public String handleRequest(Map<String, String> params) {
    String cmd = params.get("cmd");
    switch (cmd) {
        case "cmd1": return "Value to return";
        case "cmd2": return "Value to return";
        default: return unknownCommandError(cmd);
    }
}

Voilà ci-dessous le gestionnaire de HelloModule complété avec les instructions setText et setFont :

public String handleRequest(Map<String, String> params) {
    String cmd = params.get("cmd");
    switch (cmd) {

        case "setText":
            String str = params.get("str");
            return String.valueOf(setText(str));

        case "setFont":
            String name = params.get("name");
            int size = Integer.valueOf(params.get("size"));
            return String.valueOf(setFont(name, size));

        default:
            return unknownCommandError(cmd);
    }
}


5. Écriture des includes pour chaque langage cible

Cette étape incontournable consiste à générer les fichiers à inclure pour chaque langage cible. Ce sont eux qui constituent l'interface entre un language de programmation et l'environnement CodeLab (Application Programming Interface). La génération de ces fichiers sera réalisée automatiquement à partir de documents XML qui comportent (dans cet ordre) :

  • Des éléments "constante" (name + type + value)
  • Des éléments "instruction" (protoypes des instuctions)
  • Des éléments "include" (code à insérer directement)

Cette dernière section permet de placer dans les fichier à include des définitions qui sortent du cadre imposé par la grammaire des instructions. La DTD est la suivante :

<!ELEMENT api (constant*, instruction*, include*)>

<!ELEMENT constant EMPTY>
<!ATTLIST constant name CDATA #REQUIRED>
<!ATTLIST constant type (int|float|string) #REQUIRED>
<!ATTLIST constant value CDATA #REQUIRED>

<!ELEMENT instruction (arg*)>
<!ATTLIST instruction name CDATA #REQUIRED>
<!ATTLIST instruction type (int|float|string) #REQUIRED>

<!ELEMENT arg EMPTY>
<!ATTLIST arg name CDATA #REQUIRED>
<!ATTLIST arg type (int|float|string) #REQUIRED>

<!ELEMENT include (#PCDATA)>
<!ATTLIST include lang (c|java|python) #REQUIRED>

Le choix du nom de ce fichier est important puisqu'il détermine les noms des fichiers à inclure. Par exemple, si nous nommons ce fichier hello.xml, l'exécution du build gérèrera les fichiers hello.c, hello.py et Hello.java. Écrivez le document XML suivant et placez-le dans le répertoire includes :

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE api SYSTEM 'api.dtd'>
<api module='HelloModule'>

    <instruction name='setText' type='int'>
        <arg name='str' type='string'/>
    </instruction>

    <instruction name='setFont' type='int'>
        <arg name='name' type='string'/>
        <arg name='size' type='int'/>
    </instruction>

</api>

Générez une nouvelle fois votre plugin en exécutant le script de build et regardez les fichiers générés dans le répertoire includes. Examinons par exemple le fichier hello.py généré pour le langage Python :

SETTEXT_CMD = 'module=HelloModule&cmd=setText&str=%s'
SETFONT_CMD = 'module=HelloModule&cmd=setFont&name=%s&size=%d'

def setText(str):
    buffer = SETTEXT_CMD % str
    return int(request(buffer))

def setFont(name, size):
    buffer = SETFONT_CMD % (name, size)
    return int(request(buffer))

Quel que soit le langage cible, ces fichiers sont construits de la même façon et contiennent :

  • Un ensemble de chaînes de formattage (une par instruction)
  • Une fonction (ou méthode) par instruction à implémenter
  • Éventuellement des constantes et des fonctions utilitaires

Les chaînes de formattage sont construites sur le modèle des requêtes HTTP GET ou vers un socket-server : module=...&cmd=...&arg1=...&arg2=...&etc.. Chaque instruction implémentée devra insérer les paramètres effectifs dans la chaîne et envoyer cette dernière à CodeLab via la fonction request.


6. Écriture de quatre templates

Cette étape (optionnelle) consiste à concevoir et à ajouter au module des templates (ou des exercices) qui seront proposé dans l'environnement CodeLab au moment de l'ajout de nouveaux programmes.

Exemple de template "growing.py" pour le langage Python :

from HelloModule.hello import *

## Program _fileName_
## Created by _author_ on _date_

setText('Hello World!')
for size in range(0, 50):
    setFont('Times new Roman', size)

Exemple de template "growing.c" pour le langage C :

#include "HelloModule/hello.c"

// Program _fileName_
// Created by _author_ on _date_

int main() {
    setText("Hello World!");
    for (int size = 0; size < 50; size++)
        setFont("Times new Roman", size);
    return 0;
}

Exemple de template "growing.java" pour le langage Java :

import HelloModule.Hello;

// Class _className_
// Created by _author_ on _date_

class _className_ extends Hello {

    private void run() {
        setText("Hello World!");
        for (int size = 0; size < 50; size++)
            setFont("Times new Roman", size);
    }

    public static void main(String args[]) {
        new _className_().run();
    }
}

Exemple de template "growing.clp" pour le langage CLIPS :

#requires HelloModule/hello

;; Program _fileName_
;; Created by _author_ on _date_

(defrule only-rule
    =>
    (setText "Hello World!")
    (loop-for-count (?size 0 50)
        (setFont "Times new Roman" ?size)))

Déposez ces fichiers dans le répertoire templates de votre arborescence, et vérifiez qu'elle correspond à ce qui suit, avant de générer à nouveau votre module :

HelloModule/
├── data/
│   ├── icon_clear.png
│   └── icon_hello.png
├── lib/
│   └── codelab.jar
├── src/
│   └── codelab/
│       └── modules/
│           └── hellomodule/
│               ├── HelloModule.java
│               └── HelloToolbar.java 
├── includes/
│   ├── api.dtd
│   ├── hello.c
│   ├── hello.clp
│   ├── Hello.java
│   ├── hello.py
│   └── hello.xml
├── templates/
│   ├── growing.c
│   ├── growing.clp
│   ├── growing.java
│   └── growing.py
├── build.bat
└── build.sh

Générez votre plugin à nouveau, exécutez CodeLab et testez votre module...


7. Ajout de boutons dans la barre d'outils

Vous allez maintenant personnaliser votre barre d'outils en lui rajoutant des boutons Hello et Clear :

Commencez par instancier une HelloToolbar dans le constructeur de HelloModule, de la façon suivante :

public HelloModule(CodeLab codelab) {
    super(codelab);
    setTitle("Hello Lab");
    setToolbar(new HelloToolbar(this));
    setComponent(panel = new JPanel());
    panel.setLayout(new GridBagLayout());
    panel.add(label = new JLabel());
}

Vous pouvez déjà tester votre module ainsi modifié, et vous verrez apparaître une nouveau bouton Hello qui se contente d'afficher un "Hello World!" orange dans la console de CodeLab. Examinez les paramètres du ToolButton dans le constructeur de la HelloToolbar :

  • "HELLO" : l'identifiant du bouton / de l'action à exécuter
  • "Hello" : le texte affiché sous le bouton
  • "Say Hello World" : le texte de l'info-bulle
  • icon_hello : l'icone du bouton
  • this : la barre de boutons

Regardez également le gestionnaire d'actions de la HelloToolbar ainsi que sa méthode action_hello invoquée par le gestionnaire. Modifiez cette méthode afin d'afficher le "Hello World!", non plus dans la console, mais dans le JPanel du module :

private void action_hello() {
    ((HelloModule) module).setFont("Times", 50);
    ((HelloModule) module).setText("Hello World!");
}

Puis, sur le modèle du bouton Hello, ajoutez un bouton Clear qui devra effacer le contenu du JPanel. Cela implique de modifier le constructeur, le gestionnaire d'actions et d'écrire une méthode d'action action_clear :

public class HelloToolbar extends AbstractToolbar {

    private ImageIcon icon_hello = loadImageIcon("icon_hello.png");
    private ImageIcon icon_clear = loadImageIcon("icon_clear.png");

    public HelloToolbar(HelloModule module) {
        super(module);
        addSeparator();
        add(new ToolButton("HELLO", "Hello", "Say Hello World", icon_hello, this));
        add(new ToolButton("CLEAR", "Clear", "Clear the panel", icon_clear, this));
        endToolbar();
    }

    public void action(String ident) {
        switch (ident) {
            case "HELLO": action_hello(); break;
            case "CLEAR": action_clear(); break;
            default: super.action(ident);
        }
    }

    private void action_hello() {
        ((HelloModule) module).setFont("Times", 50);
        ((HelloModule) module).setText("Hello World!");
    }

    private void action_clear() {
        ((HelloModule) module).clear();
    }
}

Ce n'est pas tout : n'oubliez pas d'écrire la méthode clear du HelloModule :

public int clear() {
    panel.removeAll();
    panel.add(label = new JLabel());
    panel.repaint();
    return 1;
}

Vous pouvez également ajouter une nouvelle instruction clear() dans l'API du module en complétant le fichier hello.xml et le gestionnaire d'instructions.


8. Internationnalisation du module

Commencez par ajoutez un fichier lang.properties dans le dossier data et copiez-y les lignes suivantes :

title_fr = Bonjour Lab
title_en = Hello Lab

btn_hello_fr = Bonjour
btn_hello_en = Hello

btn_hello_tooltip_fr = Dire Bonjour
btn_hello_tooltip_en = Say Hello World

btn_clear_fr = Effacer
btn_clear_en = Clear

btn_clear_tooltip_fr = Effacer le panel
btn_clear_tooltip_en = Clear the panel

Les classes AbstractModule et AbstractToolbar contiennent toutes deux les méthodes getProperty(String key) et getLangProperty(String key) qui recherchent une chaîne à partir d'une clé dans les fichiers de propriétés. La particularité de la seconde méthode est d'ajouter le suffixe qui correspond à la langue spécifié par l'entrée LANG dans le fichier userconfig/user.properties de CodeLab. Ainsi, si LANG=fr l'entrée title devient title_fr.

Chargez ce fichier au début du constructeur de HelloModule et utilisez cette facilité dans le setTitle :

public HelloModule(CodeLab codelab) {
    super(codelab);
    loadProperties("lang.properties");
    setTitle(getLangProperty("title"));
    setToolbar(new HelloToolbar(this));
    setComponent(panel = new JPanel());
    panel.setLayout(new GridBagLayout());
    panel.add(label = new JLabel());
}

Pour terminer, modifiez le constructeur de la HelloToolbar comme suit :

public HelloToolbar(HelloModule module) {
    super(module);
    addSeparator();

    add(new ToolButton("HELLO",
        getLangProperty("btn_hello"),
        getLangProperty("btn_hello_tooltip"), icon_hello, this));

    add(new ToolButton("CLEAR",
        getLangProperty("btn_clear"),
        getLangProperty("btn_clear_tooltip"), icon_clear, this));

    endToolbar();
}

Ce tutoriel est terminé. Vous pouvez continuer à explorer le développement de plugins CodeLab en réalisant les tutoriels suivants.

Document under Creative Commons License CC-BY-NC-ND
Copyright © 2022 Jérôme Lehuen, Le Mans Université, France
Version 04-05-2022