Logging tomcat et hibernate

Introduction

Dans une application d'un client que je reprends pour de la maintenance, j'ai un problème. Cette application est faite pour Oracle et, sur ma machine de test, je n'ai aucune envie d'installer cette base de données plutôt gourmande en ressources.

Cette application utilise l'ORM hibernate pour abstraire tous les accès à la base de données. C'est donc, normalement, un jeu d'enfant d'utiliser un au SGBD. J'ai donc décidé d'utiliser le MySQL 5.1.36 qui est installé sur ma machine. Ensuite, j'ai modifié le fichier "persistence.xml" pour qu'hibernate se connecte à une nouvelle base de données sous MySQL.

Problème

Alors que tout devrait fonctionner sans problème, en lançant l'application, je n'ai aucune table qui est créée.

Dans le logging de cette application, j'obtiens l'erreur suivante :

ERROR org.hibernate.tool.hbm2ddl.SchemaUpdate - Unsuccessful: alter table nom_de_la_table add index FKCD10932052C35B33 (clé), add constraint FKCD10932052C35B33 foreign key (clé) references preview (id)
ERROR org.hibernate.tool.hbm2ddl.SchemaUpdate - Can't create table 'nom_de_la_table' (errno: 150)

Logging

Bon, le fait d'avoir un message d'erreur ne me pose pas plus de problèmes que ça. Par contre, ce qui m'embête c'est le fait que ce message est plutôt vague. Il me faut donc avoir un logging plus détaillé.

persistence.xml

Pour ce faire, j'ai activé les logging de requêtes dans persistence.xml

 

C'est un bon début, mais je ne vois toujours pas les requêtes de création de tables qui m'intéressent...

log4j.properties

La première chose à faire est d'ajouter un fichier "log4j.properties" au bon endroit dans ce projet qui utilise "Maven" : Voici le contenu du fichier. J'y suis allé franchement en mettant les niveaux de logging à "debug" de manière à récupérer un maximum d'informations.

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
 
log4j.rootLogger=warn, stdout
log4j.logger.org.hibernate=debug, stdout
log4j.logger.org.hibernate.SQL=debug, stdout
log4j.logger.org.hibernate.type=debug, stdout
log4j.logger.org.hibernate.engine.QueryParameters=debug, stdout

Solution

En fouillant dans ce logging très détaillé, je trouve le problème suivant :

09:12:59,704 DEBUG SchemaExport:415 -
    create table NOM_DE_LA_TABLE (
        CLE integer not null,
        ...
        primary key (CLE)
    ) type=InnoDB
Cette requête n'est pas correcte. "type=InnoDB" ne fonctionne pas avec ma version de MySQL, il faudrait "Engine=InnoDB". Je dois donc, simplement, changer le dialecte SQL :
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>

Conclusion

En fait, j'avais créé moi-même le problème en utilisant le mauvais dialecte MySQL. Mais, sans un bon système de logging, je n'avais aucun moyen de m'en apercevoir. Je me dis que ça peut arriver à n'importe qui...

Essai d’utilisation de la bibliothèque « KineticJS » dans un projet GWT (Google Web Toolkit)

Introduction

Le but de cet article est de tester la bibliothèque "KineticJS". Celle-ci bénéficie d'une meilleure intégration à GWT que "D3.js" car un wrapper "KineticGWT" existe.

Du coup elle devrait la remplacer avantageusement dans un projet de ce type.

Projet de test

Pour générer le projet de test je vais utiliser un archetype Maven pour générer le squelette du projet.

mvn archetype:generate \
   -DarchetypeGroupId=org.codehaus.mojo \
   -DarchetypeArtifactId=gwt-maven-plugin \
   -DarchetypeVersion=2.5.0
  • groupId: conceptforge.kinetics
  • artifactId: TestKinetics
  • version: 1.0-SNAPSHOT
  • package: conceptforge.kinetics
  • module: TestKinetics

A ce stade, le projet est fonctionnel, mais c'est le projet par défaut généré par l'archetype Maven.

Après une bonne cure d'amaigrissement, le projet est devenu un magnifique "Hello World" à partir duquel je vais pouvoir commencer à intégrer "KineticJS".

Voici un aperçu des fichiers qui restent dans le projet :

Le code source complet de ce Hello World est disponible ici : TestKinetics1.tar.gz

Intégration de la librairie KineticsJS

Le portage de cette librairie sous GWT porte (naturellement) le nom de "KineticGWT". Le lien "github" met à disposition de la documentation pour l'intégration de cette librairie dans une application GWT/Maven. Ça tombe bien, c'est exactement le cas de ce programme de test.

Il faut donc :

  • Ajouter les nouvelles dépendances dans "pom.xml"
  • Ajouter le module dans "TestKinetics.gwt.xml"
  • Modifier l'implémentation de "onModuleLoad()"
package conceptforge.kinetics.client;
 
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.RootPanel;
import net.edzard.kinetic.*;
 
/**
 * Entry point classes define
 * <code>onModuleLoad()</code>.
 */
public class TestKinetics implements EntryPoint {
 
    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        // Kinetic needs a special div in the DOM
        Element div = DOM.createDiv();
        RootPanel.getBodyElement().appendChild(div);
 
        // Setup stage
        Stage stage = Kinetic.createStage(div, 400, 400);
        Layer layer = Kinetic.createLayer();
        stage.add(layer);
 
        Rectangle c = Kinetic.createRectangle(new Box2d(10, 10, 200, 200));
        layer.add(c);
        stage.draw();
    }
}

Résultat

Site du zéro

Par chance le site du zéro propose un tutoriel sur KineticsJS.

Formes prédéfinies

Voici un exemple librement inspiré de la première partie du tutoriel (formes prédéfinies)

package conceptforge.kinetics.client;
 
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.RootPanel;
import net.edzard.kinetic.*;
 
/**
 * Entry point classes define
 * <code>onModuleLoad()</code>.
 */
public class TestKinetics implements EntryPoint {
 
    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        Element div = DOM.createDiv();
        RootPanel.getBodyElement().appendChild(div);
 
