Java et la programmation fonctionnelle

Maintenant que je commence à prendre un peu plus en main la programmation fonctionnelle, comme vu dans mes précédents articles sur Scala ou mon projet Scalable Explorer, je dois reconnaitre qu'elle apporte une nouvelle façon de penser et surtout des facilités d'écriture (grâce à "l'intelligence" des compileurs) non négligeables. Le passage de Java à Scala est donc relativement aisé et permet une ouverture d'esprit pour la compréhension et la prise en main des nouveaux concepts.

Armés de ces nouveaux outils, faisons un petit retour en arrière... Qu'en est-il de la programmation fonctionnelle en Java? Il est impossible que ces concepts ne puissent pas lui être appliqués, même si on connait la rigueur de ce langage, qui en est devenue une caractéristique majeure. Je vais donc ici exposer comment certains principes fonctionnels peuvent être reproduits dans le monde Java.

Les frameworks

Il existe différents frameworks apportant les outils ou la syntaxe nécessaires à la mise en oeuvre des concepts fonctionnels : fun4j ou Functional Java sont les plus répandus, mais ils ont du mal à s'imposer : peu documentés et parfois mal maintenus, ils ne paraissent pas être la meilleure alternative.

Les "Functors"

La fondation Apache a mis en place les "Apache Commons Collection Utilities", une collection de librairies et d'objets, dans le but d'apporter une "functional touch" au code Java. Ces "functors" ou "functional objects" sont en fait un lot d'interfaces pour créer de nouveaux comportements, essentiellement pour le traitement des collections. Trois concepts majeurs donc : Transformer, Closure et Predicate.

Les Closures

Les closures sont des fonctions qui vont être exécutées sur tous les objets d'une liste. On peut ainsi créer rapidement des traitements de groupe (utiliser ou modifier les objets de la liste, sans modifier le contenu de la liste elle-même). Par exemple :

public static void main(String[] args) {
   List<String> list = Arrays.asList("Apache!","Java!","Functors!");
   CollectionUtils.forAllDo(list, new Closure() {  
     public void execute(Object o) {
       System.out.println(o.toString().replace("!","..."));  
     }  
   });
}
/* Sortie :
Apache...
Java...
Functors...
*/

Les Transformers

Le but ici est de créer une liste d'objets à partir d'une autre, totalement différente (pratique dans le cas de conversions de bean par exemple) :

public static void main(String[] args) {  
  Collection<String> strings = Arrays.asList("Optimus Prime", "Bumblebee", "Megatron", "The fallen");  
  Collection<Autobots> bots = CollectionUtils.collect(strings, new Transformer() {  
      public Object transform(Object o) {  
          return new AutoBot(o.toString());  
      }  
  });  
  CollectionUtils.forAllDo(bots, PrintIt.getInstance() );  
}
/* Sortie :
Optimus Prime à vos ordres!
Bumblebee à vos ordres!
Megatron s'est réveillé...
The fallen prendra sa revanche!!
*/

Les Predicate

Ils sont utilisés principalement pour filtrer des listes : leur rôle est de simplement tester un objet et renvoyer true ou false selon que l'objet passe ou non le filtre :

public static void main(String[] args){
  List<Integer> peopleAges = Arrays.asList(5,18,9,24,35,44,11);  
  Collection adults = CollectionUtils.predicatedCollection(peopleAges,  
      new Predicate() {  
          public boolean evaluate(Object o) {
              Integer num = (Integer) o;  
              return num >= 18;
          }  
      }); 
  System.out.println("Adultes de la liste : ");
  CollectionUtils.forAllDo(numbersOnlyList, PrintIt.getInstance() ); 
}
/* Sortie :
Adultes de la liste : 
18,24,35,44
*/

La librairie Apache fournit également un certain nombre de prédicats prédéfinis pour ce genre de test simple (nullité, comparaison...). Il est aussi possible de créer des prédicats plus complexes grâce à des combinaisons, opérées par les interfaces du type Predicates.or ou Predicates.and.

Pour terminer, je vous conseille fortement de lire le livre Common Java Cookbook traitant de ces librairies communes qui facilitent la manipulation des tableaux, collections, strings, dates et autres objets récurrents en Java.

On voit donc que la programmation fonctionnelle pure est loin de vraiment voir le jour en Java : il faut reconnaitre que sa grammaire même (et donc son compilateur) ne s'y prête pas vraiment, puisqu'au fond ce qui caractérise la programmation fonctionnelle est, au delà des concepts qui peuvent être reproduits en Java comme nous venons de le voir, c'est sa syntaxe : le compilateur doit autoriser certaines souplesses et doit être capable de deviner (inférence) tout ce qui ne relève pas de l'algorithme (typage des variables, cast...)

