Closure Commons Collections

De EjnTricks
Révision de 29 septembre 2016 à 20:54 par Etienne (discussion | contributions)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

L'utilisation de Closure, interface org.apache.commons.collections4.Closure, va permettre d'exécuter une action à partir d'objets. Cette interface définie la méthode execute.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Java format icon.png Implémentation

Dans le cadre de cette étude, deux implémentations sont mises en place.

Icon-log.png Ecriture log

La première implémentation va permettre d'écrire les informations de l'instance People à l'aide d'un logger, avec le framework SLF4J.

package fr.ejn.tutorial.apache.commons.collections4.closure;

import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Closure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Sample closure used to execute logging information on People instance.
 *
 * @author Etienne Jouvin
 *
 */
public class LogClosure implements Closure<People> {

  private static final Logger LOGGER = LoggerFactory.getLogger(LogClosure.class);

  /** {@inheritDoc} */
  @Override
  public void execute(People input) {
    if (null != input) {
      LOGGER.trace("Execute on people");
      LOGGER.debug("Name : {}", input.getName());
      LOGGER.debug("Surname : {}", input.getSurname());
      LOGGER.debug("Gender : {}", input.getGender());
    }
  }

}

La méthode execute teste que l'argument n'est pas null, permettant de s'affranchir les NullPointerException, et invoque les fonctions de trace du logger.

Save-icon.png Sauvegarde

La seconde implémentation va permettre d'invoquer la fonction save d'une instance de PeopleDao.

package fr.ejn.tutorial.apache.commons.collections4.closure;

import fr.ejn.tutorial.dao.PeopleDao;
import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Closure;

/**
 * Sample closure used to execute logging information on People instance.
 *
 * @author Etienne Jouvin
 *
 */
public class SavePeopleClosure implements Closure<People> {

  private PeopleDao peopleDao;

  /**
   * Constructor with the DAO.
   *
   * @param peopleDao DAO instance.
   */
  public SavePeopleClosure(PeopleDao peopleDao) {
    this.peopleDao = peopleDao;
  }

  /** {@inheritDoc} */
  @Override
  public void execute(People input) {
    if (null != input) {
      this.peopleDao.save(input);
    }
  }

}

La méthode execute teste que l'argument n'est pas null, permettant de s'affranchir les NullPointerException, et invoque la fonction save du DAO spécifiée dans le constructeur. Attention, il n'y a aucun test sur l'instance fournie du DAO dans le constructeur, ce n'est pas totalement "null safe".


Start-icon.png Save-icon.png Sauvegarde éléments

Les instances de Closure permettent d'exécuter une action sur les éléments. Un exemple est implémenté dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.closure.SavePeopleClosureTest.

package fr.ejn.tutorial.apache.commons.collections4.closure;

import fr.ejn.tutorial.dao.PeopleDao;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import fr.ejn.tutorial.test.impl.dao.PeopleDaoImpl;

import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.collections4.Closure;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class SavePeopleClosureTest {

  @Rule
  public TemporaryFolder folder = new TemporaryFolder();

  private PeopleDao peopleDao;

  @Before
  public void setUp() throws Exception {
    PeopleDaoImpl dao = new PeopleDaoImpl();
    dao.setTargetFolder(folder.getRoot().getAbsolutePath());

    peopleDao = dao;
  }

  @Test
  public void testExecute() throws Exception {
    People people = new People();
    people.setName("Etienne");
    people.setGender(Gender.MAN);
    people.setSurname("Jouvin");

    List<People> peoples = new ArrayList<People>(3);
    peoples.add(people);

    // Add a null to test closure on null.
    peoples.add(null);

    people = new People();
    people.setName("Mother");
    people.setGender(Gender.WOMAN);
    people.setSurname("Jouvin");
    peoples.add(people);

    // Execute the closure
    Closure<People> closure = new SavePeopleClosure(this.peopleDao);
    IterableUtils.forEach(peoples, closure);

    // Validate the execution.
    File rootFolder = folder.getRoot();
    String[] files = rootFolder.list(new SuffixFileFilter(".txt"));
    Assert.assertNotNull("Sub files should not be null", files);
    Assert.assertEquals("Validate number of files", 2, files.length);

    String targetPath = FilenameUtils.concat(rootFolder.getAbsolutePath(), "1.txt");
    List<String> actual = FileUtils.readLines(new File(targetPath), Charset.defaultCharset());
    List<String> expected = Arrays.asList(
        /* Display name. */
        "Name : Etienne",
        /* Display surname. */
        "Surame : Jouvin",
        /* Display gender. */
        "Gender : MAN");
    Assert.assertEquals("Validate content", expected, actual);

    targetPath = FilenameUtils.concat(rootFolder.getAbsolutePath(), "2.txt");
    actual = FileUtils.readLines(new File(targetPath), Charset.defaultCharset());
    expected = Arrays.asList(
        /* Display name. */
        "Name : Mother",
        /* Display surname. */
        "Surame : Jouvin",
        /* Display gender. */
        "Gender : WOMAN");

    Assert.assertEquals("Validate content", expected, actual);
  }

}

Ce test unitaire utilise les Rules de JUnit afin de créer un répertoire temporaire, qui sera utilisé pour le DAO. L'avantage de cette utilisation est que le répertoire est automatiquement supprimé après l'exécution du test, et permet d'avoir un environnement vierge.

Le DAO est construit dans la méthode avec l'annotation Before.

Dans le test, une liste est créée dans un premier temps. Puis la fonction forEach de la classe IterableUtils est exécutée sur la liste avec une instance du Closure. Ainsi, les informations de chacun des éléments sont sauvegardées à l'aide du DAO, à savoir la création d'un fichier dans le répertoire spécifié.

