Paris JUG : Petites librairies Java

G. Laforge expliquant Pretty Time

La session du Paris JUG du 8 novembre 2011 était consacrés aux petites librairies Java.

I. Pretty Time

Guillaume Laforge, leader du projet Groovy nous a présenté la librairie PrettyTime développée par Lincoln Baxter III qui travaille chez OcpSoft et RedHat. Celle-ci permet de présenter des dates sous forme de dates relatives du type : à l’instant, dans 10 minutes, il y a 3 heures. Guillaume l’utilise sur son blog pour afficher la date des posts ce qui permet d’indiquer la date de la façon suivante : Posted on 09 November, 2011 (2 days ago). La librairie est constituée d’un jar n’a pas de dépendance. Elle supporte 18 langues dont le français et l’anglais et est très simple à utiliser.

// on initialise avec une date de reference PrettyTime t = new PrettyTime(new Date(1000 * 60 * 12), new Locale("fr")); // on formate une date par rapport a cette date de reference assertEquals("il y a 12 minutes", t.format(new Date(0)));

on peut utiliser facilement PrettyTime avec Grails ou JSF.

II. GPars

GPars (prononcez Jeepers) est une librairie permettant de s’adapter au multi-processeurs et au multi-coeurs en faisant de la programmation concurrente.

il est difficile de bien utiliser ce  que le JDK propose : threads / synchronize / wait-notify-notifyAll, qui peuvent conduire à

  • des dead-locks, des live-locks (in interblocage qui consomme du CPU), des race conditions ou l’épuisement de ressources
  • des problèmes de gestion d’accès à la mémoire partagée

Pour éviter ces problèmes, GPars propose d’utiliser l’immuabilité et le passage de messages au travers des paradigmes suivants

un exemple avec les fonctions parallèles pour les collections

@Grab('org.codehaus.gpars:gpars:0.12') import static groovyx.gpars.GParsPool.withPool defnums=1..100000 withPool(5){ def squares=nums.collectParallel{it**2} .grepParallel{it%7it%5} .grepParallel{it%30} println squares[0..3]+"..."+squares[-3..-1] assertsquares[0..3]==[36,144,1089,1296] }

on utilise un pool de taille nombre de coeurs + 1 afin de maximiser l’utilisation du CPU car souvent on a un thread qui sert à la coordination des autres et qui consomme peu de ressources. GPars a comme dépendance les librairies jsr166y et extra166y. Il dispose d’une API Java et d’une API Groovy.

Selon le problème que l’on a à résoudre on utilisera des outils différents. Cela est bien présenté par le graphe suivant.

GPars when to use what

les slides de la présentation de Guillaume ici

III. Project Lombok (ou comment cacher la plomberie)

Florent Ramière de la société Jaxio nous a présenté Lombok, “un outil qui sert à enlever la plomberie”. Il s’agit d’un plugin eclipse qui va générer du code technique grâce à des annotations, le source ne sera donc pas pollué par ce code technique qui existera néanmoins dans le bytecode après compilation. En utilisant Lombok on pourra donc clarifier son code et se concentrer sur d’autres parties de son code. On peut bien sûr utiliser les assistants eclipse pour générer un code similaireà celui généré par Lombok.

on dispose notamment des annotations suivantes

@Getter

@Setter

@EqualsAndHashCode

@RequiredArgsConstructor un constructeur avec tous les champs finaux

@Data est un raccourci pour @ToString, @EqualsAndHashCode, @Getter sur tous les champs et @Setter sur tous les champs non-finaux, et @RequiredArgsConstructor! à utiliser pour ses beans.

@Delegate permet de déléguer toutes les méthodes publiques de l’attribut annoté, excepté les méthodes présentes dans java.lang.Object.

@Log et ses variantes @CommonsLog, @Log4j et @Slf4j permet d’instancier une variable statique privée log

@Synchronized permet de générer un bloc synchronize sur une variable privée $lock ou $LOCK (dans le cas d’une méthode statique)

val est un type Lombok permettant d’avoir des variables ‘final’ et de bénéficier de l’inférence de type.On peut ainsi écrire