Sources


Fichier(s) joint(s) :



ScalableExplorer : libre et ouvert... à la critique!

Voilà quelques jours que j'ai commencé à m'exercer un peu plus avec Scala et je dois dire que j'en suis très satisfait! Afin de tester un maximum de fonctionnalités du langage, j'ai décidé de créer un projet d'explorateur de fichiers, qui est donc en cours de développement et auquel j'ajouterai des fonctionnalités régulièrement.

Pour l'occasion, j'ai mis en place un espace SourceForge afin de permettre à tout le monde d'accéder aux sources : l'intérêt est de permettre à qui le veut de jeter un oeil au code que j'ai écrit afin de découvrir les fonctionnalités de Scala à l'oeuvre, de me corriger ou de donner son avis, voire de contribuer à l'avancée du projet! ;)

L'application se nomme Scalable Explorer et est dors et déjà disponible. N'hésitez pas à aller consulter les fichiers, j'ai volontairement essayé d'exploiter au mieux la syntaxe et les outils de Scala. Brièvement, les fonctionnalités disponibles :

  • Arborescence des fichiers avec "lazy-loading"
  • Fenêtre d'aperçu pour les fichiers graphiques et textuels
  • Barre de menu avec raccourcis clavier
  • Menu contextuel (clic-droit) sur l'arbre (JTree)

Comme décrit sur SourceForge, ce projet est bien entendu en phase naissante, "pre-alpha" et est donc voué à largement évoluer dans les semaines à venir. Une fois de plus, si vous êtes intéressé(es), je vous invite à participer, que ce soit en commentant ce qui est mis en place ou en ajoutant des fonctionnalités! Pour information, le projet est développé sous Netbeans et peut être récupéré via SVN ou CVS.

[Edit]Nouvelles fonctionnalités au 18/11/10 :

  • Autoscroll sur le Jtree
  • Drag and drop pour le déplacement de fichiers

Fichier(s) joint(s) :



Java et Business Intelligence avec JasperSofts

Pour faire suite à mon article sur la manipulation de documents Office avec Java et rester dans le domaine de l'informatique décisionnelle, voici une présentation de l'outil JasperReports et de son éditeur graphique iReport de la société JasperSofts. Cet exemple se base sur la dernière version (3.7.6 à l'heure de l'écriture) qui offre de nombreuses facilités quant à la mise en place de rapports.

