Affichage des articles dont le libellé est Java. Afficher tous les articles
Affichage des articles dont le libellé est Java. Afficher tous les articles


Gestion des Exceptions, mais pas trop n'en faut...

Les gestions des erreurs au sein d'une application est souvent un point sensible, voire critique. Nous avons tous déjà lu tout un tas d'articles et conseils sur les bonnes pratiques, comme par exemple :

Mais un point important qui est pourtant bien moins évoqué, est le coût des Exceptions : en effet, les méthodes de création des objets Exception, comme fillInStackTrace() sont coûteuse en matière de charge CPU, comme le démontre cet article (que je vous recommande fortement) : Performance Impact of Exceptions

Un autre exemple plus pragmatique démontre par l'exemple ces affirmations : The Cost of an Exception.

Pour résumer, ce qu'il faut garder en mémoire :

Getting the stack trace of an exception had a 10x higher impact on the performance than just catching and throwing them. So while stack traces help to understand where and possibly also why a problem occurred, they come with a performance penalty.

A bon entendeur!


Fichier(s) joint(s) :



Un moteur de recherche pour piles d'exceptions

Quoi de plus pénible que d'essayer de trouver dans les moteurs de recherche la cause d'une erreur à partir de la stracktrace??

Heureusement il existe Brainleg, un moteur de recherche spécialisé :

Il suffit de coller la pile d'erreur dans le champ de recherche pour que le moteur affiche une liste de sites dans lesquels cette pile (ou une pile similaire) a été postée.

Brainleg se comporte comme un méta-moteur qui indexe un grand nombre de sites de référence et de forums afin de retrouver les traces des erreurs :

Pour ce qui est de l'intégration dans les IDEs, Brainleg ne propose qu'un plugin IDEA. Pour Eclipse, il vaut mieux se tourner vers le plugin Code Recommenders qui propose une fonctionnalité similaire.


Fichier(s) joint(s) :



Détection de deadlock en Java

Un petit article afin de partager quelques liens très utiles vers des méthodes simples et efficaces pour observer et analyser des deadlocks.

Ces exemples étant déjà très complets et précis, je ne vais pas plagier, je vous laisse les découvrir par vous-mêmes :

Bonne lecture!


Fichier(s) joint(s) :



Valider des fichiers XML en Java

La vérification de fichiers XML en fonction de schémas XSD peut vite devenir plus complexe qu'elle n'en a l'air...

Imaginons une application Java qui utilise des schémas imbriqués situés dans un certain répertoire pour valider des fichiers situer dans un autre répertoire :

Dans C:\schemas\ :

schema-parent.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:include schemaLocation="schema-fils.xsd"/>
 <xs:element name="OPS">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

schema-fils.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:include schemaLocation="schema-petit-fils.xsd"/>
 <xs:element name="TOT">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

schema-petit-fils.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:element name="XDF">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

Dans C:\xml\ :

avalider.xml


<?xml version="1.0" encoding="UTF-8"?>
<OPS xmlns="..." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="schema-parent.xsd" AD="FRA" FR="AA"
 ON="540" OD="2012-11-12" OT="15:08">
 <DAT TM="CU">
  ...
 </DAT>
</OPS>

Et ci-dessous le code permettant d'exécuter la validation :


...
import org.xml.sax.helpers.DefaultHandler;
...

XSDHandler handler = new XSDHandler();

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();

sp.getParser().setLocale(Locale.ENGLISH);

sp.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");

InputStream schemaIs = getClass().getResourceAsStream("schema-parent.xsd");
sp.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaIs);
sp.parse(fichierAvalider, handler);

...

class XSDHandler extends DefaultHandler {

 private final static Logger logger = Logger.getLogger(XSDHandler.class);

 @Override
 public void fatalError(SAXParseException e) {
  logger.error("Erreur fatale :" + message);
 }

 @Override
 public void error(SAXParseException e) {
  logger.error("Erreur :" + message);
 }

 @Override
 public void warning(SAXParseException e) {
  logger.warn("Warning :" + message);
 }
}

Lors de l'exécution, l'erreur suivante va être levée :

Echec de la lecture du document de schéma 'schema-fils.xsd' pour les raisons suivantes : 1) Le document est introuvable ; 2) Le document n'a pas pu être lu ; 3) L'élément racine du document n'est pas .

Ceci est du à la présence de l'attribut schemaLocation au sein même du fichier XML, qui prévaut sur toute autre déclaration schemaLocation dans les XSD. Le SAXParser va donc tenter de trouver le schéma relativement au fichier en cours de vérification (dans C:\xml\)... Il est donc préférable de supprimer cet attribut.