        Stage scene = Kinetic.createStage(div, 400, 400);
        Layer calque = Kinetic.createLayer();
        scene.add(calque);
 
        Rectangle r = Kinetic.createRectangle(new Box2d(50, 75, 350, 300));
        r.setFill(Colour.blue);
        r.setStroke(Colour.blue);
        r.setStrokeWidth(0);
 
        Circle c = Kinetic.createCircle(new Vector2d(50, 50), 50);
        c.setFill(Colour.green);
        c.setStroke(Colour.darkgray);        
 
        RegularPolygon h = Kinetic.createRegularPolygon(new Vector2d(200,150), 50, 6);
        h.setRotation(30);
        h.setFill(Colour.red);
 
        calque.add(r);
        calque.add(c);
        calque.add(h);
 
        scene.draw();
    }
}

Résultat

Ombres

En m'inspirant du tutoriel "Opacité et ombre", j'ai ajouté une ombre aux formes de l'exemple précédent :

Pour arriver à ce résultat il suffit de créer l'instance suivante de "Shadow":

Shadow shadow = new Shadow(Colour.black, 12, new Vector2d(8, 8), 0.7);

Ensuite, il faut utiliser la méthode "setShadow" pour passer cette valeur à toutes les formes.

Gestion des événements

En m'inspirant du tutoriel "Interactions", j'ai écrit le code suivant pour observer le contenu de la variable "evt" passée en paramètre dans la gestion d'événements :

r.addEventListener(Event.Type.CLICK, new Node.EventListener() {
 
            public boolean handle(Event evt) {
                try {
                    Window.alert(evt.toString());
                } catch (Exception e) {
                    Window.alert(e.getMessage());
                }
                return true;
            }
        });

En testant, j'ai eu la désagréable surprise de recevoir le message suivant :

Grosse déception

Alors que tous mes tests jusqu'à maintenant me montraient une bonne librairie pour manipuler la balise Canvas et un bon wrapper pour GWT. Je viens de toucher aux limites de cette implémentation.
Lors de la gestion d'événements, la méthode "handle" d'un EventListener contient une variable evt mais celle-ci contient toujours "null".
Après une petite recherche dans le code source, j'ai trouvé (dans l'implémentation de la classe "Node") la méthode suivante qui explique mon problème :

	/**
	 * Add a multi-event listener to the node.
	 * @param eventTypes A number of events to listen to
	 * @param handler The handler.
	 */
	// Seems to be buggy in kineticjs. Always getting mousemove events
	// TODO Maik: It's not buggy, it just fails and then creates too many events due to the failure.
	//             the evt variable is directly from the browser, not from kineticjs, that's why there's
	//             a mismatch between event types
	//             Touch also needs to be handled differently if evt should be used.
	//       quick fix: ignore evt object
	public final native void addEventListener(List eventTypes, EventListener handler) /*-{
		this.on(@net.edzard.kinetic.Node::createEventTypeString(Ljava/util/List;)(eventTypes), function(evt) {
//			if (evt != null) {
//				console.log(evt.type);
//				var javaEvt = @net.edzard.kinetic.Event::new(Lnet/edzard/kinetic/Event$Type;Lnet/edzard/kinetic/Event$Button;II)(
//					@net.edzard.kinetic.Event.Type::valueOf(Ljava/lang/String;)(evt.type.toUpperCase()),
//					@net.edzard.kinetic.Event.Button::fromInteger(I)(evt.button),
//					evt.offsetX,
//					evt.offsetY
//				);
//				javaEvt.@net.edzard.kinetic.Event::setShape(Lnet/edzard/kinetic/Shape;)(evt.shape);
//				var bubble = handler.@net.edzard.kinetic.Node.EventListener::handle(Lnet/edzard/kinetic/Event;)(javaEvt);
//				evt.cancelBubble = !bubble;
//			} else {
				handler.@net.edzard.kinetic.Node.EventListener::handle(Lnet/edzard/kinetic/Event;)(null);
//			}
		});
	}-*/;

En attendant une correction de cette méthode, je propose une solution de contournement (pas très élégante, forcément) :

r.addEventListener(Event.Type.CLICK, new Node.EventListener() {
 
            public boolean handle(Event evt) {
                if (r.getFill().toString().equals(Colour.blue.toString())) {
                    r.setFill(Colour.white);
                } else {
                    r.setFill(Colour.blue);
                }
 
                scene.draw();
                return true;
            }
        });

Je fais un accès direct à l'objet. Ce n'est pas élégant et c'est beaucoup moins souple, mais, au moins, c'est fonctionnel.
En écrivant cette petite routine, j'ai eu une deuxième mauvaise surprise, mais moins grave que la précédente. La comparaison de la couleur ne fonctionne ni avec "==" ni avec "equals".

Conclusion

Tout s'est très bien passé jusqu'à ce que j'essaye de gérer les événements. Mon soupçon est que la librairie "KineticJS" est très fonctionnelle. Ce soupçon n'est pas fondé sur des arguments techniques, mais plutôt construit à partir des différents commentaires que j'ai pu lire à ce sujet sur le Web.

Par contre, le wrapper "KineticGWT" m'a permis de réaliser très facilement les exemples du tutoriel que j'ai utilisé tant que je ne faisais pas de gestion d'événements. Pour l'instant, il n'est qu'en version "0.9.1" c'est donc certainement un bug de jeunesse, mais à ce stade je ne sais pas si j'ose prendre le risque d'intégrer ce wrapper dans un projet réel et contourner tous les petits problèmes résiduels...

Représentation de données arborescentes à l’aide de D3.js

Introduction

Le but de cet article est de mettre en oeuvre la librairie D3.js pour représenter des données sous forme d’arborescence.

Données

Les données que je souhaite exploiter dans cet exemple sont stockées dans une base de données. La table "MenuItem" contient les éléments suivants :

Le champ "PARENT_ITEM" contient le "ID_MENU_ITEM" d'un élément de menu d'un ordre supérieur.

Cette simple table permet de stocker des menus sous forme arborescente.

Extraction