Je vais fournir dans cette article tout le code et les fichiers nécessaires à reproduire l'exemple. En pièce jointe se trouve un lien vers le workspace que j'ai utilisé (diminué des librairies nécessaires à JasperReports pour des raisons de volume d'upload). Je vais donc décrire ici les quelques subtilités que j'ai rencontrées mais le rapport mis en place se compose de la majorité des éléments couramment recherchés (sous-rapport, diagrammes, listes, tableaux croisés, styles, temps d'évaluation...), donc il vous suffira de l'explorer de fond en comble pour trouver votre bonheur! :)

J'ai choisi de créer un rapport au format PDF car à mon sens il est celui qui propose le meilleur rendu pour les différents composants disponibles. Les objets utilisés dans le code sont volontairement simplistes pour ne pas compliquer la compréhension de leur organisation et de leur utilisation dans le modèle.

Voici un aperçu du rapport final créé pour cet article :

Regardons le code de la classe principale qui permet de le générer (Reporter.java) :

package com.developpef;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.FileResolver;

public class Reporter {

private static final String ENCOURAGEMENT = "Bonne lecture!";

private static final String CLASSIQUE = "Hope this helps...";

public static void main(String[] args) {
 List<TestBean> beans = new ArrayList<TestBean>();
 beans.add(getBean(ENCOURAGEMENT));
 beans.add(getBean(CLASSIQUE));

 try {
  FileResolver fileResolver = new FileResolver() {
   @Override
   public File resolveFile(String fileName) {
    URI uri;
    try {
     uri = new URI(Thread.currentThread()
       .getContextClassLoader().getResource(
          "com/resources/" + fileName).getPath());
     return new File(uri.getPath());
    } catch (URISyntaxException e) {
     e.printStackTrace();
     return null;
    }
   }
  };
  HashMap<Object, Object> parameters = new HashMap<Object, Object>();
  parameters.put("REPORT_FILE_RESOLVER", fileResolver);
  URL jasperModelUrl = Thread.currentThread().getContextClassLoader()
    .getResource("com/resources/jasper/reportTest.jasper");
  URL resourceFolder = Thread.currentThread().getContextClassLoader()
    .getResource("com/resources/jasper/");
  InputStream stream = new FileInputStream(new File(jasperModelUrl
    .toURI()));
  JRBeanCollectionDataSource datasource = new JRBeanCollectionDataSource(beans);
  JasperPrint jasperPrint = JasperFillManager.fillReport(stream,
    parameters, datasource);
  File finalPdfPath = new File(resourceFolder.toURI().getPath()
    + "reportTest.pdf");
  JasperExportManager.exportReportToPdfFile(jasperPrint, finalPdfPath
    .getAbsolutePath());
  Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler "
    + finalPdfPath.getAbsolutePath());
 } catch (Exception e) {
  e.printStackTrace();
 }
}

private static TestBean getBean(String conclusion) {
 TestBean bean = new TestBean();
 bean.setAuthor("Developpef");
 bean.setHomeUrl("http://developpef.blogspot.com/");
 bean.getStatsHolder().getStats().add(new StatBean("Mozilla", 60));
 bean.getStatsHolder().getStats().add(new StatBean("IE", 25));
 bean.getStatsHolder().getStats().add(new StatBean("Others", 15));
 bean.getStatsHolder().getStatsOS().add(new StatBean(502, "Windows"));
 bean.getStatsHolder().getStatsOS().add(new StatBean(158, "Linux"));
 bean.getStatsHolder().getStatsOS().add(new StatBean(12, "Other Unix"));
 bean.getArchives().add(new ArchiveBean("Eclipse", "Septembre", 2));
 bean.getArchives().add(new ArchiveBean("Eclipse", "Octobre", 7));
 bean.getArchives().add(new ArchiveBean("Java", "Septembre", 10));
 bean.getArchives().add(new ArchiveBean("Java", "Octobre", 3));
 bean.getArchives().add(new ArchiveBean("Java", "Novembre", 6));
 bean.getJavaBeans().add(new StatBean(null, 0));
 bean.getJavaBeans().add(new TestBean());
 bean.getJavaBeans().add(new ArchiveBean(null, null, 0));
 bean.getJavaBeans().add(new StatsHolder());
 bean.setJavaTxt1("Ce texte est issu de Java et fait donc partie du contenu "
   + "dynamique du rapport. "
   + "Seuls les quelques POJO listés ci-dessous ont suffit à créer "
   + "tous les graphiques et autres listes présentés ici :");
 bean.setJavaTxt2("Ce rapport assez simple ne se compose que d'une seule page mais est"
   + " édité pour 2 éléments, comme le prouve le code barre basé sur le hashCode"
   + " de ce texte. " + conclusion);
 return bean;
}
}

Tout commence par la création de la liste de beans qui sera passée comme source de données au rapport. Un rapport sera créé pour chaque bean, ce qui dans cet exemple résultera en un PDF de 2 pages. Ces beans doivent être différentiables par le template, c'est la raison pour laquelle il est fortement conseillé de surcharger les méthodes equals() et hashCode() sous peine de voir le rapport répété X fois pour les même données. Ici la distinction se fait grâce à la toute dernière phrase du rapport (variables statiques).

L'en-tête contient une image de fond. Pour permettre au moteur du template de pouvoir la résoudre dans notre classpath et l'insérer dans le PDF, il est indispensable de lui injecter un FileResolver lui permettant d'accéder à tous les fichiers nécessaires (images, sous-rapports...). Pour cela, il faut utiliser le ContextClassLoader afin de parcourir les ressources. Le paramètre fileName correspond au chemin des éléments indiqué dans le template ("images/..." ou "jasper/..").

Il faut également savoir que JasperReports se base sur tout un tas de librairies standards pour créer ses composants. Si vous rencontrez quelques difficultés, il vous suffit de vous rapporter aux documentations respectives :


Fichier(s) joint(s) :



Une Pizza aux 3 langages...

Après avoir lu pas mal d’articles sur Scala et les generics de Java, j’ai essayé de trouver un lien entre tout cela. Pour ce faire, rien de plus simple (!) : prenez quelques tranches de Scala ("high-order functions"), ajouter des bons morceaux de généricité (polymorphisme paramétrique), déposez le tout sur une bonne pâte (programmation fonctionnelle) et introduisez dans un four thermostat 100 (compileur Java). Vous obtiendrez : Pizza !

Plus sérieusement, voici concrètement ce qu’est Pizza, son origine et ses implications dans le monde Java.

Le projet Pizza