Cependant, même après cette correction, l'erreur persiste... En effet, dans ce cas de schémas imbriqués, le SAXParser utilise l'attribut schemaLocation de schema-parent.xsd contenant normalement le chemin relatif vers le schéma fils, mais recherche toujours de manière relative au fichier XML!

Pour régler ce problème, sans avoir à indiquer de chemin absolu dans les schémas, il est possible d'indiquer au SAXParser comment retrouver ses petits. Pour ce faire, il faut modifier le XSDHandler qui lui est passé pour surcharger la méthode resolveEntity :


class XSDHandler extends DefaultHandler {

 private final static Logger logger = Logger.getLogger(XSDHandler.class);

 @Override
 public void fatalError(SAXParseException e) {
  logger.error("Erreur fatale :" + message);
 }

 @Override
 public void error(SAXParseException e) {
  logger.error("Erreur :" + message);
 }

 @Override
 public void warning(SAXParseException e) {
  logger.warn("Warning :" + message);
 }

 @Override
 public InputSource resolveEntity(String arg0, String arg1)
   throws IOException, SAXException {
  File tmp = new File(arg1);
  return new InputSource(new FileInputStream(new File(getClass().getResource("/v3/"+tmp.getName()).toURI())));
 }
}

Le deuxième argument arg1 contient le chemin complet où le parser cherche le schéma imbriqué. Il suffit donc d'un petit tour de passe-passe pour renvoyer le bon fichier!

Hope this helps!


Fichier(s) joint(s) :



Observer une JVM sous toutes ses coutures

J'ai déjà écrit quelques articles sur les différents moyens de monitorer une JVM, notamment avec la JConsole, mais il existe bien d'autres outils permettant d'observer le déroulement d'une application dont VisualVM, qui est sûrement le plus complet et le plus agréable à utiliser.

Comme il existe déjà de nombreux blogs décrivant son fonctionnement, je ne vais pas les paraphraser et je vous laisse les découvrir via les liens plus bas.

Je vais ici plutôt m'attarder sur la description du plugin Eclipse qui permet d'ajouter un lanceur d'application basé sur VisualVM afin d'accéder directement au profilage dès le démarrage.

Après avoir téléchargé et installé le plugin sur le site officiel, direction les préférences du workspace :

Il s'agit ici de simplement configurer :

  • Dans la section "VisualVM configuration", le chemin d'exécution de VisualVM et du JDK.
  • Dans la section "Default launchers", le nouveau lanceur à utiliser (qui n'est finalement qu'un hook avant le démarrage de l'application)

Ainsi, à la prochaine exécution dans l'IDE, VisualVM sera lancé en parallèle et automatiquement câblé dessus :

Il ne reste plus qu'à profiter de toutes les fonctionnalités de l'outil, toutes plus indispensables les unes que les autres... Enjoy!

Sources :


Fichier(s) joint(s) :



Comment je me suis lancé dans le développement mobile

J'ai décidé d'expliquer dans cet article comment, après quelques semaines de recherches, je me suis lancé dans le développement d'une application pour appareils mobiles. Comme le processus de décision a été plutôt long et le fruit de nombreux questionnements, je le rend public afin d'aider ceux qui seraient dans la même perspective à prendre une bonne décision.

Le contexte du projet

L'idée originelle était de développer un petit "assistant" dans la pratique d'une activité de loisir. Etant moi-même pratiquant, je me suis rendu à l'évidence qu'il fallait que l'outil soit toujours à portée de main de l'utilisateur, donc sur un appareil mobile! (je garde pour le moment le mystère que le sujet exact du projet :-) )

Le choix de la technologie

Il existe aujourd'hui pas mal de moyens pour réaliser une application nomade. Ayant une bonne expérience de Java, j'ai d'abord essayé de créer une simple application Swing emballée dans un Jar, puisque la majorité des terminaux savent lire ce genre de paquet. Mais là premier écueil : les Manifest des applications Java sont en réalité totalement différents de ceux des applications mobiles... Qu'à cela ne tienne, utilisons J2ME... Dès la première tentative de compilation du projet, c'est le drame : J2ME requiert un compilateur compatible 1.3, donc adieu Swing, adieu encore les listes paramétrées et pire, adieu les listes tout court, à remplacer par des Vecteurs. C'en est trop, il faut trouver autre chose!

Les premières recherches sur le net concernant le développement mobile et la compatibilité inter-plateformes font apparaître qu'il est nécessaire d'utiliser HTML5, CSS3 et Javascript... Je n'ai jamais été très fan du développement web... Mais cela pourrait faire une bonne raison de s'y mettre! En regardant de plus près, il existe déjà pas mal de solutions "in cloud" pour déployer un projet web sur toutes les plateformes mobiles. Super! Maintenant, il faut encore rattraper le retard accumulé auprès des technos citées précédemment pour mettre en place quelque chose de correct. Au fond je ne suis pas très motivé pour ça.

Une solution pour faire le pont Java-Web? GWT, me souffle-t-on! Bon sang mais c'est bien sûr! Superbe occasion de se lancer! Et là, pour faire court : la présentation sur le site officiel est alléchante quoique plutôt sommaire, conceptuellement cela semble complexe mais bien organisé mais alors pour la réalisation... Ils ont sorti l'artillerie lourde! A grand renfort de lignes de commandes, panels graphiques et objets ésotériques, on est vite perdus!

Donc c'est le moment de se décider pour un développement spécifique à une plateforme. Restreignons le choix aux plus répandues : iOS et Android. N'ayant jamais fait de C, il ne reste plus qu'Android (qui semble prendre de plus en plus de parts de marché, donc intéressant). Et là, enfin, le Graal! Un site officiel qui présente plus clairement ce que permet le SDK, une documentation qui semble plus accessible et plus claire et surtout du code basé uniquement sur JAVA, du XML maison et seulement quatre ou cinq grands concepts techniques et/ou généraux sur le fonctionnement d'une appli... Ouf! De plus, ayant une vieillissante expérience en Flex, tout se ressemble.

Les débuts

Alors c'est parti, je me lance! Etant habitué à Eclipse, je récupère le SDK Android et la batterie de plugins nécessaire, je génère le premier projet "Hello world" et après la configuration de l'émulateur c'est la désillusion : ça rame! Eclipse est connu pour vite monter en charge lors de lourdes opérations, mais le mode Debug sur l'émulateur Android bat des records, mon PC est submergé, difficile d'avancer.

En se renseignant un peu, on comprend vite qu'il est préférable d'utiliser l'émulateur de manière indépendante de l'IDE, en ligne de commande (simple). Faisons ça. Reste l'IDE lui même qui malgré tout semble peiné par la surcharge des plugins Android. Il faudra donc lui faire des infidélités cette fois-ci : je me tourne vers IntelliJ IDEA. Et là effectivement, on respire un peu! D'autant plus que le développement Android semble grandement amélioré par une intégration à l'IDE beaucoup plus importante : auto-complétion des informations textuelles par lien direct avec les fichiers de ressources XML, meilleure organisation du projet par reconnaissance des éléments spécifiques à l'architecture Android (dossier "res") etc.

Enfin cette fois, c'est parti! C'est assez plaisant (pour un débutant) de voir une appli fonctionner sur un appareil mobile. Je profiterai dans l'avenir de cette nouvelle expérience pour publier quelques articles sur le développement Android.

A suivre donc...


Fichier(s) joint(s) :



Eclipse : extraire une feature et ses dépendances

Lors de la création d'applications RCP, il peut être utile de récupérer quelques features indépendantes afin des les intégrer à la distribution (pour ajouter des nouveaux plugins). Quand ces features ne sont pas livrées de manière autonome, il devient vite assez fastidieux de suivre à la main toutes les dépendances pour récupérer les plugins utiles.

Pour faciliter ce travail à tous ceux qui en auront besoin, voici une petite moulinette prête à l'emploi pour copier toutes les dépendances d'une feature :

public class EclipseFeatureExtractor {
 
 private static Set<String> requiredFeatures = new HashSet<String>();
 private static Set<String> requiredPlugins = new HashSet<String>();
 private static DocumentBuilder builder;
 private static XPath xpath;
 private static File featuresFolder;
 private static File pluginsFolder;

 /**
  * @param args
  * @throws Exception 
  */
 public static void main(String[] args) throws Exception {
  if(args.length != 2 || (args.length>=1 && args[0].equals("-help"))) {
   System.out.println("*********** USAGE ***********");
   System.out.println("-help affiche aide.\n");
   System.out.println("Paramètres nécessaires :");
   System.out.println("1- Dossier de la feature à examiner");
   System.out.println("2- Dossier de destination pour la copie des features et plugins");
   return;
  }
  
  String mainFeature = args[0];
  String destination = args[1];
  
  File destFolder = new File(destination);
  File mainFeatureFolder = new File(mainFeature);
  featuresFolder = mainFeatureFolder.getParentFile();
  pluginsFolder = new File(featuresFolder.getParentFile(), "plugins");
  
  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  factory.setNamespaceAware(true);
  builder = factory.newDocumentBuilder();
  
  XPathFactory xpathfactory = XPathFactory.newInstance();
  xpath = xpathfactory.newXPath();
  
  parseFeature(mainFeatureFolder);
  
  File destFeaturesFolder = new File(destFolder,"features");
  if(!destFeaturesFolder.exists()) {
   destFeaturesFolder.mkdirs();
  }
  copyFeatures(destFeaturesFolder);
  
  File destPluginsFolder = new File(destFolder,"plugins");
  if(!destPluginsFolder.exists()) {
   destPluginsFolder.mkdirs();
  }
  copyPlugins(destPluginsFolder);
 }

 private static void copyPlugins(File destPluginsFolder) throws Exception {
  for(String required : requiredPlugins) {
   for(File other : pluginsFolder.listFiles()) {
    if(other.getName().startsWith(required)) {
     System.out.println("Copying plugin : "+other.getName());
     if(other.isDirectory()) {
      File specificDest = new File(destPluginsFolder, other.getName());
      specificDest.mkdirs();
      FileUtils.copyDirectory(other, specificDest);
     } else {
      File copiedPlugin = new File(destPluginsFolder, other.getName());
      FileUtils.copyFile(other, copiedPlugin);
     }
    }
   }
  }
 }

 private static void copyFeatures(File destFeaturesFolder) throws Exception {
  for(String required : requiredFeatures) {
   for(File other : featuresFolder.listFiles()) {
    if(other.isDirectory() && other.getName().startsWith(required)) {
     File specificDest = new File(destFeaturesFolder, other.getName());
     specificDest.mkdirs();
     System.out.println("Copying feature : "+other.getName());
     FileUtils.copyDirectory(other, specificDest);
    }
   }
  }
 }

 public static void parseFeature(File featureFolder)
   throws SAXException, IOException, XPathExpressionException {
  File featureDescriptor = new File(featureFolder, "feature.xml");
  System.out.println("Parsing : " + featureDescriptor.getAbsolutePath());
  Document doc = builder.parse(featureDescriptor);
  // Required features
  XPathExpression xRequiredFeatures = xpath.compile("//requires/import/@feature|//includes/@id");
  NodeList nodes = (NodeList)xRequiredFeatures.evaluate(doc, XPathConstants.NODESET);
  String feature = "";
  for (int i = 0; i < nodes.getLength(); i++) {
   feature = nodes.item(i).getNodeValue()+"_";
   requiredFeatures.add(feature);
   for(File other : featuresFolder.listFiles()) {
    if(other.isDirectory() && other.getName().startsWith(feature)) {
     parseFeature(other);
    }
   }
  }
  // Required plugins
  XPathExpression xRequiredPlugins = xpath.compile("//plugin/@id");
  nodes = (NodeList)xRequiredPlugins.evaluate(doc, XPathConstants.NODESET);
  String plugin = "";
  for (int i = 0; i < nodes.getLength(); i++) {
   plugin = nodes.item(i).getNodeValue()+"_";
   requiredPlugins.add(plugin);
  }
 }

}

Ce code peut être amélioré pour gérer de manière plus fine les versions des features/plugins, ici ignorées.


Fichier(s) joint(s) :



Créer une console personnalisée avec JTextPane

Voici la problématique à résoudre : un outil a été développé en prévision de n'être exécuté qu'en ligne de commande. Toutes les informations ressorties le sont donc via System.out ou System.err. Or il se trouve maintenant qu'il est nécessaire d'intégrer cet outil dans une application comportant une interface graphique et donc de diriger les sorties vers un composant graphique en y ajoutant un formatage selon le type de message. Il est bien sûr impossible de modifier le code de l'outil... Voici un aperçu du résultat attendu :

Pour résoudre ceci, il faut commencer par redéfinir la sortie des messages. Java offre cette possibilité à l'aide de :

System.setOut(...);
System.setErr(...);

Ces deux méthodes attendent en entrée un objet héritant de java.io.PrintStream. Mais avant de voir comment mettre en place ces éléments, regardons comment est créée l'interface :