Etant donné que ces données sont déjà largement utilisées dans des projets Java, je vais écrire un petit programme de test en réutilisant au maximum les routines existantes. Mon but est d'obtenir un fichier JSON pour que D3.js puisse le représenter .

Maven

Dans les projets de production, Maven a été massivement utilisé pour lier les différents librairies et sous-projets. Je vais donc aussi utiliser Maven pour mon projet de test et ainsi importer facilement les librairies dont j'ai besoin et surtout l'accès aux données via hibernate qui va largement me simplifier la vie.

pom.xml

Ce fichier me permet de lier les librairies dont j'ai besoin :

  • JUnit : Pour les tests unitaires.
  • NavigationEntity : Librairie "maison" contenant les entités hibernate en rapport avec les données qui permettent à l'utilisateur de "naviguer" dans le programme.
  • Guice : Outil d'injection de dépendances.
  • Mysql-Connector : Pour accéder à mes données de test
  • XStream : Outils de sérialisation d'objets
  • Jettison : Librairie qui permet à XStream de sérialiser les données au format JSON
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>ch.conceptforge.group</groupId>
    <artifactId>json_d3</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>json_d3</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>NavigationEntity</artifactId>
            <version>2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.inject.extensions</groupId>
            <artifactId>guice-persist</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.14</version>
        </dependency> 
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jettison</groupId>
            <artifactId>jettison</artifactId>
            <version>1.3.2</version>
        </dependency>        
    </dependencies>
</project>

Hibernate

Je ne vais pas entrer trop dans les détails, car c'est trop spécifique à la librairie maison "NavigationEntity". Celle-ci utilise Hibernate et Guice pour se connecter à la base de données et extraire les entités sous forme d'objets. Ceci nécessite de créer un fichier "persistence.xml" et de le configurer pour pouvoir utiliser la classe "MenuItem" qui encapsule les données présentées précédemment dans des objets.

Guice

De même que pour hibernate, cette partie est propre à la librairie maison "NavigationEntity", donc je ne vais pas entrer trop dans les détails. Je vais simplement préciser qu'il est nécessaire de construire une classe qui implémente l'interface "Module" de guice. Pour plus d'informations à ce propos, je vous invite à consulter les documents suivants :

Programme principal

package ch.conceptforge.paquet;
 
import ch.conceptforge.paquet.MenuItemTree;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;
import java.io.Writer;
import javax.persistence.EntityManager;
 
public class App
{
 
    private static EntityManager createEntityManager() {
        Injector injector = Guice.createInjector(new GuiceModule());
        return injector.getInstance(EntityManager.class);
    }
 
    private static XStream createXStream() {
        XStream xstream = new XStream(new JsonHierarchicalStreamDriver() {
 
            @Override
            public HierarchicalStreamWriter createWriter(Writer writer) {
                return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE);
            }
        });
 
        return xstream;
    }
 
    public static void main( String[] args )
    {
        // Liaison de l'entityManager à la classe MenuItem
        MenuItemTree.setEm(createEntityManager());
 
        // Construction de l'arborescence
        MenuItemTree mit = new MenuItemTree();
 
        // Exportation de l'arbre en JSON sur la console
        System.out.println(createXStream().toXML(mit));
    }
}

La partie la plus importante de ce petit programme de test est la construction de l'instance de "MenuItemTree". Le code qui précède cette instanciation permet d’accéder aux entités hibernate. Le passage de l'entityManager par une méthode statique n'est pas élégant, mais, s'agissant d'un petit programme de test, je ne vais pas y remédier. Le code qui suit l'instanciation de "MenuItemTree" permet de sérialiser cet objet. Il suffira ensuite de mettre cette chaine de caractères dans un fichier texte et de la lier à D3.js.

MenuItemTree

Cette classe va s'occuper d'interroger la base de données et de construire l’arborescence d'objets. Dans un esprit de réutilisabilité, j'ai découpé cette classe en deux niveaux d'abstraction.

Tree

Cette classe contient la structure de base d'un arbre qui pourra être utilisé par D3.js

package ch.conceptforge.paquet.tree;
 
import java.util.ArrayList;
import java.util.List;
 
public class Tree {
 
    private String name;
    private List children = null;
 
    public Tree() {
 
    }        
 
    public Tree(String name) {
        this.name = name;
    }
 
    protected void addChild(Tree tree) {
        if (children == null) {
            children = new ArrayList();
        }
        children.add(tree);
    }
 
    public List getChildren() {
        return children;
    }
}

MenuItemTree

Cette classe contient l'implémentation concrète de la structure arborescente pour mes MenuItems.

package ch.ultrasoft.cortex.tree;
 
import com.ultrasoft.server.core.entity.navigation.MenuItem;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
 
/**
 *
 * @author silenus
 */
public class MenuItemTree extends Tree {
 
    private static EntityManager em;        
 
    public static EntityManager getEm() {
        return em;
    }
 
    public static void setEm(EntityManager em) {
        MenuItemTree.em = em;
    }           
 
    public MenuItemTree() {
        super();
        parseItems(em.createQuery("from MenuItem where parentItem = null"));
    }
 
    public MenuItemTree(int id, String name) {
        super(name);
        parseItems(em.createQuery("from MenuItem where parentItem = " + id));
    }
 
    private void parseItems(Query q) {
        for (MenuItem mi : (List) q.getResultList()) {
            addChild(new MenuItemTree(mi.getId(),mi.getText()));
        }
    }
}

JSON

Comme expliqué précédemment, ce petit programme affiche sur la console les données au format json. Voici un extrait :

{
  "children": [
    {
      "name": "Stammdaten",
      "children": [
        {
          "name": "Adressetyp"
        },
        {
          "name": "Bauart"
        },
        {
          "name": "Betriebstyp"
...
        }
      ]
    }
  ]
}

La totalité des données est stockée dans le fichier test2.json

D3.js

A partir des nombreux exemples d'utilisation de cette librairie, voici quelques propositions d'affichage pour mon arborescence.

Le code html n'est pas particulièrement soigné, je souhaitais dans cet article me concentrer sur le rendu visuel des différentes variantes. Au final, une seule sera choisie pour être implémentée dans une application. C'est à ce moment-là que le code sera repris en profondeur.