"Back to the 90’s". 1995 exactement. Martin Odersky et Niklaus Wirth étudiants à l’Ecole Polytechnique Federale de Lausanne (Suisse), à l’origine des versions 1.1 à 1.4 du compileur Java, décident de travailler sur un langage fonctionnel compilable en bytecode Java. C’est la naissance du projet Pizza. Le but était de créer un langage à la fois objet et fonctionnel, sans les restrictions de Java. Le premier résultat fût Funnel, un langage minimaliste basé sur les "functional nets" mais qui s’est finalement révélé peu utilisable. Scala est donc issu de la seconde mouture de leurs recherches, plus pragmatique et centré sur l’interopérabilité avec les plateformes standards. Il n’est donc pas exactement une extension de Java même s’il lui est totalement compatible et comparable en terme de performances. La première distribution publique de Scala est née en 2003, sa seconde version actuelle est apparue en 2006.

Implications dans le monde Java

Pizza peut donc être vu comme une supercouche de Java (toute application Java est de facto compatible Pizza), lui ajoutant trois principales fonctionnalités souvent réclamées par la communauté : le polymorphisme paramétrique, les fonctions de haut-niveau ("function pointers") et les types de données algébriques ("case classes" et "pattern matching"), sujets sur lesquels je reviendrai plus loin dans cet article. Pizza essaie donc de retranscrire ces concepts dans le monde Java, autant de manière abstraite que concrète, par transposition (adaptation des concepts) ou définition (nouvelles JSR). Comme le dit son concepteur, "Pizza fits smoothly to Java, with only a few rough edges.". C’est notament grace à la JSR-014 que les generics ont fait leur apparition dans Java et qu’ils sont toujours étudiés par l’intermédiaire du compileur étendu de Java : GJ (Generic Java Language).

Détail des concepts majeurs

Le polymorphisme paramétrique

Un exemple simple et rapide pour comprendre de quoi il s’agit (en Java). Le code suivant est syntaxiquement bon :

List<String> lstr = new ArrayList<String>() ;

Alors que celui-ci ne compilera pas :

List<Object> lstr = new ArrayList<String>() ;

En effet, comme nous l’avons vu dans mon précédent article sur les generics, les types paramétrés ne sont pas covariants! C’est donc ceci que veut résoudre Pizza par l’apport du polymorphisme paramétriques : autoriser l’utilisation de l’héritage dans les generics.

Fonctions de haut-niveau

Autrement appelées "Higher-order functions" ou "functions pointer" en C par exemple, elles peuvent être définies simplement comme des functions acceptant d’autres fonctions en paramètre et étant capables de retourner une fonction en résultat. Un exemple simple en Scala :

val numbers = Array(1,2,3,4,5)
val fact = numbers.reduceLeft(_*_)
println("The factorial of five is :" + fact)

Ici, Array.reduceLeft est une méthode de haut-niveau, à laquelle on indique de parser tous les éléments du tableau et de les traiter deux par deux avec la méthode que nous lui passons _*_ qui peut se comprendre comme x*y.

Types algébriques

En termes de programmation fonctionnelle, un type algébrique est un type de donnée qui peut être instancié de plusieurs façons, notamment avec d’autres types disposant du même genre de constructeur et étant des sous-types du premier. Un petit exemple pour éclaircir cette définition. Supposons que nous disposons des classes ci-dessous (Scala) :

abstract class Tree[+T]
case object Leaf extends Tree
case class Node(elem :T, left :Tree[T],right :Tree[T]) extends Tree[T]

Nous pourrons créer la structure suivante :

Grâce à cette "simple" instanciation :

val tree = Node("F", Node("B",Node("A",Leaf,Leaf),Node("D",Node("C",Leaf,Leaf),Node("E",Leaf,Leaf))),Node("G",Leaf,Leaf))

Ces classes sont appelées "case classes" puisqu’elle permettent d’être évaluée avec le mot clé "case" afin de déterminer facilement leur type (par décomposition). Un autre exemple. Soit la classe :

case class Person(firstName :String, lastName :String)

Instanciée de deux manières :

val mark = Person("Mark", "Thomas")
val henry = Person("Henry", "Hudson")

Ces deux instances peuvent faire l’objet d’une évaluation simple avec la syntaxe suivante, pour par exemple rechercher un membre de la famille "Thomas" :

person match {
 case Person(firstName, "Thomas") => println("You are one of my family member "+firstName)
 case _ => println("Nice to meet you" + firstName)
}

Cette méthode est appelée "pattern matching". Un exemple un peu plus concret est donné sur le blog de Martin Odersky lui-même :