Le test unitaire valide le contenu du répertoire de travail ainsi que le contenu des fichiers créés.

A noter que ce test utilise des fonctions utiles des autres librairies de Apache Commons, comme la manipulation des fichiers avec Commons IO.


Icon-memory.png Implémentations standard

L'interface Closure permet d'écrire ses propres logiques d'exécution fonctionnelle. Mais quelques utilitaires sont fournis depuis la classe org.apache.commons.collections4.ClosureUtils.

Multiples-icon.png Multiple instances

Dans les exemples précédents, l'implémentation LogClosure a été écrite pour ecrire dans les logs, et la classe SavePeopleClosure our effectuer une sauvegarde à l'aide d'un DAO. Les deux instances peuvent être combinées afin de les exécutées les unes après les autres. Ainsi, nous pouvons simuler une écriture dans les traces avant l'appel au DAO grace à l'implémentation de la classe org.apache.commons.collections4.functors.ChainedClosure retournée par la fonction chainedClosure.

Le test unitaire fr.ejn.tutorial.apache.commons.collections4.closure.ChainedClosureTest illustre ce cas d'utilisation.

  @Test
  public void testExecute() throws Exception {
    People people = new People();
    people.setName("Etienne");
    people.setGender(Gender.MAN);
    people.setSurname("Jouvin");

    List<People> peoples = new ArrayList<People>(3);
    peoples.add(people);

    // Add a null to test transform on null.
    peoples.add(null);

    people = new People();
    people.setName("Mother");
    people.setGender(Gender.WOMAN);
    people.setSurname("Jouvin");
    peoples.add(people);

    // Execute the closure
    Closure<People> saveClosure = new SavePeopleClosure(this.peopleDao);
    Closure<People> logClosure = new LogClosure();

    IterableUtils.forEach(peoples, ClosureUtils.chainedClosure(Arrays
        .asList(logClosure, saveClosure)));

    // Validate the execution.
    File rootFolder = folder.getRoot();
    String[] files = rootFolder.list(new SuffixFileFilter(".txt"));
    Assert.assertNotNull("Sub files should not be null", files);
    Assert.assertEquals("Validate number of files", 2, files.length);

    String targetPath = FilenameUtils.concat(rootFolder.getAbsolutePath(), "1.txt");
    List<String> actual = FileUtils.readLines(new File(targetPath), Charset.defaultCharset());
    List<String> expected = Arrays.asList(
        /* Display name. */
        "Name : Etienne",
        /* Display surname. */
        "Surame : Jouvin",
        /* Display gender. */
        "Gender : MAN");

    Assert.assertEquals("Validate content", expected, actual);

    targetPath = FilenameUtils.concat(rootFolder.getAbsolutePath(), "2.txt");
    actual = FileUtils.readLines(new File(targetPath), Charset.defaultCharset());
    expected = Arrays.asList(
        /* Display name. */
        "Name : Mother",
        /* Display surname. */
        "Surame : Jouvin",
        /* Display gender. */
        "Gender : WOMAN");

    Assert.assertEquals("Validate content", expected, actual);
  }

L'utilisation de ce type de Closure va donc permettre d'en enchainer une multitude.

Plugin-icon.png Condition exécution

Sur l'article Predicate, l'implémentation GenderPredicate est présentée afin de filtrer les instances de People en fonction de la valeur de gender. Grace à l'instance de org.apache.commons.collections4.functors.IfClosure, retournée par la fonction ifClosure, il est possible de conditionner l'exécution d'une instance de Closure.

Le test unitaire fr.ejn.tutorial.apache.commons.collections4.closure.ConditionalClosureTest illustre ce cas d'utilisation.

  @Test
  public void testExecute() throws Exception {
    People people = new People();
    people.setName("Etienne");
    people.setGender(Gender.MAN);
    people.setSurname("Jouvin");

    List<People> peoples = new ArrayList<People>(3);
    peoples.add(people);

    // Add a null to test closure on null.
    peoples.add(null);

    people = new People();
    people.setName("Mother");
    people.setGender(Gender.WOMAN);
    people.setSurname("Jouvin");
    peoples.add(people);

    // Create the predicate
    Predicate<People> predicate = new GenderPredicate(Gender.MAN);

    // Execute the closure
    Closure<People> closure = new SavePeopleClosure(this.peopleDao);

    // Create the conditional Closure
    Closure<People> conditionalClosure = ClosureUtils.ifClosure(predicate, closure);

    IterableUtils.forEach(peoples, conditionalClosure);

    // Validate the execution.
    File rootFolder = folder.getRoot();
    String[] files = rootFolder.list(new SuffixFileFilter(".txt"));
    Assert.assertNotNull("Sub files should not be null", files);
    Assert.assertEquals("Validate number of files", 1, files.length);

    String targetPath = FilenameUtils.concat(rootFolder.getAbsolutePath(), "1.txt");
    List<String> actual = FileUtils.readLines(new File(targetPath), Charset.defaultCharset());
    List<String> expected = Arrays.asList(
        /* Display name. */
        "Name : Etienne",
        /* Display surname. */
        "Surame : Jouvin",
        /* Display gender. */
        "Gender : MAN");
    Assert.assertEquals("Validate content", expected, actual);
  }

Avec ce test, le Predicate est exécuté dans un premier temps. Si l'instance valide l'objet, le Closure est alors exécutté. Sur ce test, le DAO ne sera exécuté que pour les instances de People dont la valeur de gender est égale à MAN.

Dans l'article sur les Predicates, il est illustré une utilisation de NotNullPredicate permettant d'échapper les nulls. Ainsi, il serait possible de supprimer le test de nullité dans l'implémentation du Closure.