val map = new HashMap();

la page suivante explique très bien le code généré par Lombok. on peut d’ailleurs si besoin générer ce code en utilisant delombok (pour GWT notamment ou si l’on veut utiliser le code générer sans dépendre de lombok). lombok-pg est un ensemble d’extensions pour lombok.

Lombok peut également être utilisé via ant, maven ou javac.

Au détour de ses démonstrations, florent nous a également conseillé l’outil awaitility qui permet de faire des tests sur du code asynchrone

IV. Apache Commons Configuration

Emmanuel Bourg est membre de la fondation Apache et est commiter sur le projet Apache Commons Configuratio qu’il est venu nous présenter. Il s’agit d’une abstraction de la configuration d’une application qui présente une interface simple de type clé/valeur pour les données. Elle permet de gérer différents formats de données et des transformations dans les types voulus.

On peut gérer un format Properties amélioré

list1=val1,val2,val3 list2=val1 list2=val2 list2=val3 include=chunk.properties

On peut également utiliser des fichiers XML, Property List XML, Ini ou encore une base données.

Plusieurs formats sont prévus : OGDL, YAML, JSON, Registry Windows, Binary Property List. Les contributions sont les bienvenues si vous voulez aider à ajouter le support des nouveaux formats. On peut combiner plusieurs configurations grâce à CompositeConfiguration. On peut également utiliser un descripteur de configuration. Commons Configuration s’intègre très bien avec la librairie d’abstraction de systeme de fichiers Apache Commons VFS.

Sauvegarde automatique

PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties"); config.setAutoSave(true);

Rechargement automatique (on peut également déclencher le rechargement)

PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties"); config.setReloadingStrategy(new FileChangedReloadingStrategy());

V. Guava (N’aime pas les null)

Thierry Leriche-Dessirier (Indépendant) et Etienne Neveu (SFEIR) nous ont fait faire un tour du propriétaire de Guava, la librairie utilitaire de Google autrefois nommée google-collections.

Factory methods

Inférence de type : grâce à des factory statiques on peut écrire

List primeNumbers = newArrayList(); Set colors = newTreeSet(); Map ages = newHashMap();

ce qui n’est plus si intéressant que ca en Java 7 avec la notation diamant.
L’initialisation de collection est également facilitée

List dogs = newArrayList(Dog1, Dog2, Dog3)

Programmation fonctionnelle

On peut filtrer une collection grâce à un prédicat

Iterable maleDogs = Iterables.filter(dogs, new Predicate() { public boolean apply(Dog dog) { return dog.getSex() == MALE; } });

un predicat peut utiliser les prédicats and, in, or, not déjà définis dans la classe Predicates

boolean isMilouInBoth = and(in(dogs), in(superDogs)) .apply(new Dog("Milou"));

on peut transformer les elements d’une liste en utilisant une Function

List chiens = Lists.transform(dogs, new Function() { public Chien apply(Dog dog) { return new Chien(dog.getName()); } });

Attention ce sont des vues qui sont retournées. cela peremt d’avoir du lazy-loading mais la conversion sera faite autant de fois que nécessaire.
Iterables.find utilise le même prédicat que pour Iterables filter mais renvoit le premier objet correspondant au predicat ou un objet par défaut si rien ne correspondait au prédicat.

Dog firstMale = Iterables.find(dogs, malePredicate, DEFAULT);

collections immuables

on a des syntaxes plus concises que la syntaxe standard tout en proposant de la copie défensive.

ImmutableSet.of(1, 2, 3, 5, 7); ImmutableSet.copyOf(favoriteColors)

base.Objects

cette classe propose d’aider à la simplification de l’écriture des méthodes equals(), hashcode() ou toString()

//equals Objects.equal(name, that.name) && Objects.equal(weight, that.weight) && sex == that.sex; // hashcode Objects.hashCode(name, weight, sex); //toString Objects.toStringHelper(this) .add("name", name) .add("weight", weight) .add("sex", sex) .toString();

on peut créer facilement un comparateur