try {
  ...
} catch {
  case ex: IOException => "handle io error"
  case ex: ClassCastException => "handle class cast errors"
  case ex: _ => "generic recovery"
}

Nous venons donc de voir en quoi Scala est un langage fonctionnel et quels sont les avantages qu’il apporte à Java en matière d’architecture ou de manipulation des données. Je vous invite à lire les sources ci-dessous car elles apportent une nouvelle façon de voir et de penser complètement différente de la programmation impérative, très utile pour prendre du recul sur sa façon de travailler...

Sources


Fichier(s) joint(s) :



Débuter avec la programmation fonctionnelle

Cet article fait suite à celui que j'ai récemment écrit sur Scala. Etant débutant avec ce langage et le concept de programmation fonctionnelle, je me suis rendu compte qu'il me manquait certaines notions pour aborder sereinement la découverte de Scala. J'ai donc décidé de trouver des ressources expliquant clairement ces principes et là il y a de quoi baisser rapidement les bras...

Tous ceux qui ont essayé de se lancer dans la compréhension de la programmation fonctionnelle se sont sûrement heurtés, comme moi, à des articles ou tutoriels assez obscures, voire même abscons, car souvent trop élitistes ou destinés à un public industriel ou mathématicien...

Après plusieurs heures de recherches à travers plusieurs pistes, j'ai enfin trouvé quelques perles rares : des articles clairs, exhaustifs, à vocation pédagogique et destinés à des développeurs! Je vous conseille donc vivement d'aller les consulter, cela éclaircit grandement les idées. Bonne lecture!

Sources


Fichier(s) joint(s) :



Premiers pas avec Scala

[Edit]
Pour ceux qui sont arrivés sur cette page et qui débutent avec la programmation fonctionnelle, je vous conseille d'aller lire rapidement mon article à ce sujet pour trouver les donnes ressources pour apprendre toutes les bases et principes nécessaires par la suite.

Le crédo de tout bon développeur est de rester attentif aux nouveaux langages ou outils faisant leur apparition et pouvant lui permettre d'améliorer son travail. C'est la raison pour laquelle j'ai décidé de me lancer dans l'apprentissage du langage Scala...

Pourquoi celui-ci en particulier? Tout simplement car il est le plus proche de Java, au point qu'il est prévu pour s'exécuter dans une JVM et que son bytecode est compatible avec celui de Java (autrement dit, les deux langages peuvent s'interfacer!).

L'intérêt principal de Scala est qu'il est beaucoup moins verbeux que Java et comble certaines lourdeurs de syntaxe, comme par exemple :

// En Java
for(int i=0; i<10; i++){
  System.out.println(i);
}

// En Scala
for(i<-List.range(0,10)) println(i)

ou encore :

// En Java
public static String returnStr(Object data) {
  String result = "";
  if(data!=null){
    result = data.toString();
  }
  return result;
}

// En Scala
def returnStr(data:Object) = if(data!=null) data.toString else ""

Ceci n'est qu'un banal exemple, mais il est certain que Scala offre beaucoup d'avantages à l'usage, notamment grâce au fait que le compileur déduit de lui-même beaucoup de choses quant aux types des variables utilisées : dans le dernier exemple ci-dessus, on voit qu'aucun type de donnée de retour n'est spécifié pour la méthode returnStr et pourtant un code comme celui-ci ne compilera pas :

// Scala
var test:Int = returnStr(data)

Alors bien sûr les puristes de Java comme moi pourront s'exclamer devant ce genre de "laxisme" en terme de rigueur syntaxique, mais il est indéniable que cela permet de gagner en productivité et en légèreté lors de l'écriture du code. Scala a en effet l'avantage de s'appuyer sur l'expérience de Java afin de proposer une syntaxe plus concise et directive en ajoutant l'aspect fonctionnel à la programmation purement objet.

Quel IDE pour commencer?

Le première chose compliquée pour se lancer avec Scala est de trouver le bon IDE. En faisant quelques recherches, on se rend vite compte que le débat est assez important entre Eclipse, Netbeans et IDEA. Pour avoir testé les 3, je recommande vivement Netbeans : le principal défaut du plugin Scala pour Eclipse (Galiléo) est son instabilité... Il n'est pas rare qu'il fige littéralement l'IDE après une recherche d'occurrences ou d'expression. Qui plus est, l'auto-complétion n'est vraiment pas au point. Concernant IDEA, je n'est même pas réussi à installer le plugin! (Problèmes de dépendances à la chaîne...)

Finalement, et même s'il n'est pas encore totalement au point, le plugin Netbeans est largement au dessus des autres : auto-completion satisfaisante, stabilité parfaite. Son seul défaut est la lenteur de compilation : entre 2 et 5 secondes pour 2 classes (environ 100 lignes au total...). Pour l'installer, je vous conseille de suivre cette démarche.

Couplage avec Java

Comme je l'ai dit plus haut, Scala est totalement interfaçable avec Java. Il est donc possible d'utiliser des classes Java dans du code Scala (et c'est même parfois nécessaire).

L'exemple le plus parlant est le support de Swing dans Scala. La plupart des objets courants du package javax.swing.* sont disponibles dans le package scala.swing.*. Ainsi, il est possible d'utiliser par exemple scala.swing.Table et d'obtenir son homologue Java (JTable) par l'intermédiaire de la simple propriété scala.swing.Table.peer.

Je ne fais ici qu'une rapide présentation du langage en lui-même, mais je ne manquerai pas de publier des analyses plus approfondies lorsque j'aurai moi même fais le tour de tout ce que Scala peut apporter à un développeur Java. J'ai listé ci-dessous la documentation la plus pertinente que j'ai parcouru pour le moment. Je vous conseille de lire rapidement les détails sur le site officiel afin de prendre connaissance de l'ampleur des nouveautés disponibles!

Sources


Fichier(s) joint(s) :



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



Java annotations et introspection-reflexion

J'étais sur le point d'écrire un article sous forme de petit cours au sujet des annotations Java jusqu'à ce que je parcoure le web et finisse par tomber sur deux documents très intéressants. Alors plutôt que faire du plagiat, je vais simplement vous diriger directement vers ces ressources, en vous expliquant tout de même pourquoi je les ai choisi particulièrement.


Conférence: Les annotations enfin expliquées simplement
: sur le blog de Zenika (une société proposant des services d'expertise dans le monde Java/Open source), un premier cours très intéressant (slides et fichiers source à l'appui) pour commencer à prendre en main le concept d'annotations et d'introspection : définitions, règles, personnalisation... C'est par ceci qu'il faut commencer si vous voulez vous lancer dans ce domaine!

Cours de Julien Cervelle : Réflexion et annotation : professeur à l'université de Marne-la-Vallée qui a rédigé un cours plus avancé et pointu sur certains aspects de la réflexion en Java : travailler avec des types paramétrés, détail du package java.lang.reflect, problématiques de sécurité et annotations. Une mine d'or!


Fichier(s) joint(s) :



e4 : enfin un IDE sexy! (même si...)

e4 est le petit nom donné à la nouvelle version de l'IDE Eclipse, encore à l'état d'incubation dans les Eclipse projects mais dont les premières releases sont déjà très prometteuses...

Avant tout, voici les premiers liens utiles pour vous renseigner à ce sujet :

  • Présentation du projet, sur la page officielle
  • Le wiki Eclipse, qui permet de prendre connaissance des différentes avancées du projet, premiers tutoriaux et documentations, télécharger les nouvelles releases...

Et maintenant, un aperçu du nouveau look :


Comme vous pouvez le voir, l'ensemble paraît beaucoup plus travaillé et stylisé. Une clarté dans la séparation des panneaux rend la prise en main un peu plus simple.

D'un point de vue purement pragmatique, le reproche que l'on peut faire à cette interface est de perdre en efficacité puisqu'elle réduit la surface utile (à cause des nouvelles formes et dispositions des vues). Mais on met alors le doigt sur un débat lancé il y a quelques mois sur l'équilibre à trouver entre efficacité et utilisabilité de l'interface.

Kevin McGuire a écrit plusieurs articles à ce sujet, décrivant comment l'interface actuelle d'Eclipse "gaspille" réellement certains espaces : Eclipse UI Real Estate Wasters!. Il propose également quelques solutions pour améliorer cette ergonomie : Every pixel is sacred (not any more!).

Enfin, Michael Scharf propose quant à lui un moyen de passer l'IDE en fullscreen afin de remédier à ces inconvénients : Every pixel counts! The new eclipse fullscreen plugin.... Mais comme vous pourrez le constater, ces discussions datent d'un moment et il ne me semble pas que tout ceci ait réellement avancé depuis... Alors peut-être y a-t-il de nouvelles choses à exploiter avec les nouvelles versions de l'IDE!


Fichier(s) joint(s) :



Planet Eclipse et la communauté francaise

Il y a peu de temps, j'ai voulu m'inscrire au sein de la communauté Eclipse Planet afin de figurer parmi les nombreux membres actifs... Suivant la démarche indiquée, j'ouvre un post dans le bugzilla d'Eclipse. La réponse ne s'est pas faite attendre, mais mauvaise nouvelle : ils n'acceptent pour l'instant que les blogs publiant des posts en anglais. Loin de moi l'idée de critiquer ce concept, mais le but de mon blog est justement de promouvoir et supporter la communauté française.

Voici le lien vers le ticket en cours : Bug 325703. Comme vous pourrez le constater, la personne qui m'a répondu indique qu'ils envisagent de bientôt prendre en compte toutes les langues! Alors patience et persévérance!


Fichier(s) joint(s) :



Communauté officielle des développeurs Eclipse

Une fois de plus, au hasard de mes déambulations sur la Toile, je suis tombé sur un lien que je souhaite partager : le site Planet Eclipse qui rescence un grande quantité de liens vers des bloggers/développeurs Eclipse. En somme, une base de connaissance sans fin! Le détour en vaut la peine...


Fichier(s) joint(s) :

Retrouver les icônes utilisés dans Eclipse

Je fais suivre un lien intéressant sur lequel je viens de tomber un peu par hasard, sur le blog de Benjamin Cabé, qui permet de récupérer tous les icônes utilisés dans l'IHM d'Eclipse Ganymede. Cela peut être intéressant pour reproduire des interfaces COTS ;) : les icônes ici


Fichier(s) joint(s) :



Maîtriser Tycho de A à Z - part 4

Nous allons voir ici comment créer d'une part un référentiel pour le build Tycho, plutôt que d'utiliser la plateforme cible définie précédemment, et d'autre part comment mettre en place un site de mise à jour pour une application RCP.

Plateforme implicite

Nous avons vu dans le précédent article comment utiliser une plateforme cible pour déployer une application avec Tycho. L'intérêt de ce chapitre est de démontrer la possibilité de déposer les plugins nécessaires au déploiement sur un référentiel Eclipse, tout comme le référentiel Galileo par exemple.

La première étape est de construire le référentiel. Dans Eclipse, il faut exporter les features utilisées dans le projet, en spécifiant bien de générer les metadata.

Ensuite, il faut placer ce répertoire sur un serveur qui sera accessible par Tycho (par exemple sur un Apache simple : http://localhost/tychoRepository)

Finalement, il ne reste plus qu'à compléter le POM global du projet en ajouter l'instruction :


 
  developpef
  http://localhost/tychoRepository
  p2
 

Le build peut donc simplement être lancé par la commande : mvn clean package. Tycho ira automatique chercher les plugins de l'application sur le référentiel distant, tout comme il le fait vers le référentiel central pour ses plugins de base.

Création du site de mise à jour

Ici, rien de compliqué. Il faut commencer par créer, dans Eclipse, un projet de site de mise à jour (avec l'assistant adéquat). Le projet en question ne contient qu'un fichier site.xml dont le seul but est de rescencer les feature qui devront faire l'objet de mises à jour. Nous allons donc y ajouter com.developpef.rcpfeature :

Il faut maintenant relancer la génération des fichiers POM sur le workspace pour voir apparaitre, dans le projet en cours, un fichier généré comportant le packaging-type "eclipse-update-site". Il suffit alors de lancer la commande citée plus haut pour que Tycho construise, dans le dossier "target" du projet, un site de mise à jour de type p2 (l'action "Build All" d'Eclipse aura le même résultat).

A propos du packaging type "eclipse-update-site"

Comme indiqué dans cet article de Git, ce packaging type utilisé aujourd'hui par Tycho (v0.9.0) fait référence à l'ancienne version de référentiel p2 d'Eclipse, qui n'a pas la structure désirée. Il ne pourra donc pas être déployé par la simple commande mvn deploy mais plutôt à la main ou grâce à une tâche Ant par exemple.


Fichier(s) joint(s) :



Maîtriser Tycho de A à Z - part 3

Aujourd'hui nous allons nous attaquer à la mise en place du build headless et lancer Tycho pour la première fois!

Pour qu'une application puisse être construite entièrement en dehors d'un environnement PDE, il faut rajouter à la plateforme cible que nous avons construit précédemment les plugins nécessaires à la création des launchers (exécutables). Pour ceci, il faut récupérer un Delta pack, comme celui que vous pourrez trouver ici, qui contient tout ce dont nous avons besoin.

Pour l'intégrer à notre projet, voici la démarche à suivre :

  • Téléchargez le pack et décompressez le dans le dossier de la plateforme cible afin d'y ajouter tout son contenu.
  • Dans Eclipse, rechargez le fichier .target pour actualiser la liste des plugins/features disponibles.
  • Importez dans le workspace la feature org.eclipse.equinox.executable (pas en tant que "binary project") afin de pouvoir l'épurer des plugins non nécessaires à votre configuration (selon l'environnement d'exécution de l'application)
  • Ajouter la feature à celle utilisée par notre produit (ici com.developpef.rcpfeature)
  • Re-générer les fichiers POM de Tycho afin que la feature soit ajoutée aux modules utilisés.

Et voilà, cette fois-ci notre plateforme cible est prête pour être utilisée avec Tycho!

Et pour ce faire, rien de plus simple : placez-vous à la racine du workspace et lancer la commande mvn clean package -Dtycho.targetPlatform=C:\minimal_target

Comme à son habitude, Maven va télécharger la terre entière dans son repository et au début du moins, le build risque d'être en échec suite au manque de quelques plugins nécessaires à Tycho. Il vous suffit de les récupérer depuis une intallation classique d'Eclipse et tout devrait rouler!


Fichier(s) joint(s) :



Maîtriser Tycho de A à Z - part 2

Ce deuxième article de la série a pour but d'illustrer comment configurer les fichiers POM du projet qui seront utilisés pour le build Maven.

Génération des fichiers POM

Tycho dispose d'une tâche qui permet de générer les fichiers POM de chaque projet. Pour ce faire, se placer au niveau du workspace Eclipse et lancer la commande :

mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=com.developpef

Le plugin Tycho est tout d'abord téléchargé et une fois le processus terminé, on pourra trouver :

  • à la racine du workspace, un fichier POM contenant des liens vers tous les projets (« modules »).
  • un fichier POM dans chaque projet indiquant sa nature (eclipse-plugin, eclipse-feature...)

Configuration du POM principal

Afin d'assurer le bon déroulement du build Tycho, le fichier POM situé à la racine du worskpace doit être configuré de la manière suivante :

  • ajouter l'option permettant de forcer l'utilisation du compilateur Java 6 (pour la prise en charge des annotations) :
    
    
    
    
    org.sonatype.tycho
    maven-osgi-compiler-plugin
    true
    0.9.0
    
    6.0
    6.0
    
    
    
    
    
  • paramétrer l'environnement d'exécution cible du produit. Par exemple :
    
    org.sonatype.tycho
    target-platform-configuration
    0.9.0
    
    
    
    win32
    win32
    x86
    
    
    p2
    
    
    

Cette configuration devrait permettre de faire fonctionner la plupart des projets Java 6 de base.

Dans le prochain article, nous verrons comment compléter la plateforme cible pour réaliser un véritable build "headless" puis lancer notre premier build Tycho.


Fichier(s) joint(s) :

Maîtriser Tycho de A à Z - part 1

Ce premier article décrit comment créer un produit indépendant de l'interface de développement PDE.

Configuration du produit

Afin que Tycho résolve correctement les liens entre le produit et les autres projets, il est indispensable de créer un projet propre au produit, qui ne contiendra rien d'autre. Le projet "com.developpef" contient donc uniquement le fichier "com.developpef.product" (pour Tycho, le fichier doit porter le même nom que le projet, suffixé par ".product"). Il est également important que la configuration du produit se base sur des features et non des plugins. Voici la configuration terminée :

Création de la plateforme cible

L'intérêt de créer une plateforme cible est de permettre à l'application finale (le produit) de se lancer depuis un ensemble minimal de plugins que nous aurons défini qui sera au final utilisé comme base pour le build automatique de Tycho.

Commençons par créer la feature de base de notre produit qui contiendra ces fameux plugins de base. A l'aide de l'assistant de création de projet, créons un projet de feature. Elle devra être initialisée à partir d'une application RCP standard, comme illustré:

Une fois la feature créée, on pourra voir dans sa liste de plugins tout ce qui concerne une application Eclipse de base. Il faut cependant l'épurer de ce qui reste encore inutile (plugins pde...) : pour ce faire, il faut exporter la feature dans un répertoire quelconque (disons C:\minimal_target) puis supprimer tous les plugins contenant : source, mylyn, wst et pde.

Pour terminer, nous allons créer la définition de la cible. Dans un nouveau projet, créer une nouvelle "target definition", puis dans la catégorie "Location", renseigner le dossier généré précédemment :

Pour terminer, cliquer sur le lien "Set as target platform" pour indiquer au workspace de se baser uniquement sur les plugins et features de ce dossier pour compiler l'application.

Dans le prochain article, je décrirai comment utiliser Tycho pour générer les fichiers POM nécessaires à la description des projets pour la compilation de Maven.


Fichier(s) joint(s) :