public static void buildGUI() throws Exception {
 final CustomConsole out = new CustomConsole();
 JScrollPane sp = new JScrollPane(out.getOutComponent());
 JButton btn = new JButton("Run!");
 btn.addActionListener(new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
   out.clear();
   Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
     doJob();
    }
   });
   t.start();
  }
 });
 JPanel p = new JPanel(new BorderLayout());
 p.add(btn, BorderLayout.NORTH);
 p.add(sp, BorderLayout.CENTER);
 
 JFrame f = new JFrame("Test console");
 f.setSize(800, 850);
 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 f.add(p);
 f.setVisible(true);
}

Cette première méthode utilise des objets Swing courants pour construire l'interface, et introduit CustomConsole : c'est dans cette classe que réside toute notre customisation. Voici son contenu :

public class CustomConsole {

 private JTextPane outComponent;

 public CustomConsole() throws Exception {
  this.outComponent = new JTextPane() {
   private static final long serialVersionUID = 8575888440178628669L;
   // to remove line wrap
   public boolean getScrollableTracksViewportWidth() {
    return getUI().getPreferredSize(this).width <= getParent().getSize().width;
   }
   @Override
   public void setEditable(boolean b) {
    super.setEditable(false);
   }
  };
  // build error style
  Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
  Style s = outComponent.getStyledDocument().addStyle("error", def);
  StyleConstants.setForeground(s, Color.RED);
  StyleConstants.setItalic(s, true);
  // define standard out
  System.setOut(new CustomOutStream(outComponent));
  System.setErr(new CustomErrStream(outComponent));
 }

 public JTextPane getOutComponent() {
  return outComponent;
 }

 public void clear() {
  outComponent.setText("");
 }
}

Comme vous pouvez le constater, le code est assez simple. La définition du style de police pour les messages d'erreurs se fait à partir du style standard de police du JTextPane auquel on ajoute nos caractéristiques. Notez la subtilité lors de la définition de cet objet : pour empêcher les retours à la ligne lors de l'affichage, on surcharge la méthode getScrollableTracksViewportWidth(). Enfin, la suite de notre personnalisation réside dans les objets CustomOutStream et CustomErrStream : mais là encore, rien de très compliqué, les deux classes sont quasiment identiques :

public class CustomOutStream extends CustomPrintStream {

 private JTextPane outComponent;

 public CustomOutStream(JTextPane area) throws Exception {
  super();
  outComponent = area;
 }

 @Override
 public void write(byte buf[], int off, int len) {
  addToConsole(new String(buf, off, len));
 }

 @Override
 public void write(int b) {
  addToConsole(new String(new char[] { (char) b }));
 }

 @Override
 public void println(String x) {
  addToConsole(x);
 }

 private void addToConsole(String s) {
  StyledDocument doc = outComponent.getStyledDocument();
  try {
   if (!s.endsWith("\n")) {
    println();
   }
   doc.insertString(doc.getLength(), s, doc.getStyle(StyleContext.DEFAULT_STYLE));
  } catch (BadLocationException e) {
   e.printStackTrace();
  }
  outComponent.setCaretPosition(doc.getLength() - s.length());
 }

}

Elles redéfinissent les méthodes d'écriture les plus courantes afin de nous permettre de gérer les styles : dans cet exemple, pour la sortie standard, on choisi d'utiliser la police par défaut, correspondant au style StyleContext.DEFAULT_STYLE.

Pour ce qui est de la sortie d'erreur, on utilise le même code en remplaçant simplement le style par celui défini dans la console (italique rouge) : doc.getStyle("error")

L'instruction setCaretPosition permet de créer l'effet d'autoscroll sur la console.

Pour unifier la création du flux de sortie, j'ai créé la classe ci-dessous, mère des classes précédentes :

public class CustomPrintStream extends PrintStream {

 public CustomPrintStream() throws FileNotFoundException, UnsupportedEncodingException {
  // on indique un fichier texte parce que requis mais non utilisé ici
  // et on indique l'encodage désiré pour cette console
  super(new FileOutputStream("C:\\empty.txt"), true, "ISO-8859-1");
 }

}

Cette console peut alors être testée avec ce simple code :