public int compareTo(Dog other) { return ComparisonChain.start() .compare(name, other.name) .compare(weight, other.weight) .compare(sex, other.sex) .result(); }

base.Preconditions

on peut vérifier des paramètres

this.currency = checkNotNull(currency, "currency cannot be null"); checkArgument(amount.signum() >= 0, "must be positive: %s", amount);

base.CharMatcher

il s’agit d’un outil permettant d’exprimer une expression régulière en language naturel. Il permet de remplacer avantageusement StringUtils des Apache Commons.

Collections

on dispose de collections n’existant pas dans le JDK

MultiMap : il s’agit d’une map pouvant avoir plusieurs valeurs pour une même clé

Multimap favoriteColors = HashMultimap.create();

BiMap : il s’agit d’une map pouvant n’ayant pas 2 fois la même valeur

BiMap favoriteColors = HashBiMap.create();

Multiset: il s’agit d’un set pouvant contenir plusieurs fois la même valeur (on peut même connaître le nombre de doublons)

Multiset primeNumbers = HashMultiset.create();

Joiner et Splitter permettent de manipuler les conversions entre chaine de caractères et liste de String.
En utilisant Joiner, on aura une NullPointerException si un des éléments de la liste vaut null mais l’on pourra utiliser skipNulls() ou userForNull(MY_DEFAULT) pour spécifier le comportement lorsque l’on rencontre un null.

List dogs = newArrayList("Milou", "Idefix", "Rintintin"); String names = Joiner.on(", ").join(dogs);

En utilisant Splitter on peut nettoyer le résultat en utilisant trimResults() et omitEmptyStrings()

Iterable superDogs = Splitter.on(",") .trimResults() .omitEmptyStrings() .split("Lassie, , Volt, Milou");

Cache

Memoization

la memoization permet de garder en cache une unique valeur. l’exemple donné était la mise en cache du numéro de version du dernier jdk. On défini via l’utilisation d’un Supplier comment aller chercher la valeur qui nous intéresse. Celle valeur ne sera récupérée que lors du premier appel (lazy loading). Les appels suivant utiliserons le cache.

private Supplier versionCache = Suppliers.memoize( new Supplier() { public JdkVersion get() { return jdkWebService.checkLatestVersion(); } } );

On peut également préciser une durée de validité du cache avec memoizeWithExpiration()

Key-Value

On peut construire un Cache à plusieurs entrées grâce aux classes CacheBuilder et CacheLoader

private Cache<Long, Film> filmCache = CacheBuilder.newBuilder() .maximumSize(2000) .expireAfterWrite(30, TimeUnit.MINUTES) .build(new CacheLoader<Long, Film>() { public Film load(Long filmId) { return imdbWebService.loadFilmInfos(filmId); } });

Concurrent

Guava propose d’étendre Future avec la classe ListenableFuture. On peut ainsi associer un listener à un Future.

ListenableFuture future = ... future.addListener(new Runnable() { public void run() { logger.debug("Future is done"); } }, executor);

On peut également faire de la programmation asynchrone grâce à FutureCallback. cela permet d’isoler le traitement des exceptions.

Futures.addCallback(future, new FutureCallback() { public void onSuccess(Result result) { storeInCache(result); } public void onFailure(Throwable t) { reportError(t); } });

pour utiliser des ListenableFuture on utilise un ListeningExecutorService de la façon suivante.

ListeningExecutorService executor = MoreExecutors.listeningDecorator(executor); public ListenableFuture getNote(final Film film) { return executor.submit(new Callable() { public Note call() { return imdbWebService.getNote(film); } }); }

En utilisant les méthodes proposées par Futures on peut facilement proposer des méthodes non bloquantes qui renvoient un Future au lieu d’une liste de notes. Cela permet au programme appelant de ne pas bloquer pendant la récupération des notes et d’effectuer d’autres traitements pendant celle-ci.

ListenableFuture <List> getNotes(Film film) { ListenableFuture noteImdb = imdbService.getNote(film); ListenableFuture noteAllo = allocineService.getNote(film); return Futures.allAsList(noteImdb, noteAllo); }