Environnement de développement pour le PHP

Introduction

De même que pour le C, je suis à la recherche d'un EDI pour le développement PHP. Jusqu'à maintenant, la programmation en PHP était réalisée avec Notepad++. Même si celui-ci est un bon éditeur de code, je lui reproche son absence de debugger. Pour des raisons pédagogiques, j'ai besoin régulièrement d'exécuter pas à pas une routine et de pouvoir observer les différentes valeurs que va prendre une variable.

WampServer

Il y a quelque temps, j'ai écrit un petit article sur WampServer. Nous allons donc utiliser cette installation pour le développement PHP.

Netbeans

Etant donné que l'EDI choisi pour le C est Netbeans, il me parait normal d'essayer d'utiliser Netbeans pour le PHP aussi.

Pour cet essai, nous allons télécharger et installer la version PHP de Netbeans.

Configuration

Avant de pouvoir utiliser le plugin PHP, il faut le configurer. Il s'agit de donner le chemin de l'interpréteur PHP.

Hello World

Comme d'habitude, un petit "Hello World" pour tester l'installation.

Nouveau projet

Nom et emplacement du projet

Attention! Netbeans ne crée pas automatiquement le sous-dossier "HelloWorld"

A ce stade, il est possible de faire "finish" vu que les valeurs par défaut sont utilisées pour les paramètres suivants. Malgré cela, voici la suite des paramètres.


Nous avons maintenant un projet vide. Il s'agit de mettre un peu de code. Pour ce faire, il faut ajouter un fichier "index.php".


Ensuite, il faut mettre le code

Et voici ce que donne le projet lancé à l'aide du bouton "run"

Debugging

En installant WampServer, nous avons aussi installé le debugger "XDebug". Pour l'activer sous Netbeans, il faut juste se déplacer dans le menu de WampServer (PHP/PHP Settings) et activer "(XDebug) : Remote debug".

Ensuite, il est possible d'utiliser le debugger depuis Netbeans :

Conclusion

Bonne nouvelle! Netbeans et son plugin PHP répondent aux besoins. Ils permettent d'éditer le code PHP et de le debugger convenablement.

WampServer

Introduction

Pour les besoins d'un cours PHP, étant donné l'hégémonie de Windows, je vais installer un serveur WAMP.
Le produit, WampServer, a été choisi par mes prédécesseurs, je vais simplement rédiger un petit article concernant son installation.

Installation

Sur le site de WampServer, je choisis l'installation appropriée à l'OS de ma machine de test (32 bits).

En suivant les informations ci-dessus, j'installe les logiciels suivants :

  • Visual C++ 2010 SP1 Redistributable Package"
  • WampServer

Test


=> Le serveur http est disponible sur http://localhost

Premier script PHP

Dans le dossier "C:\wamp\www\MonSite", j'ai ajouté le fichier "index.php" avec le contenu suivant :

<!--?php    echo 'Hello world'; ?-->

Voici le résultat de son appel :

=> OK, mon script est interprété correctement.

Conclusion

L'installation de WampServer est très simple et très rapide. Après quelques minutes, je peux commencer à réaliser des scripts PHP.

Environnement de développement pour le C

Introduction

Depuis quelque temps, je suis à la recherche d'un environnement de développement pour enseigner le C.

Les contraintes sont les suivantes :

  • IDE (pas des produits détachés)
  • Multiplateformes (en tout cas Windows et MacOSX)
  • Debugger intégré
  • Autocomplétion

Recherches

Jusqu'à maintenant, le produit utilisé était Microsoft Visual Studio. Celui-ci n'étant pas multiplateforme, il s'agit de trouver une alternative.

Lors de ma phase de recherche, je me suis penché sur les produits suivants :

Après les avoir tous évalués, j'ai choisi Netbeans. Celui-ci présente, selon moi, les avantages suivants :

  • Utilisé par les étudiants plus tard dans leur cursus pour les cours Java
  • Open Source
  • Multiplateformes
  • Traduit en français

De plus, même si je ne l'ai pas beaucoup utilisé pour écrire du C, j'écris la quasi-totalité de mes projets Java avec. J'ai donc beaucoup d'expérience sur ce produit et je trouve qu'il est de très bonne qualité.

Dans un premier temps, je n'avais pas envisagé Netbeans car une mauvaise expérience avait eu lieu, il y a quelques années. Sous Windows, le compilateur gcc était installé à partir du projet cygwin et cela ne s'est pas toujours très bien passé avec les différentes variantes d'installations.

GCC

Les trois EDI envisagés plus haut nécessitent l'installation séparée d'un compilateur. Voici, par exemple, la liste des compilateurs supportés par Code::Blocks.

Pour débuter, je vais essayer le plus courant de ces compilateurs (gcc) et, s'il ne donne pas satisfaction, j'étudierai des variantes plus exotiques.

MacOSX

Les outils de développement regroupés dans le projet xcode de Apple contiennent le compilateur gcc. Ce qui me dérange c'est que xcode contient beaucoup plus de choses que gcc. Ayant un disque ssd, je préfère économiser un peu de place, donc je me suis mis à la recherche d'un package prêt à l'emploi qui permet de n'installer que le gcc.

Voici ce que j'ai trouvé : gcc-10.6. J'ai choisi la version préconstruite pour mon OS (Snow Leopard) : GCC-10.6.pkg

 Test

Après cette installation, je fais un petit test de gcc :

Je crée le fichier "helloworld.c" dans le dossier /tmp

#include 
 
int main(int argc, char** argv) {
 
    printf("Hello World!\n");
 
    return 0;
}

Je le compile et l'essaye :

Windows

MinGW

Pour éviter les problèmes liés à cygwin cités précédemment, je vais essayer d'utiliser MinGW.

Téléchargement

Installation

Une fois le programme d'installation démarré, je ne choisis que le strict nécessaire : "C Compiler" et "MSYS Basic System".
Voici un aperçu de la dernière page avant l'installation :

Installing:
      mingw-get
      pkginfo
      C Compiler
      MSYS Basic System
 