private static void doJob() {
 FileInputStream fstream = null;
 try {
  System.out.println("Sortie console standard");
  if(true) {
   throw new Exception("premiere erreur!!");
  }
 } catch (Exception e) {
  e.printStackTrace();
  System.out.println("info après premiere erreur");
 } finally {
  try {
   fstream.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

Nous avons donc pu créer cette interface graphique sans modifier le code de l'outil de base.

Hope this helps!

Sources :


Fichier(s) joint(s) :



Un système de template avec Groovy et Java

La génération de rapports au format texte brut (TXT) peut parfois devenir un vrai casse-tête, surtout s'il contient une mise en forme particulière quelles que soient les données.

Pour se faciliter la tâche, il est possible d'utiliser un moteur de templates basé sur Groovy, exécutable en Java, qui assurera la stabilité du rendu avec un effort réduit.

Commençons par le code Java générant notre "rapport" :

import groovy.text.SimpleTemplateEngine;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.groovy.control.CompilationFailedException;

public class ReportUtil {

 public static void main(String[] args) {
  Map<String, Object> data = new HashMap<String, Object>();

  Map<String, String> context = new HashMap<String, String>();
  context.put("mascotte", "Duke");
  context.put("auteur", "DevellOpPeF");
  context.put("tool", "GRoOvY");
  context.put("date", "01042012");

  data.put("context", context);
  
  List<String[]> remerciements = new ArrayList<String[]>();
  remerciements.add(new String[]{"Rodolphe","Pour avoir retrouvé ces sources"});
  remerciements.add(new String[]{"Olivier","Pour ses questions pertinentes sur Groovy/Java"});
  
  data.put("remerciements", remerciements);

  try {
   buildFileFromGroovyTemplate(ReportUtil.class.getClassLoader()
     .getResource("TestReport.tmpl").toURI(), "txt", data);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static String buildFileFromGroovyTemplate(URI templateFilePath,
   String outputType, Map<String, Object> dataProvider)
   throws CompilationFailedException, IOException {

  String outputFileName = "out_" + System.currentTimeMillis() + "."
    + outputType;
  String outputFilePath = "C:/test/" + outputFileName;

  File outputFile = new File(outputFilePath);
  if (outputFile.exists()) {
   outputFile.delete();
  }

  File tmplFile = new File(templateFilePath);
  if (tmplFile.exists()) {
   FileWriter fileWriter = new FileWriter(outputFilePath, false);

   new SimpleTemplateEngine().createTemplate(new FileReader(tmplFile))
     .make(dataProvider).writeTo(fileWriter);

   fileWriter.close();
  }

  return outputFileName;
 }

}

Son contenu est assez simple :

  • La méthode main constitue le jeu de données à utiliser,
  • La méthode buildFileFromGroovyTemplate lance le moteur Groovy pour générer le fichier final à partir du template TestReport.tmpl et par l'intermédiaire du SimpleTemplateEngine

Il suffit d'utiliser la dépendance suivante pour compiler cette classe :

<dependency>
 <groupId>org.codehaus.groovy</groupId>
 <artifactId>groovy</artifactId>
 <version>1.8.6</version>
</dependency>

Voici maintenant le plus important, le contenu du template :

<%
/////// FUNCTIONS

// Method to write string on given char number
def format(format, text) {
 String.format(format,text)
}

def writePageHeader()
{
%>Hi! from ${context["mascotte"]}!

I was generated with Groovy, run by Java and built by ${context["auteur"]}...


Remerciements :
<%}

def writeFirstQuarter() {
%>


7aJeS6viEuWwWk0hX3Bj8twFQi5q7VQ6xZXE
l2Oe8tA3F7OW2AcPEGahtIffLSf6UugDZ3yX
QNL31XLtRuB5pabFbMsFjFsueIIEm1I9ET35
cd4XIYyRrOIsOM5HIdMoqv9t71NMoJDUcAl6
MfbMIxEW8dyC7RChMz3YUWlbDPP23R3G5kTD
bT2iH4cWbhgkcpZGf2tDUt8neMWFdVdzgy8c
EsSDawQv02JeUb7SsZCwhrpfj7B99RvE2FHT
zJZAvF7bF7F2B87JSvuarvksYckRrLN6Vd6I
w6UzumNNBjYOsw1y3pY0WO3BF5hGPGf9A8zu
fxGivMreSlo5lp6JFsbnUK8SH1HqguzSRo0F
q02oIAaQqFvBXFqOKUikYpvXUeQPNx7vd0Ww
xcspJ5BaLPMJCbAZp1MsSEaxTUnvYNbJAZmK
cnlMsCian5gfFgDbbZpZ5pMsdbRFAbmvgzAQ
OVyz7IPPNQE7DlYD82qhMDAJsAJ8np6E6ay2
oNxpWXquVZ${context["tool"]}3PwACcLc0F6984kiKrGi
<%}

def writeSecondQuarter() {
%>EaGQM4WH5VEf2OH9x8hnLc8fWJc5NLPSsWpb
nKqvv77q3LcJrXyDMb8ICPI5ZX5oX6WME7A2
8T5HOUtK21EPEJMEBHCt5Mw2nXUuNsLL6GSJ
DnDbk2LCZYfspil2jJhArVYCE4b2ylmpicXP
KBapEyy3XAsGpJ34ZoyjfedT9FlZPQtZS58X
HA8mU8nDXEU2tdbr9O7QDzI8yKcusJCmKAry
cVx5UQFGvk${context["auteur"]}qZze5ybzbHNK2It
xfGLFoGsXiZtxpW6DuJgeFyNrnnxFRQ16WCA
RIpvWCdMvkrEoYaqYVmAgYBznssLORhuZOTx
Oq5PBx0d4E6ssT06FI0CNtrXkT4ti2Bxe4At
rrv70yQvAvjBpJ8qfjzxVYcd5ImXo9jCet9T
UBDHf8PWWbWC0bunHyFOyJAyc4tAyKhnLl7n
lf5mJlhflLbYBAH8nbKB2Lp8C4qmalyJVz2t
bFEXDHTf0wWW3kbcM8VV1FSI0PipDmr1biP2
HozZjRhO1l7Uqc2nhiYKDkC45dg5ZURu2l4p
YhnzdwDQjrs89rk5Dli0p1dcxK0yXedxIeMw
<%}

def writeThirdQuarter() {
%>A1DdfnctUK475FZI0dZ1iJtZ2s9ixQmxIcYc
Ae8y20H1X2IHgQ6DCuOTepuW1nhR7sH8EFt4
oUAFj16WJ27Q1BVV8S4cdalDp6ZwKRLmdLbS
YlMGccBLiLrl6F3mY4rDIFhqpTYZ5tLTNYZ9
KX107aJWnE1AYEZbDpPPQ23had8kwNr6qUwo
K0TDymjtzak6USOF199vq8pLWzMiWc9udSqI
ROF1azSd9hY8LBRcJUCxbmFNwl11My8rmDUJ
H1DNFv57hHrfqNk5XLpJh1UHWi0f364Bc9O1
CgOXcq6dNbuKnSKDK8M9zrSpVbXIkXymCmHb
zTZw2woP3PdY9gHGeeI3A91lhIsZqrVBcxcg
<%}

def writeLastQuarter() {
%>iYnZHyol6IMPK8${format("%-16s",context["date"])}8Xrbcq
s9mfwL0aUjySD8H0oaL1ghXIT0f64jcijuLa
xieIhx9Tj5Aerj8hGjj7kYUCJhrxpWoV3Atp
hRg9TbhvpPJHqDIFf6puW9aR3BKelwlXBXXV
yTi5qa7w3htdDu9z1My98bR3lf5kd2PHLcDx
Y3yhvNTl65sJCv4GpJGFPvH7ighmCqaat5GR
EiYLt50Kh3wFUOAayJ0yMMHYquiEynhqWEx0
BHTJy3hD8L2fxPliGlJHYh2It33wZb1a5Kpz
<%}


/////// MAIN

writePageHeader()

remerciements.each {
%>- ${format("%-15s",it[0])} : ${format("%-100s",it[1])}
<%}

writeFirstQuarter()
writeSecondQuarter()
writeThirdQuarter()
writeLastQuarter()
%>

Pour ceux qui ne connaissent pas Groovy, sa syntaxe est très proche de celle de Java et/ou JSF. Les instructions Groovy sont placées dans des balises <% (...) %>. Tout ce qui est en dehors correspond au contenu du template, ce qui sera écrit dans le fichier final. Dans ce contenu, l'accès aux variables se fait pas l'usage de ${...}

Ce que l'on trouve ici :

  • La méthode format est utilisée pour écrire une donnée selon un format particulier, en complétant si besoin est par des espaces afin d'assurer la mise en page.
  • Les méthodes writePageHeader, writeFirstQuarter,writeSecondQuarter, writeThirdQuarter et writeLastQuarter permettent de segmenter la génération du contenu, une manière parfois plus lisible de répartir le code.
  • En dernière partie, un bloc intitulé "Main" qui est constitué des appels aux méthodes précédentes et qui va donc entrainer la génération du rapport.
  • Une boucle each parcourant les éléments de la liste remerciements passée en paramètre

Voici donc le résultat (avec un formattage HTML pour la présentation sur cette page) :

Hi! from Duke

I was generated with Groovy, run by Java and built by DevellOpPeF...

Remerciements :

Rodolphe        : Pour avoir retrouvé ces sources
Olivier         : Pour ses questions pertinentes sur Groovy/Java


7aJeS6viEuWwWk0hX3Bj8twFQi5q7VQ6xZXE
l2Oe8tA3F7OW2AcPEGahtIffLSf6UugDZ3yX
QNL31XLtRuB5pabFbMsFjFsueIIEm1I9ET35
cd4XIYyRrOIsOM5HIdMoqv9t71NMoJDUcAl6
MfbMIxEW8dyC7RChMz3YUWlbDPP23R3G5kTD
bT2iH4cWbhgkcpZGf2tDUt8neMWFdVdzgy8c
EsSDawQv02JeUb7SsZCwhrpfj7B99RvE2FHT
zJZAvF7bF7F2B87JSvuarvksYckRrLN6Vd6I
w6UzumNNBjYOsw1y3pY0WO3BF5hGPGf9A8zu
fxGivMreSlo5lp6JFsbnUK8SH1HqguzSRo0F
q02oIAaQqFvBXFqOKUikYpvXUeQPNx7vd0Ww
xcspJ5BaLPMJCbAZp1MsSEaxTUnvYNbJAZmK
cnlMsCian5gfFgDbbZpZ5pMsdbRFAbmvgzAQ
OVyz7IPPNQE7DlYD82qhMDAJsAJ8np6E6ay2
oNxpWXquVZGRoOvY3PwACcLc0F6984kiKrGi
EaGQM4WH5VEf2OH9x8hnLc8fWJc5NLPSsWpb
nKqvv77q3LcJrXyDMb8ICPI5ZX5oX6WME7A2
8T5HOUtK21EPEJMEBHCt5Mw2nXUuNsLL6GSJ
DnDbk2LCZYfspil2jJhArVYCE4b2ylmpicXP
KBapEyy3XAsGpJ34ZoyjfedT9FlZPQtZS58X
HA8mU8nDXEU2tdbr9O7QDzI8yKcusJCmKAry
cVx5UQFGvkDevellOpPeFqZze5ybzbHNK2It
xfGLFoGsXiZtxpW6DuJgeFyNrnnxFRQ16WCA
RIpvWCdMvkrEoYaqYVmAgYBznssLORhuZOTx
Oq5PBx0d4E6ssT06FI0CNtrXkT4ti2Bxe4At
rrv70yQvAvjBpJ8qfjzxVYcd5ImXo9jCet9T
UBDHf8PWWbWC0bunHyFOyJAyc4tAyKhnLl7n
lf5mJlhflLbYBAH8nbKB2Lp8C4qmalyJVz2t
bFEXDHTf0wWW3kbcM8VV1FSI0PipDmr1biP2
HozZjRhO1l7Uqc2nhiYKDkC45dg5ZURu2l4p
YhnzdwDQjrs89rk5Dli0p1dcxK0yXedxIeMw
A1DdfnctUK475FZI0dZ1iJtZ2s9ixQmxIcYc
Ae8y20H1X2IHgQ6DCuOTepuW1nhR7sH8EFt4
oUAFj16WJ27Q1BVV8S4cdalDp6ZwKRLmdLbS
YlMGccBLiLrl6F3mY4rDIFhqpTYZ5tLTNYZ9
KX107aJWnE1AYEZbDpPPQ23had8kwNr6qUwo
K0TDymjtzak6USOF199vq8pLWzMiWc9udSqI
ROF1azSd9hY8LBRcJUCxbmFNwl11My8rmDUJ
H1DNFv57hHrfqNk5XLpJh1UHWi0f364Bc9O1
CgOXcq6dNbuKnSKDK8M9zrSpVbXIkXymCmHb
zTZw2woP3PdY9gHGeeI3A91lhIsZqrVBcxcg
iYnZHyol6IMPK801042012        8Xrbcq
s9mfwL0aUjySD8H0oaL1ghXIT0f64jcijuLa
xieIhx9Tj5Aerj8hGjj7kYUCJhrxpWoV3Atp
hRg9TbhvpPJHqDIFf6puW9aR3BKelwlXBXXV
yTi5qa7w3htdDu9z1My98bR3lf5kd2PHLcDx
Y3yhvNTl65sJCv4GpJGFPvH7ighmCqaat5GR
EiYLt50Kh3wFUOAayJ0yMMHYquiEynhqWEx0
BHTJy3hD8L2fxPliGlJHYh2It33wZb1a5Kpz

Les paramètres passés au template sont bien contenus dans le rapport final (si, en cherchant bien!)

Voilà tout, au final il est assez simple de disposer de la puissance de Groovy, conjointement à un projet Java classique. Bon courage!


Fichier(s) joint(s) :