A la découverte de Jet, un outil pour "Pragmatic programers"

Ayant potassé dernièrement le livre « Pragmatic programers » de Andrew Hunt et David Thomas (que je recommande vivement), j'ai trouvé bon de l'utiliser comme introduction de cet article, particulièrement le chapitre sur les générateurs de code dont le leitmotiv est :

« Write Code That Writes Code : Code generators increase your productivity and help avoid duplication. »

Ainsi, j'ai décidé de faire ici une présentation de Jet (Java Emmitter Templates), un outil du projet EMF d'Eclipse dont le but est de faciliter la génération de code. Son fonctionnement est le suivant : il se base sur des fichiers "templates" pour produire n'importe quel type de contenu (xml, java, html) par l'intermédiaire de classes Java.

J'ai choisi, pour cet article, de mettre en pratique Jet dans le cadre d'un plugin Eclipse ajoutant un builder à un projet web, qui créera la page d'accueil du site en fonction de certaines caractéristiques du projet... Tout un programme !

Il faut donc pour commencer, mettre en place un plugin Eclipse constitué d'un builder et d'une nature, qu'il sera possible d'ajouter/supprimer sur un projet du workspace. C'est ce qu'explique la source (5), à l'aide de l'assistant pour le point d'extension en question. En suivant ce tutoriel, vous devriez arriver à mettre en place ce système, comme je l'illustre ici :

A ce stade, le lancement du plugin doit aboutir à l'ouverture d'une nouvelle application Eclipse à l'intérieur de laquelle, si vous créez un projet de n'importe quel type, vous disposerez dans le menu contextuel d'une action pour ajouter/supprimer la nature en question et de ce fait le builder que nous venons de mettre en place.

Il faut maintenant ajouter à notre plugin la capacité de gérer les templates Jet. Pour ce faire, il faut aller dans l'assistant de création d'un nouveau projet, choisir "Converts projects to JET Projects" et sélectionner le plugin.

La première conséquence est l'apparition du dossier "templates" dans notre projet : c'est lui qui accueillera nos fichiers jet. La première chose à faire est de configurer le moteur Jet afin de lui indiquer où générer ses fichiers. Dans les propriétés du projet, on trouve maintenant une section "jet settings" : il faut ici spécifier au moteur de produire ses fichiers au sein de notre classpath, donc dans le dossier "src".

Les différents packages contenant les classes générées seront indiqués plus tard, à l'intérieur même des templates.

Nous pouvons dès à présent écrire le template de notre page d'accueil. Dans le dossier "templates", le fichier "index.htmljet" contient :

<%@ jet package="jetTemplates" class="GeneratedIndex" imports="org.eclipse.core.resources.IProject" %>
<% IProject project = (IProject) argument; %>
... en-tete html ...

I was generated by Pef for Jet tutorial in project <%=project.getName()%>!

...

Le premier attribut "package" de l'en-tête jet (première ligne) indique le nom du package dans lequel sera générée la classe correspondante à ce template, dont le nom est spécifié dans l'attribut "class". Le paramètre implicite "argument" représente le projet du workspace sur lequel s'exécutera le builder.

Une fois ce fichier écrit, nous disposons d'une nouvelle classe, "GeneratedIndex.java", dont nous allons nous servir pour créer physiquement la page d'accueil. Pour ce faire, voici le code à ajouter au builder :

package jetbuilderplugin.builder;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import jetTemplates.GeneratedIndex;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;

public class Builder extends IncrementalProjectBuilder {

 public static final String BUILDER_ID = "JetBuilderPlugin.pefBuilder";

 /*
  * (non-Javadoc)
  * 
  * @see org.eclipse.core.internal.events.InternalBuilder#build(int,
  * java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
  */
 protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
   throws CoreException {
  if (kind == FULL_BUILD) {
   fullBuild(monitor);
  } else {
   IResourceDelta delta = getDelta(getProject());
   if (delta == null) {
    fullBuild(monitor);
   } else {
    incrementalBuild(delta, monitor);
   }
  }
  return null;
 }

 protected void fullBuild(final IProgressMonitor monitor)
   throws CoreException {
  System.out.println("full build!");
  GeneratedIndex gc = new GeneratedIndex();
  String generatedString = gc.generate(getProject());
  IFolder folder = getProject().getProject().getFolder("WebContent");
  String nomFichier = "index.html";
  ecritureFichier(folder, nomFichier, generatedString, monitor);
 }

 protected void incrementalBuild(IResourceDelta delta,
   IProgressMonitor monitor) throws CoreException {
  System.out.println("incremental build!");
 }

 private void ecritureFichier(IContainer container, String nomFichier,
   String contenu, IProgressMonitor monitor) throws CoreException {
  if (!container.exists() && container instanceof IFolder) {
   IFolder folder = (IFolder) container;
   monitor.subTask("Creation du dossier " + folder.getName());
   prepareFolder(folder, monitor);
  }
  try {
   InputStream stream = new ByteArrayInputStream(contenu.toString()
     .getBytes());
   IFile file = container.getFile(new Path(nomFichier));
   if (file.exists()) {
    file.setContents(stream, true, true, monitor);
   } else {
    file.create(stream, true, monitor);
   }
   file.setDerived(true);
   stream.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 private void prepareFolder(IFolder folder, IProgressMonitor monitor)
   throws CoreException {
  IContainer parent = folder.getParent();
  if (parent instanceof IFolder) {
   prepareFolder((IFolder) parent, monitor);
  }
  if (!folder.exists()) {
   folder.create(true, true, monitor);
  }
 }
}

Le plugin est terminé, lançons maintenant l'application finale et créons le projet web. La génération de notre page d'accueil est automatiquement lancée à la création du projet ou à chaque "clean" (puisque positionnée dans le "fullBuild" du builder) :

Il suffit maintenant de lancer le projet web avec une instance de Tomcat pour voir la page d'accueil suivante :

On voit clairement que la page d'accueil indique bien le nom du projet d'où elle provient ! Je vous laisse donc imaginer les possibilités offertes par ce système : génération automatique de squelettes d'applications, réutilisation de code...

J'ai volontairement survolé les détails de cette mise en place de Jet pour plutôt donner un aperçu de tout ce qui est envisageable. Les sources (1) et (2) vous permettrons d'entrer plus dans les détails de l'implémentation.

Pour finir, je précise tout de même que cet article présente la première version de Jet utilisée dans EMF. J'ai récemment observé qu'une nouvelle version a été mise à disposition dans le cadre du "nouveau" projet EMFT, surnommée JET2 et dont le fonctionnement est légèrement différent puisqu'il offre de nombreuses nouvelles possibilités. Le temps de me renseigner plus à ce sujet et je publierai un article décrivant toutes ces nouveautés.

Source

  1. http://www.eclipse.org/articles/Article-JET/jet_tutorial1.html
  2. http://www.vogella.de/articles/EclipseJET/article.html
  3. http://www.linuxtopia.org/online_books/eclipse_documentation/eclipse_jet_development_guide/index.html
  4. http://www.eclipse.org/modeling/emf/docs/architecture/jet2/jet2.html
  5. http://www.eclipsepluginsite.com/builders-natures-markers.html

Fichier(s) joint(s) :

0 commentaires: