Bien comprendre les "generics" de Java

Depuis sa version 1.5, le JDK met à disposition le concept de "generics" ou "types paramétrés". Leur but est de permettre l'ajout d'informations de typage sur des objets pour définir leur domaine d'utilisation.

Un exemple rapide pour comprendre leur usage :

List myIntList = new LinkedList(); 
myIntList.add(new Integer(0)); 
Integer x = (Integer) myIntList.iterator().next(); 

Peut être ré-écrit de manière plus propre :

List<Integer> myIntList = new LinkedList<Integer>(); 
myIntList.add(new Integer(0));
Integer x = myIntList.iterator().next(); 

L'ajout du type paramétré <Integer> permet d'indiquer au compilateur (et accessoirement au développeur!) que notre liste n'acceptera que des Integer. Cependant, ceci n'est qu'une infime partie des possibilités offertes par les type paramétrés, bien plus vastes, puissantes et subtiles...

Wildcards

L'utilisation de la notation que nous venons de voir est très restrictive, comme le montre l'exemple suivant :

public static void main(String[] args) {
 List<String> ls = new ArrayList<String>();
 testMethod(ls);
}

private static void testMethod(List<Object> params) {
 for (Object o : params) {
  System.out.println(o);
 }
}

Ce code ne compilera pas car nous avons clairement décrit que la méthode testMethod n'accepte qu'une liste d'Object. Et ce n'est pas parce que String est un sous-type de Object que List<String> est un sous-type de List<Object>!! Pour résoudre ce problème, il faut utiliser les wildcards ("jokers"). Notre code deviendra donc :

public static void main(String[] args) {
 List<String> ls = new ArrayList<String>();
 testMethod(ls);
}

private static void testMethod(List<?> params) {
 for (Object o : params) {
  System.out.println(o);
 }
}

Mais avec cette notation, nous avons modifié le contrat initialement élaboré par notre méthode. En effet, maintenant, elle est susceptible de recevoir n'importe quel type d'objet ce qui pourrait compromettre son comportement. Un moyen plus subtile d'arriver à notre fin est d'utiliser les types paramétrés contraints ("bounded wildcards") :

public static void main(String[] args) {
 List<String> ls = new ArrayList<String>();
 testMethod(ls);
}

private static void testMethod(List<? extends MySuperClass> params) {
 for (Object o : params) {
  System.out.println(o);
 }
}

Ainsi nous pouvons restreindre l'utilisation de notre méthode aux seuls objets héritant de MySuperClass. Mais cette syntaxe a aussi ses inconvénients : puisque nous déclarons notre méthode comme utilisant "une liste d'objets de type inconnus héritant de MySuperClass", cette dernière ne sera accessible qu'en lecture seule. En effet, le code suivant créera une erreur de compilation :

private static void testMethod(List<? extends MySuperClass> params) {
 params.add(new MySuperClass());
}

Méthodes génériques

Pour aller plus loin avec les types paramétrés, il est possible de mette en place des méthodes génériques, qui pourront être utilisées avec tout type d'objets (très pratiques dans le cas de la réutilisation de code existant) :

private static <T,E> List<E> testMethod(List<T> params) {
 List<E> result = new ArrayList<E>();
 for(T var : params) {
  result.add(transformTtoE(var));
 }
 return result;
}

Cette méthode utilise deux types génériques T et E (définis arbitrairement). Elle retourne une liste du second et prend en entrée une liste du premier. Ainsi, elle pourra très bien être utilisée comme ceci :

List<String> params = new ArrayList<String>();
List<MyBean> list = testMethod(params);

On voit donc l'ampleur des possibilités qu'offre cette extension du JDK. Comme d'habitude, si vous voulez approfondir le sujet, consultez les liens indiqués dans les sources, mon but ici était de mettre en avant par une rapide présentation un autre aspect trop peu utilisé de Java.

Edit (25/01/2013) : Voici une petite méthode pour récupérer le className d'un type générique (via stackoverflow) :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

abstract class MyClass {
  public MyClass() {        
    Type genericSuperclass = this.getClass().getGenericSuperclass();
    if (genericSuperclass instanceof ParameterizedType) {
      ParameterizedType pt = (ParameterizedType) genericSuperclass;
      Type type = pt.getActualTypeArguments()[0];
      System.out.println(type); // prints class java.lang.String for FooClass
    }
  }
}

public class FooClass extends MyClass { 
  public FooClass() {
    super();
  }
  public static void main(String[] args) {
    new FooClass();
  }
}

Les pour et contre

Comme pour toute chose, en lisant quelques documents sur le Net, on se rend vite compte que certaines personnes ont un avis négatif sur ces generics. Il est néanmoins intéressant d'étudier leur point de vue. J'ai tiré cet exemple d'une discussion initiée par un certain Eric Armstrong (source 2). Certes elle commence à dater mais les arguments utilisés n'ont pas vieillis.

Les principaux arguments utilisés par Eric Armstrong sont d'une part la lourdeur de cette syntaxe ainsi que la perte de lisibilité qu'elle entraine et d'autre part le travail supplémentaire qu'elle implique, je cite :

I just called generics "syntactic sugar". In this case, the sugar makes the code more palatable to the compiler, not the coder. You have to pour it on, but you don't get to enjoy it.

Pour résumer, il explique que les generics sont faits pour faciliter l'exécution du code par le compileur et non son utilisation par le développeur. Or, pour avoir plusieurs fois manipulé du code "ancien", non typé, il est vite agaçant d'avoir à caster des objets à tour de bras ou perdre du temps à lire l'implémentation d'une méthode pour savoir comment elle manipule ses paramètres à cause d'une signature trop peu explicite.

Eric Armstrong continue en comparant le syntaxe de Ruby à celle de Java, beaucoup plus permissive légère, mais qui à mon sens comporte aussi plus de risques : l'utilisation des generics pour du "typage fort" des objets permet de mieux cerner leur utilisation et leur évolution, sans que ce soit le compileur qui exécute des actions "magiques". Un intervenant de la discussion le démontre très bien en citant un des dangers déjà présent dans Java, l'autoboxing :

For example, I particularly dislike the automatic type boxing feature:

Integer i = 12; or
int i = new Integer(12);

Yes, it's shorter . More "elegant" than Integer.valueOf(12) according to Erics formula, but it's also wrong and confusing to newcomers. 12 is a primitive type and Integer is an object, they are not equal. This can cause some subtle errors that are hard to notice. For example every time you have an overloaded method that accepts a primitive type and an object, such as in ArrayList:
remove(Object o);
remove(int index);

I had an Integer object "i" and was calling list.remove(i), expecting to remove the object at a specific index (thanks to autoboxing). Of course, the actual method being called was remove(Object) and nothing was being removed, because my Integer object wasn't in the list.

Vous pouvez vous faire votre propre avis, mais il est certain que l'utilisation des generics apporte de réels avantages quant à la maintenance et l'évolution d'une application...

Sources


Fichier(s) joint(s) :



Un peu de culture générale sur la programmation

Parce qu'on ne devient pas un bon développeur en se contentant de "pisser du code" (dixit un des mes anciens profs!), il faut savoir de temps en temps prendre du recul et apprendre des choses plus théoriques, globales sur notre métier.

Cette idée est illustrée par le précepte très répandu "Sharpening the saw", tiré du livre "The 7 Habits of Highly Effective People" de Stephen R. Covey :

There's a guy who stumbled into a lumberjack in the mountains. The man stops to observe the lumberjack, watching him feverishly sawing at this very large tree. He noticed that the lumberjack was working up a sweat, sawing and sawing, yet going nowhere. The bystander noticed that the saw the lumberjack was using was about as sharp as a butter knife. So, he says to the lumberjack, "Excuse me Mr. Lumberjack, but I couldn't help noticing how hard you are working on that tree, but going nowhere." The lumberjack replies with sweat dripping off of his brow, "Yes... I know. This tree seems to be giving me some trouble." The bystander replies and says, "But Mr. Lumberjack, your saw is so dull that it couldn't possibly cut through anything." "I know", says the lumberjack, "but I am too busy sawing to take time to sharpen my saw."

J'ai donc décidé aujourd'hui d'écrire cet article sur la programmation en générale (différents types, classification...). Voici son contenu :

  1. Paradigmes les plus courants
  2. Classification selon les grammaires
  3. Différentes générations de langages

Le but de cet article est d'attirer l'attention sur ces notions. Je vous laisse libres d'approfondir le sujet en suivant les liens à la fin de l'article.

Paradigmes les plus courants

La première façon de classifier les langages de programmation est de les grouper selon leur fonctionnement fondamental. Ici sont donc présentés les 5 principaux paradigmes incluant des langages parmi les plus connus :

  • Programmation déclarative : consiste simplement à décrire les informations utilisées ou leur agencement. Exemples : HTML
  • Programmation séquentielle : l'exécution du programme suit le déroulement des instructions qui le compose. Exemples : PHP
  • Programmation procédurale : se base sur l'appel de méthodes. Une vision un peu plus modulaire de la programmation séquentielle. Exemples : PHP, C
  • Programmation orientée objet : met en relation des entités logicielles représentant des concepts ou des idées indépendants. Exemples : JAVA
  • Programmation évènementielle : contrairement à la séquentielle, le déroulement du code réagit à la réception d'évènements de l'application. Exemples : JavaScript, ActionScript

Classification selon les grammaires

La grammaire d'un langage de programmation est une série de règles définissant sa syntaxe. Java est basée sur une grammaire de type 3 dans la classification de Chomsky.

Noam Chomsky est un linguiste américain qui a beaucoup travaillé sur l'étude des langues naturelles et qui a également fortement contribué à l'évolution de la structuration des langages formels, notamment en informatique. Sa classification peut se résumer ainsi :

  • Les grammaires de type 1, sans contrainte (où chaque phrase est composée d'un seul et unique mot)
  • Les grammaires de type 2, dites contextuelles (chaque phrase est composée d'une partie centrale encadrée de 2 mots)
  • Les grammaires de type 3 ou hors-contexte (il existe plusieurs types de décomposition d'une même phrase à une étape donnée)
  • Les grammaires de type 4, dites régulières (la partie droite ou gauche d'une phrase est elle-même une phrase)

Concrètement, appliqué à Java, c'est sa grammaire de définition qui permet au compilateur de dire que la phrase suivante est juste :

public static void main(String[] args)

Plutôt que :

public void static main(String[] args)

Qui lèvera une erreur de compilation. C'est donc ce système de grammaire qui permet de créer des compilateurs, à l'image des fonctionnalités proposées par les outils Lex et Yacc. Un autre outil plus répandu reposant sur ce même principe : les expressions régulières. En effet, elle permettent de créer une grammaire à base de symboles terminaux, mots, phrases etc permettant de tester des chaines de caractères (des mini-compilateurs en quelque sorte!).

Je ne vais pas m'attarder plus longtemps sur ce sujet, à la fois vaste et très complexe, mais je pense qu'il est bon d'au moins jeter un oeil à ce principe et aux façons dont il peut être appliqué en programmation (notamment en survolant la définition de la grammaire Java). Je fournis plus bas tous les liens nécessaires pour creuser la discussion.

Différentes générations de langages

Pour terminer, il faut savoir que chaque langage appartient à une génération, représentant son niveau d'abstraction :

  1. 1GL (First generation language) : composés uniquement de 1 et 0, ils sont directement interprétés par la machine.
  2. 2GL (Second generation language) : ce sont des langages d'assembleur. Une syntaxe un peu plus intelligible mais directement convertible en langage machine.
  3. 3GL (Third generation language) : regroupe tous les langages structurés. Introduisent les notions de fonctions, d'objets et les paradigmes vus plus haut. Dans les faits, ces langages ne dépendent plus du processeur, mais d'un compilateur spécifique, comme pour Java.
  4. 4GL (Fourth generation language) : ceux-ci ont généralement une syntaxe un peu plus proche du langage naturel, mais sont destinés à des usages assez spécifiques, comme SQL.
  5. 5GL (Fifth generation language) : répondent aux besoins de la programmation par contraintes, plutôt que par algorithme, comme dans le cadre de l'intelligence artificielle.


Sources


Fichier(s) joint(s) :



Manipuler des documents Office avec Java

Quand on travaille sur des systèmes d'informations en entreprise, particulièrement dans le cadre de l'informatique décisionnelle (business intelligence), il arrive souvent que l'on rencontre l'obligation de générer des formats de documents précis pour satisfaire les besoins des utilisateurs. Et parmi les formats les plus courants, la famille Office est certainement la plus pénible redoutée.

Bon nombre de développeurs ont eu à s'arracher les cheveux pour satisfaire les caprices particularités de ces chers documents... Mais heureux ceux qui, comme moi récemment, ont croisé la route de l'API Apache POI.

Et là, force est de reconnaitre que la fondation Apache nous gratifie encore une fois d'un travail remarquable en fournissant tout une bibliothèque libre permettant de lire, écrire et utiliser presque tous les types de documents : Word, Excel, Powerpoint, Publisher...

Si vous parcourez les exemples de code cités plus bas, vous vous rendrez compte à quel point la manipulation de ces formats a été simplifiée, cela en est presque énervant!

Les petits bémols (puisqu'il en faut!) sont : d'une part que POI ne prend pas en charge la gestion des graphiques Excel et se révèle quelques fois réticent lorsqu'il s'agit d'évaluer des formules complexes. D'autre part, le code à utiliser peut parfois paraître lourd ou maladroit, mais cela semble inhérent aux contraintes imposées par Office (à en croire le nom des classes : Horrible Spreadsheet, Horrible Document ou même POI : Poor Obfuscation Implementation). Pour y remédier, il existe Formula One, un outil graphique pour générer des templates de documents Excel utilisables en Java. Il permet de réduire considérablement la quantité de code à écrire de passer outre certaines des difficultés pré-citées.

Sources


Fichier(s) joint(s) :



Gérer rapidement un pool de threads avec notification

Aujourd'hui un rapide bout de code pour montrer quels objets Java utiliser dans le cas où vous avez besoin de mettre en place rapidement un pool de threads.

Le but est d'utiliser un ExecutorService fournit par la classe Executors. Voici comment :

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExportEnginePoolManager implements Observer {

 private static final int poolSize = 10;

 private static int managedThreads = 0;

 private static ExecutorService execSvc;

 private static ExportEnginePoolManager instance;

 private static List managedEngines;

 public static List getManagedEngines() {
  return managedEngines;
 }

 private ExportEnginePoolManager() {
  execSvc = Executors.newFixedThreadPool(poolSize);
  managedEngines = new ArrayList();
 }

 public static ExportEnginePoolManager getInstance() {
  if (instance == null) {
   instance = new ExportEnginePoolManager();
  }
  return instance;
 }

 public void addAndStart(AbstractExportEngine engine) throws Exception {
  if (!execSvc.isShutdown()) {
   engine.addObserver(this);
   execSvc.submit(engine);
   managedEngines.add(engine);
   managedThreads++;
   if (managedThreads == poolSize) {
    execSvc.shutdown();
   }
  } else {
   throw new UnsupportedOperationException(
     "Le nombre maximum de threads simultanés a été atteint!");
  }
 }

 @Override
 public void update(Observable o, Object arg) {
  try {
   AbstractExportEngine aee = (AbstractExportEngine) o;
   if (AbstractExportEngine.TERMINATED_STATUS.equals(aee
     .getStatus())) {
    System.out.println("### " + aee.getName()
      + " state is " + aee.getStatus());
   }
   managedEngines.remove(aee);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

La méthode qui nous intéresse ici est addAndStart. Elle permet d'ajouter à la file d'exécution le thread en question, qui sera automatiquement lancé par l'ExecutorService. Le constructeur privé a ici pour seul but de créer un singleton dans l'application gérant au maximum 10 threads.

Les threads typiques à utiliser ressembleront à :

import java.util.Observable;

public abstract class AbstractExportEngine extends Observable implements
  Runnable {

 private String status;

 private String name;

 public static final String TERMINATED_STATUS = "Terminated";

 public static final String ERROR_STATUS = "Error";

 public AbstractExportEngine(String pName) {
  this.name = pName;
 }

 public void terminate() {
  setChanged();
  notifyObservers();
 }

 public void setStatus(String status) {
  this.status = status;
 }

 public String getStatus() {
  return status;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getName() {
  return name;
 }

 @Override
 public void run() {
  try {
   // actions...
   terminate();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Cette architecture implémente donc le design pattern Observer. Les threads passés au service sont capables de notifier un évènement (en l'occurrence leur fin) à notre manager, qui sera ainsi en mesure d'exécuter une quelconque action à chaque fois qu'un de ses threads est terminé.

Cet exemple aussi simple qu'il y parait a surtout pour but de mettre en lumière le système d'Executors proposé par Java et qui permet de gérer de multiples façons et très simplement plusieurs threads : par paquets, en file... Associé à ce principe, l'utilisation des objets Callable et Future permet d'encore mieux gérer la récupération du résultat des threads, même s'ils ne sont pas applicables dans tous les contextes.

Sources


Fichier(s) joint(s) :



JET2, ou comment faire du vieux avec du neuf...

Comme promis dans mon précédent article sur Jet, j'ai fais quelques recherches sur le tout nouvel outil Jet2 du projet EMFT d'Eclipse, qui est annoncé comme le successeur de Jet. Je dois avouer que j'en reste sur ma faim...

Tout d'abord, quelles sont les nouveautés proposées par Jet2?

Fonctionnellement, trois choses :

  • les templates portent maintenant clairement l'extension ".jet" et ne contiennent plus que le code à générer (plus d'en-tête Jet comme avant...)
  • l'exécution de la génération nécessite un point d'entrée (généralement un fichier "main.jet") qui déclare les templates à parcourir ainsi que les informations portées auparavant par l'en-tête des templates
  • les sources de données sont maintenant sous forme de fichiers xml, sans contrainte aucune. Les templates utilisent XPath pour y récupérer les informations.

Techniquement, trois changements (et c'est là que le bât blaisse...) :

  • le système Jet2 est dépendant du framework Eclipse (!!) : il est donc impossible de l'utiliser de manière "headless", a l'extérieur d'un environnement Eclipse, comme pouvait le faire Jet.
  • du fait du point précédent, le moteur qui opère pour travailler les templates est devenu une boite noire, impossible de simplement appeler la méthode "generate()" de la classe produite ou du processeur principal.
  • un système de taglib a été mis à disposition au sein des templates pour aider à l'écriture (itération, accesseurs, tests...)

Pour résumer, jusqu'ici je ne vois pas vraiment de réelles nouveautés, ou en tout cas de quoi vraiment attiser ma curiosité. J'ai tout de même cherché à mettre un peu en pratique tout cela pour voir vers quel(s) concept(s) ce système s'oriente. Pour essayer de faire un parallèle avec l'exemple que nous avions vu pour Jet, j'ai ici voulu créer une classe qui affiche le nom du projet dans lequel elle se situe... Voici donc la structure de base d'un nouveau projet Jet2 :

Comme je l'ai indiqué plus haut, on retrouve le fichier "main.jet", point d'entrée de la transformation. Son contenu est le suivant :

<%@taglib prefix="ws" id="org.eclipse.jet.workspaceTags" %>
<%-- Main entry point for jet2Project --%>


    

Comme on le voit, il pointe sur le fichier "projectIntrospectionTemplate.jet" qui contient le code ma classe. L'attribut "path" indique le fichier de destination (à la place de l'en-tête Jet)

Le contenu de mon template :

package com;
public class ProjectIntrospection {
 public static void main(String[] args){
  System.out.println("This class project name : ");
 }
}

On voit ici que le contenu est épuré de toute meta-information pour le moteur. Puisque je cherche à connaitre le nom du projet, j'ai fournit comme source de données au template le fichier ".project" d'Eclipse (qui n'est ni plus ni moins que du xml) :



 jet2Project
 
 
 
 
...

La balise "c:get" de mon template permet d'accèder grâce à XPath à la valeur du nom du projet.

Pour lancer la transformation, il faut créer une Run Configuration :

C'est ici qu'il faut indiquer la source de données. Elle sera automatiquement passée en paramètre du moteur. Voici donc le résultat :

package com;
public class ProjectIntrospection {
 public static void main(String[] args){
  System.out.println("This class project name : jet2Project");
 }
}

Au final, rien de vraiment fulgurent... Ce que je regrette donc le plus dans ce nouveau système est le fait qu'il soit dépendant du framework Eclipse, je ne comprends pas vraiment l'intérêt. De plus, je doute du réel "plus" fournit pas l'utilisation des balises : cela contraint à l'emploi de fichiers xml en entrée et par dessus tout, cette technique me semble empiéter beaucoup sur le territoire de Groovy. Pour ceux qui n'en ont pas entendu parler, je vous conseille d'aller lire quelques articles sur cet outil et vous vous apercevrez rapidement à quel point Jet2 semble s'en rapprocher.

J'invite donc tous ceux qui ont déjà travaillé avec Jet2 à donner leur avis, car j'ai l'impression qu'avec cette nouvelle version, la philosophie première de Jet (en tant qu'outil "pragmatique") se perd un peu...

Sources

  1. http://www.ibm.com/developerworks/opensource/library/os-ecl-jet/
  2. http://help.eclipse.org/ganymede/index.jsp?topic=/org.eclipse.platform.doc.user/tasks/antRunner.htm : Une tâche Ant qui permet de s'extraire de l'interface graphique d'Eclipse
  3. http://weblogs.java.net/blog/2009/01/04/template-code-generator-apache-velocity-jet-jet2
  4. http://www.linuxtopia.org/online_books/eclipse_documentation/eclipse_jet_development_guide/index.html

Fichier(s) joint(s) :



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) :



Tout savoir et plus sur le pattern IAdaptable du framework Eclipse

Dans cet article je vais essayer d'expliquer à quoi sert et comment fonctionne le pattern IAdaptable d'Eclipse. Pour aller plus loin dans sa compréhension, il m'a également paru opportun de décrire plus en profondeur le principe du design pattern Adapter et de ses accolytes (design patterns structuraux) : Proxy, Facade, Bridge et Decorator. Je ne vais pas ici faire un cours complet sur ces pattern mais simplement les présenter et montrer par quelles analogies/distinctions subtiles on peut les rapprocher.

Pour ceux qui sont pressés de comprendre l'implémentation codée, j'ai choisi de la présenter en premier (je fais partie des gens qui préfèrent lire un code avant de se perdre dans la théorie !). Voici donc le plan de cet article :

  1. IAdaptable : une implémentation du design pattern Adapter
  2. Intérets et rôles
  3. Lien avec le design pattern Bridge
  4. Lien avec les design pattern Decorator, Proxy et Façade
  5. Sources

IAdaptable : une implémentation du design pattern Adapter

Qu'est-ce que le pattern IAdaptable et quel est son but ? Une première réponse conscise à cette question, avant de le présenter plus en détail dans la suite de cet article, consiste à dire que IAdaptable est l'implémentation, dans le framework Eclipse, du design pattern Adapter. Son rôle est de permettre à une application d'utiliser de manière transparente des objets dont les interfaces ne sont à priori pas compatibles.

L'adapter va donc d'une part implémenter l'interface de l'objet désiré (afin de répondre au contrat attendu) et d'autre part instancier l'objet qui doit être adapté. Une explication concrète est donnée dans la source (2) avec l'exemple d'une clef a cliquet (soyons pragmatiques !). Les sources (1) et (3) quant à elles fournissent plusieurs extraits de code illustrant la mise en place d'un adapter dans plusieurs contextes.

Intérêts et rôles

Le principal intérêt de ce pattern est de permettre la réutilisabilité d'un code hérité : pendant un développement, il peut s'avérer indispensable de récupérer des objets existants, mais leur comportement ne colle pas toujours avec ce qui est nouvellement mis en place. La création d'adapteurs comble donc le manque d'interfaces compatibles, sans avoir à modifier le code existant : c'est cette nouvelle couche d'abstraction qui liera les deux systèmes.

Un autre aspect important est qu'il permet de structurer les relations entre classes en respectant la loi de Déméter : grâce à l'interface qu'il fournit au client, un objet qui implémente Adapter autorise l'accès à ses propriétés internes sans pour autant créer de couplage fort avec le client.

Lien avec le design pattern Bridge

Maintenant que nous connaissons mieux le pattern Adapter, nous allons voir en quoi il ressemble et est à la fois très différent du pattern Bridge.

Le principe de ce dernier est de découpler l'interface de l'implémentation, afin que chacune puisse évoluer indépendamment. Mais contrairement à Adapter, cette organisation est mise en place avant le développement, pour architecturer les objets et leurs comportements et non pas les adapter les uns aux autres. Pour faire simple, alors que Adapter est parfois plus connu sous le nom de Wrapper, Bridge est quant à lui plutôt nommé Driver. La source (4) expose ces relations et fournit des liens vers Bridge pour comprendre tous les mécanismes.

Lien avec les design pattern Decorator, Proxy et Façade

L'idée propagée par Decorator est de pouvoir attacher dynamiquement de nouvelles responsabilités à un objet : son interface initiale n'est jamais modifiée, mais de nouveaux services lui sont rendus disponibles. Il est donc une alternative simple à l'héritage. On voit donc la différence avec Decorator qui enveloppe l'objet pour lui fournir une nouvelle interface.

Proxy quant à lui a pour but de fournir une couche intermédiaire de manipulation entre le client et l'objet mais en reprenant la même interface que celle de l'objet. Il permet donc de rajouter quelques fonctionnalités lors des accès comme des contrôles de sécurité ou de "lazy instantiation".

Enfin, le pattern Facade peut être vu comme une extension de Adapter, puisqu'il permet l'accès et la manipulation de plusieurs objets avec un seul point d'entrée commun.

[Edit]

Comme il m'a été suggéré plusieurs fois ces derniers jours, je rajoute à mon article une présentation de l'AdapterFactory disponible dans le framework Eclipse.

Comme je l'ai décrit plus haut, un Adapter utilise d'un côté l'interface attendue par le client et implémente d'un autre côté l'objet adapté. On voit donc qu'il existe un couplage "fort" entre ces trois entités.

Pour pallier à ce problème, la plateforme Eclipse RCP fourni un système d'AdapterFactory qui consiste en la chose suivante : des fichiers de configuration permettent de déclarer d'une part la factory elle-même et d'autre part les objets dont elle sera responsable. Ainsi, elle pourra être interrogée comme un service par le client : "je dispose de cet objet, fourni moi l'adapter dont j'ai besoin par rapport à l'interface que j'utilise".

Cette configuration est détaillée dans les sources (3), (9) et (10) que je vous laisse découvrir pour ne pas les plagier. L'intérêt principal de ce système est de laisser intacts les objets métiers, sans même devoir leur faire implémenter IAdaptable, au profit d'une gestion de plus au niveau, au runtime du framework.

Sources

  1. http://userpages.umbc.edu/~tarr/dp/lectures/Adapter-2pp.pdf
  2. http://sourcemaking.com/design_patterns/adapter
  3. http://www.eclipse.org/resources/resource.php?id=407
  4. http://www.c2.com/cgi/wiki?AdapterPattern
  5. http://www.c2.com/cgi/wiki?HandleBodyPattern
  6. http://www.vogella.de/articles/DesignPatternAdapter/article.html
  7. http://fishbolt.org/java/org.fishbolt.common/doc/article.en.html
  8. http://www.mcdonaldland.info/2007/11/28/40/
  9. http://wiki.eclipse.org/FAQ_How_do_I_use_IAdaptable_and_IAdapterFactory%3F
  10. http://www.eclipse.org/articles/article.php?file=Article-Adapters/index.html

Fichier(s) joint(s) :