Eclipse over the rainbow... Java up high...

Toujours plus haut dans les nuages avec Eclipse! Pourquoi se contenter de gérer les sources des projets sur des serveurs distants partagés, alors qu'il est désormais possible de se passer d'IDE? C'est en gros le message que vient de faire passer la fondation Eclipse en annonçant la naissance d'un nouveau projet dans son écosystème : Orion.

Mais il ne faut pas s'y tromper : le but ici n'est pas de porter l'IDE dans un navigateur, mais bien de présenter un tout nouveau paradigme dans le monde du développement. La tendance actuelle étant de tout orienter vers les navigateurs web (est-ce bien raisonnable au fond?), la fondation Eclipse ne pouvait manquer cette occasion pour repenser ses outils et envisager le navigateur comme une toute nouvelle plateforme de développement.

Orion a donc pour vocation de fournir un outil léger par l'intermédiaire d'une expérience web. Son état encore embryonnaire à ce jour ne permet pour le moment que l'écriture de code Javascript, mais le but est de fournir d'ici quelques mois un panel plus complet notamment par l'ajout du versionage des fichiers (grâce à une intégration avec Git) et du debug (à l'aide de Firebug). Son interface est entièrement codée en Javascript et communique avec des librairies Java/Osgi par l'intermédiaire de REST.

Le code d'Orion est d'ores-et-déjà testable et téléchargeable ici. Même s'il est pour l'instant très limité, il faut admettre que l'idée peut paraître intéressante et qu'il faudra donc garder un oeil attentif sur son évolution.

Comme précisé plus haut, ce projet s'inscrit dans la volonté future d'Oracle de déporter les utilisations actuelles de Java en général, comme l'illustre les liens ce-dessous. A ce titre, Java EE 6 et plus tard (2012 environ) Java EE 7 vont proposer toujours plus d'outils pour déployer les applications vers le web. Plusieurs sociétés se positionnent déjà en tant que prestataires PaaS, en particulier VMware qui a récemment annoncé un partenariat pour promouvoir ce concept au travers du projet VMforce.

Sources

Java in the cloud


Fichier(s) joint(s) :



[Swing] Limiter la saisie dans une combobox editable

Plus compliqué qu'il n'y parait!

Le composant qui permet de saisir du texte dans ce type de combobox est un simple JTextField, mais il est impossible de paramétrer son nombre de caractères maximal aussi simplement qu'avec un JTextField classique.

Le text field est accessible grâce au code suivant :

JTextField tf = (JTextField)(combo.getEditor().getEditorComponent());

La solution consiste donc à modifier l'objet Document associé au champ (ici la limite sera fixée à 32 caractères) :

tf.setDocument(new JTextFieldLimit(32));

class JTextFieldLimit extends PlainDocument {
    private int limit;
    JTextFieldLimit(int limit) {
        super();
        this.limit = limit;
    }
    @Override
    public void insertString(int offset, String str, AttributeSet attr) {
        try {
            if (str == null)
                return;
            if ((getLength() + str.length()) <= limit) {
                super.insertString(offset, str, attr);
             }
        } catch (BadLocationException e) {}
    }
}

Un problème persiste cependant : à chaque fois que l'utilisateur essaiera de taper plus de 32 caractères, une erreur sera levée dans la console :

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: bad position: 33
        at javax.swing.text.JTextComponent.moveCaretPosition(JTextComponent.java:1523)
        at org.jdesktop.swingx.autocomplete.AbstractAutoCompleteAdaptor.markText(AbstractAutoCompleteAdaptor.java:116)
        at org.jdesktop.swingx.autocomplete.AutoCompleteDocument.insertString(AutoCompleteDocument.java:161)
        at javax.swing.text.JTextComponent.replaceSelection(JTextComponent.java:1358)
...

Cette erreur est volontairement levée par le composant java afin de maximiser la rétro-compatibilité, comme justifié dans ce bug : Oracle bug database (4903033). Il faut donc s'en contenter ou arriver à encapsuler suffisamment le composant pour récupérer l'erreur.


Fichier(s) joint(s) :



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