Using pre-packaged repository catalogues (20120426)
 
Destination location:
      C:\MinGW

L'installation démarre ensuite et télécharge les fichiers dont j'ai besoin. Une fois l'installation terminée, j'ai un dossier "C:\MinGW" de 170 Mb avec le compilateur gcc et tout ce qu'il faut pour faire du développement C sous Windows.

Test

Comme pour l'installation sous MacOSX, je crée le fichier helloworld.c dans le dossier %TEMP% de Windows.

#include 
 
int main(int argc, char** argv) {
 
    printf("Hello World!\n");
 
    return 0;
}

Netbeans

Pour ce test, j'ai choisi de télécharger et d'installer la dernière version de Netbeans (7.2) uniquement avec le plugin C.

MacOSX

Le même magnifique test que précédemment donne un résultat tout aussi concluant.

Windows

L'intégration de gcc sous Windows est un peu plus compliquée que sous MacOSX. Il nous faut la faire manuellement, car Netbeans n'arrive pas à trouver gcc automatiquement comme sous MacOSX.

Path

Selon Wikipedia : "Sous Windows, si un exécutable n'est pas à l'emplacement par défaut, la variable %PATH% indique dans quels répertoires le chercher."

Avant de démarrer Netbeans, il faut modifier la variable path pour que cet EDI trouve les outils de "make" dont il a besoin. Si cette configuration n'est pas réalisée, la compilation ne va pas fonctionner et le message d'erreur "mkdir: Command not found" sera affiché.

Chemin à ajouter dans le PATH :

C:\MinGW\msys\1.0\bin

C plugin

Il s'agit maintenant de configurer le Plugin C de Netbeans pour utiliser le gcc que nous installé avec MinGW.


Test

Une fois ces quelques efforts de configuration terminés, la compilation sous Netbeans fonctionne aussi bien que sous MacOSX.

Conclusion

Netbeans sera utilisé comme EDI pour d'autres langages de programmation (Java et PHP). Il est donc intéressant de pouvoir l'utiliser également pour le C. Cela évite de multiplier les installations et permet de se concentrer sur les langages eux-mêmes plutôt que sur les outils de développement. Je vais donc utiliser cet outil pour mon cours de C.

Maven/Netbeans : Tutoriel simple

Introduction

Prochainement, j'ai envie de présenter quelques tutoriels Java. Un prérequis à ces futurs tutoriels sera l'utilisation de Maven, c'est pourquoi je commence par présenter ce formidable projet. Même si Maven est un sujet maintes fois abordé, je vais en présenter ma propre variante. Plus d'un point de vue pragmatique que théorique.

Documentation

Quitte à passer pour un dinosaure, quand je m'intéresse à un nouveau jouet technologique, j'aime bien lire le mode d'emploi. Dans le cas de Maven, le mode d'emploi que j'ai utilisé est le livre suivant :

Titre : Apache Maven

Auteurs : Nicolas De loof et Arnaud Héritier

Amazon : Apache Maven

Ce livre est très agréable à lire. Il est rédigé dans un style "détendu" tout en fournissant les informations nécessaires à la compréhension et à l'utilisation de Maven.

Il se trouve que les différents sujets et technologies abordés à fil des pages et très proche de plusieurs projets sur lesquels j'ai travaillé ces dernières années:

  • Systèmes de gestion des versions (notamment SubVersion et Git)
  • Testing (JUnit, TestNG et Selenium)
  • Google Web Toolkit (GWT)
  • Serveurs d'application Web Java (Tomcat, Jetty)

Mise en oeuvre de Maven

L'EDI que j'utilise le plus souvent pour du développement Java est Netbeans. Cet environnement de développement peut ouvrir un projet ayant la structure de fichiers Maven et intègre tous les outils nécessaires aux tâches courantes d'un programmeur.

Netbeans 7.1

Création, sous Netbeans, d'un nouveau projet Java en utilisant Maven. Ayant pour habitude de faire des "Hello World" à tout vent, en voici un de plus, le 5ème ces derniers temps ;-)

Création du projet helloworld5


Sorties à la création du projet

