Closure Commons Collections
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
.
Sommaire
Votre avis
Nobody voted on this yet
|
|
Implémentation
Dans le cadre de cette étude, deux implémentations sont mises en place.
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.
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".
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.
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
.
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.
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
.