cd /Users/silenus/NetBeansProjects; JAVA_HOME=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home "/Applications/NetBeans/NetBeans 7.1.2.app/Contents/Resources/NetBeans/java/maven/bin/mvn" -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.1 -DarchetypeRepository=http://repo.maven.apache.org/maven2 -DgroupId=ch.conceptforge -DartifactId=helloworld5 -Dversion=1.0-SNAPSHOT -Dpackage=ch.conceptforge.helloworld5 -Dbasedir=/Users/silenus/NetBeansProjects -Darchetype.interactive=false --batch-mode archetype:generate
Scanning for projects...
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-deploy-plugin/2.7/maven-deploy-plugin-2.7.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-deploy-plugin/2.7/maven-deploy-plugin-2.7.pom (6 KB at 12.5 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/22/maven-plugins-22.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/22/maven-plugins-22.pom (13 KB at 108.8 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/21/maven-parent-21.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/21/maven-parent-21.pom (26 KB at 150.5 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/apache/10/apache-10.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/apache/10/apache-10.pom (15 KB at 112.9 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-deploy-plugin/2.7/maven-deploy-plugin-2.7.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-deploy-plugin/2.7/maven-deploy-plugin-2.7.jar (27 KB at 149.7 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-site-plugin/3.0/maven-site-plugin-3.0.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-site-plugin/3.0/maven-site-plugin-3.0.pom (20 KB at 165.2 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-site-plugin/3.0/maven-site-plugin-3.0.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-site-plugin/3.0/maven-site-plugin-3.0.jar (112 KB at 460.4 KB/sec)
 
------------------------------------------------------------------------
Building Maven Stub Project (No POM) 1
------------------------------------------------------------------------
 
&gt;&gt;&gt; maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom &gt;&gt;&gt;
 
&lt;&lt;&lt; maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom &lt;&lt;&lt;
 
[archetype:generate]
Generating project in Batch mode
Archetype defined by properties
----------------------------------------------------------------------------
Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
----------------------------------------------------------------------------
Parameter: groupId, Value: ch.conceptforge
Parameter: packageName, Value: ch.conceptforge.helloworld5
Parameter: package, Value: ch.conceptforge.helloworld5
Parameter: artifactId, Value: helloworld5
Parameter: basedir, Value: /Users/silenus/NetBeansProjects
Parameter: version, Value: 1.0-SNAPSHOT
********************* End of debug info from resources from generated POM ***********************
project created from Old (1.x) Archetype in dir: /Users/silenus/NetBeansProjects/helloworld5
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.814s
Finished at: Sun Aug 26 20:41:03 CEST 2012
Final Memory: 9M/81M
------------------------------------------------------------------------


Cool, ils ont pensé à moi chez Sun Oracle. Mon "Hello World" est déjà écrit.

Un petit run

Le "run project (F6)" lance le projet comme s'il s'agissait d'un projet Java standard :

cd /Users/silenus/NetBeansProjects/helloworld5; JAVA_HOME=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home "/Applications/NetBeans/NetBeans 7.1.2.app/Contents/Resources/NetBeans/java/maven/bin/mvn" "-Dexec.args=-classpath %classpath ch.conceptforge.helloworld5.App" -Dexec.executable=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/java -Dexec.classpathScope=runtime process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec
Scanning for projects...
 
------------------------------------------------------------------------
Building helloworld5 1.0-SNAPSHOT
------------------------------------------------------------------------
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.5/maven-resources-plugin-2.5.pom
 
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.5/maven-resources-plugin-2.5.pom (7 KB at 9.1 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.5/maven-resources-plugin-2.5.jar
 
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.5/maven-resources-plugin-2.5.jar (26 KB at 147.7 KB/sec)
 
[resources:resources]
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/shared/maven-filtering/1.0/maven-filtering-1.0.pom
 
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/shared/maven-filtering/1.0/maven-filtering-1.0.pom (6 KB at 46.9 KB/sec)
Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/shared/maven-filtering/1.0/maven-filtering-1.0.jar
 
Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/shared/maven-filtering/1.0/maven-filtering-1.0.jar (42 KB at 94.2 KB/sec)
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
skip non existing resourceDirectory /Users/silenus/NetBeansProjects/helloworld5/src/main/resources
 
[compiler:compile]
Compiling 1 source file to /Users/silenus/NetBeansProjects/helloworld5/target/classes
 
[exec:exec]
Hello World!
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.142s
Finished at: Sun Aug 26 20:58:13 CEST 2012
Final Memory: 10M/81M
------------------------------------------------------------------------

Ajout d'une dépendance

Pour illustrer l'utilité de Maven, je vais ajouter une fonctionnalité à mon beau "helloworld5". Je vais le faire parler Xml. Et, pour ce faire, je souhaite utiliser la librairie XStream.

Habituellement, je modifie directement le pom.xml, mais il existe aussi une possibilité via l'interface utilisateur de Netbeans.


Une recherche de XStream me propose directement les librairies qui pourraient m'intéresser.

Et voilà. Un clic et la dépendance est créée et donc, la librairie utilisable.

Projet de test

Container

package ch.conceptforge.helloworld5;
 
public class Container {
 
    private String text;
 
    public Container() {
    }
 
    public Container(String text) {
        this.text = text;
    }
 
    public String getText() {
        return text;
    }
 
    public void setText(String text) {
        this.text = text;
    }    
 
}

App

package ch.conceptforge.helloworld5;
 
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
 
public class App
{
 
    private static Container c;
 
    private static void dispalyText() {
        System.out.println(c.getText());
    }
 
    private static void displayXml() {
        XStream xs = new XStream(new DomDriver());
        xs.alias("container", Container.class);
        System.out.println(xs.toXML(c));
    }
 
    public static void main( String[] args )
    {
        c = new Container("Hello World!");
        dispalyText();
        displayXml();
    }
}

Nouveau run

cd /Users/silenus/NetBeansProjects/helloworld5; JAVA_HOME=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home "/Applications/NetBeans/NetBeans 7.1.2.app/Contents/Resources/NetBeans/java/maven/bin/mvn" "-Dexec.args=-classpath %classpath ch.conceptforge.helloworld5.App" -Dexec.executable=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/java -Dexec.classpathScope=runtime process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec
Scanning for projects...
 
------------------------------------------------------------------------
Building helloworld5 1.0-SNAPSHOT
------------------------------------------------------------------------
 
[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 0 resource
 
[compiler:compile]
Compiling 2 source files to /Users/silenus/NetBeansProjects/helloworld5/target/classes
 
[exec:exec]
Hello World!
 
  Hello World!
 
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 1.585s
Finished at: Sun Aug 26 21:38:49 CEST 2012
Final Memory: 8M/81M
------------------------------------------------------------------------

Conclusion

Mon but, dans cet article, n'était pas de réécrire le livre sur Maven, que j'ai présenté au début de l'article. Je ne souhaite pas non plus essayer de parler de Maven dans sa globalité.
Maven est présent dans la plupart des projets Java sur lesquels je travaille. C'est un outil qui fait partie de ma vie de tous les jours et qui me rend de grands services. Dans l'immense majorité des projets Java, j'utilise Netbeans comme EDI. Il y a quelque temps, j'ai eu l'occasion d'utiliser Eclipse pour un projet. Je dois avouer que j'ai été très surpris de voir à quel point Maven à l'air d'une pièce rapportée sous Eclipse par rapport à ce que j'ai l'habitude d'utiliser avec le couple Netbeans/Maven.

Serveur Virtuel Slitaz/MySQL

Introduction

Pour les besoins d'un cours sur les bases de données, j'ai créé une machine virtuelle linux contenant le SGBD-R MySQL. Les contraintes sont les suivantes :

  • Minimiser l'espace disque occupé
  • Utiliser le système de virtualisation VirtualBox présent sur les machines (Windows) à disposition
  • Serveur de base de données accessible uniquement en ligne de commande

Création de la machine virtuelle

Sous VirtualBox, créer une nouvelle machine

Avec un disque de 1Gb

Résumé de la machine à créer

Machine virtuelle prête

Paramétrage réseau

Dans un premier temps, le réseau est configuré en NAT. La machine virtuelle va donc se connecter au réseau de l'entreprise et recevoir une adresse IP via DHCP.

CD-ROM

Ajouter un lecteur CD-ROM avec l'image du disque : slitaz-3.0-base.iso

Installation de Slitaz

La distribution Linux choisie pour ce serveur est Slitaz 3.0. Celle-ci à l'avantage d'être très compacte et peu gourmande en ressources.

Démarrage du disque d'installation

Il est important de choisir l'anglais comme langue d'installation, un bug empêche d'utiliser une autre langue.

Le login pour l'installation est root/root

Partitionnement

Le disque dur virtuel de 1Gb créé précédemment est accessible dans /dev/sda

Pour ce faire, il faut utiliser l'antique "fdisk" :

  • Ajouter une partition /dev/sda1 en Linux-Swap de 250Mb
  • Ajouter une partition occupant le reste de l'espace en Linux et rendre cette partition bootable

Formattage

Pour formater les deux partitions, il faut utiliser les commandes suivantes.

root@slitaz:~# mkfs.ext3 /dev/sda2
root@slitaz:~# mkswap /dev/sda1

Téléchargement et montage du CD-ROM d'installation

Malheureusement, un bug dans la routine d'installation empêche d'utiliser l'image du disque utilisée comme disque de boot comme source de données (unable to find rootfs.gz). Pour contourner ce problème, il faut télécharger le disque une nouvelle fois et le monter manuellement.

 Installation de slitaz

Seuls les paramètres nécessitant une configuration sont présentés.

Voilà, l'installation est terminée.

Il faut maintenant éteindre cette machine avant de passer à la suite.

 

Installation des outils

Il faut commencer par retirer l'image du CD-ROM utilisée pour l'installation

Ensuite, il faut redémarrer le système

Après avoir choisi la langue et le clavier, nous sommes prêts pour l'installation des outils

Nano

Installer un éditeur plus confortable que vi.

 

Serveur sftp

Pour pouvoir transmettre des fichiers à la machine virtuelle via SSH

Serveur MySQL

Toute cette installation vise justement à déployer un serveur de base de données MySQL.

Mise en oeuvre des outils

Une fois les outils installés, une petite configuration s'impose pour en avoir une utilisation confortable.

Démarrage automatique de SSH et MySQL

Il s'agit maintenant d'ajouter dropbear et mysql à la liste des daemons à démarrer (variable RUN_DAEMONS)

# /etc/rcS.conf - Initial boot script configuration for SliTaz GNU/Linux.
# Config file used by /etc/init.d/rcS
#
 
# Use udev to populate /dev and handle hotplug events.
UDEV="yes"
 
# Clean up the system removing all tmp and pid files.
CLEAN_UP_SYSTEM="yes"
 
# Filesystems to check integrity of at boot time. You should check the
# rootfs (where SliTaz is installed) and all partitions listed in
# /etc/fstab. Example : CHECK_FS="/dev/hda5 /dev/hdb1"
CHECK_FS="/dev/sda2"
 
# Fast boot into X by setting the system keymap-locale and starting
# the Slim login manager earlier at boot time. If fast X is enabled
# then dbus, hald and slim can be removed from RUN_DAEMONS.
FAST_BOOT_X="no"
 
# Start Kernel log daemons (syslogd and klogd).
KERNEL_LOG_DAEMONS="yes"
SYSLOGD_ROTATED_SIZE="60"
 
# Kernel modules to automatically load at boot time. You can use 'modprobe -l'
# to get a list of all kernel modules available.
#
# For Intel and some Nvidia sound cards : snd_intel8x0 snd_intel8x0m snd_hda_intel
#
LOAD_MODULES="  e1000"
 
# Initialization scripts to run at boot time. Boot order is important,
# bootopts.sh (boot options) must start first, hwconf after network (tazx
# needs an active connection to install Xorg), then you are free to choose.
# Note that the local.sh script exists to let you quickly add some local startup
# commands.
RUN_SCRIPTS="bootopts.sh network.sh i18n.sh hwconf.sh local.sh"
 
# Daemons to start at boot time. SliTaz only provides a few daemons: firewall,
# Web server (lighttpd), SSH server (dropbear) and rsyncd, so boot order is
# not really important, but dbus/hald should be started before slim.
RUN_DAEMONS="dbus hald firewall slim dropbear mysql"
 
# Pre login bold message.
MESSAGE="Welcome to your box."

Démarrage automatique de SFTP

Réseau local virtuel

Il s'agit maintenant de mettre la machine virtuelle dans le réseau virtuel créé par VirtualBox.

Connexion SSH

Dans cette partie, nous allons utiliser la connexion SSH pour administrer le serveur en ligne de commande. Pour plus d'informations à ce sujet, je vous invite à consulter le site du zéro

Accès root

Par défaut, la configuration de Slitaz empêche la connexion en "root". Cette machine virtuelle étant une simple machine de test, nous n'avons pas besoin d'un bon niveau de sécurité. Nous allons donc autoriser l'accès "root".

Ensuite, il s'agit d'enlever les paramètres -w et -g pour le démarrage de dropbear.

Une fois cette configuration terminée, un redémarrage de la machine virtuelle redémarrera aussi le daemon "dropbear" dans le mode qui nous intéresse.

PuTTY, le client Windows

Pour se connecter via SHH au serveur, il existe, par exemple, le logiciel PuTTY.

Transfert de fichiers via SSH

Etant donné que nous avons installé un serveur SFTP, il s'agit maintenant d'utiliser un client Windows pour transférer des fichiers. Il y a, par exemple le  logiciel WinSCP qui permet de le faire.

Ce logiciel fonctionne comme la plupart des clients FTP, la partie gauche présente les fichiers du disque de la machine cliente et la partie droite, le disque du serveur.

Conclusion

Ca y est, nous disposons maintenant d'une machine virtuelle de test pour travailler avec MySQL en ligne de commande. Pour conclure, vous une dernière capture présentant l'occupation disque de toute notre installation :

L'installation est plutôt compacte. 61Mb pour l'OS, les serveurs SSH et SFTP ainsi que le moteur de base de données MySQL.

Exemple de mise en oeuvre de SCXML-JS

Introduction

Voici quelques articles que je parle des machines à états SCXML. Il faut dire que j'ai passé pas mal de temps à les étudier. Le cas que j'ai présenté jusqu'à maintenant (Exemple de mise en oeuvre de SCXML - partie 1) simule une application Java/Swing. Il m'arrive souvent de travailler sur des applications réseau. Celles-ci sont basées sur les technologies du Web. J'utilise beaucoup GWT, Google Web Toolkit, (dont je parlerai prochainement) qui produit une application Web HTML/CSS/JavaScript.

Quand j'ai étudié SCXML, j'avais simulé une application Web. Il s'agissait d'une calculatrice quatre opérations. C'est sur la base de cette étude que j'ai écrit les articles précédents.

Calculatrice

Comme expliqué précédemment, ce tutoriel présente une calculatrice. Cette application d'étude a été écrite il y a quelque temps et j'ai repris cette base pour écrire les articles précédents.

La calculatrice est séparée en deux parties

  • HTML : IHM de l'application Web
  • JavaScript : Machine à états
Voici l'application elle-même : calculator.html

Machine à états

La machine à états de l'exemple de mise en oeuvre de SCXML - partie 1 a été construite à partir de celle-ci. Elle est donc à 90% identique. Les différences sont au niveau du langage. En Java, les listeners s'occupent de surveiller l'activité de la machine et de réagir en fonction des changements d'état. Cette machine-ci utilise JavaScript, il est donc possible d'inclure le code dans la balise <script> de SCXML.

Étant donné le peu de différence entre cette machine et celle du premier tutoriel, je ne vais pas revenir sur le fonctionnement général. Je vais juste reprendre les états et les transitions qui, selon moi, méritent une petite explication et mettre en évidence les morceaux de JavaScript.

Clear

  • La balise <log> produit une trace dans la console du browser
  • <assign> donne une valeur (new String('')) à la variable "result", donc l'initialise avec une chaine vide.
  • <script> contient le code JavaScript "$('#display').val(0)". C'est un appel à la librairie jquery qui aura pour effet de mettre la valeur "0" dans le input ayant l'id "display".

Reset

Remise à zéro des variables op1, op2 et oper.

operand1 / onDigit

Comme dans la première partie de cette série de tutoriels, la transition "onDigit" est déclenchée dès qu'un chiffre est saisi. Le code présenté ci-dessous permet d'ajouter (concaténer) ce chiffre à la variable "op1".

  • la balise <log> affiche dans la console l'objet "_event".

Par exemple après un click sur le bouton "1", voici ce que nous pouvons voir dans la console de Firebug :

Object { name="onDigit", data="1"}

operand1 / received_op1

Mise à jour de l'affichage

onOperation

Mise à jour de la variable "oper".

Compute

Cet état contient le coeur de la calculatrice, le calcul lui-même.

  • La balise <assign> contient le calcul proprement dit. l'expression "eval(op1 + oper + op2)" demande à JavaScript d'interpréter la concaténation de ces trois variables. Avec l'exemple habituel "eval('123' + '+' + '12')" => 135

IHM

L'interface utilisateur de cette application web est réalisée en HTML. Elle est fortement inspirée d'un article de daniweb : A four function calculator (HTML). Par rapport à daniweb, j'ai modifié les points suivants :

  • utilisation de jquery pour simplifier le code javascript
  • intégration de "calculator.js"
  • modification de tous les javascripts pour utiliser la machine à états

Liaison avec la machine à états

Il s'agit maintenant de lier l'interface en HTML avec la machine à états en XML. Pour ce faire, il faut utiliser SCXML-JS.

Télécharger SCXML-JS

Un dépôt SubVersion est mis à disposition et nous devons aller chercher les sources :

svn checkout http://svn.apache.org/repos/asf/commons/sandbox/gsoc/2010/scxml-js/trunk commons-scxml-js

"Compiler" la machine à états

./run.sh --backend state --beautify --ie /path/to/calculator.scxml &gt; /path/to/calculator.js

lier la machine à états à l'IHM

D'abord, il faut faire connaitre à l'IHM le scipt généré ci-dessus

   ...
   <!-- Machine à états (calculator.scxml compilée avec scxml-js) --><script type="text/javascript" src="calculator.js"></script>

Ensuite, il faut créer l'instance JavaScript de la machine à états

...
<!-- Initialisation de la machine à états finis --><script type="text/javascript">// <![CDATA[
    sec = new StatechartExecutionContext();
    sec.initialize();
// ]]></script>

Réaliser une transition

Il est maintenant possible d'implémenter l'événement "onClick" d'un bouton de l'IHM pour qu'il demande à la machine à états de réaliser une transition.

<input onclick="sec.onClear()" type="button" name="clear" value=" C " />

Conclusion

L'application est disponible ici : calculator.html
La machine à états est ici : calculator.scxml

Cet article montre comment utiliser SCXML dans un contexte différent d'une application Java/Swing. En rédigeant ces quelques lignes, je me suis aperçu que, malheureusement, le projet SCXML-JS ne sera pas mis à jour. C'est dommage, j'ai fait de très bonnes expériences avec ce projet. Au moins, le projet SCXML-JS est utilisable et le code source est publié, il nous reste à retrousser nos manches et à le faire évoluer nous-mêmes.

Exemple de mise en oeuvre de SCXML – Code source

Les deux articles précédents (partie 1 et partie 2) faisaient une description détaillée d'une calculatrice quatre opérations qui permettait d'illustrer le développement d'une machine à états "SCXML".

Voici le code source de cette application : code source de scxml_calc.zip

Etant à buts pédagogiques, cette application n'est pas codée selon les exigences d'une application destinée à la production. Ceci étant précisé, je suis ouvert à toute demande de clarification ou suggestion d'amélioration.

Ce code source n'est pas soumis à licence. Vous pouvez utiliser tout, ou partie de celles-ci, dans des projets commerciaux ou non et rien ne vous oblige à en préciser la provenance.