Ce TP noté dure 4h.
Votre entreprise veut développer un outil de gestion et de partage de documents.
On vous a confié la réalisation d'un prototype pour la partie Indexation des documents.
Le cahier des charges suivant (simplifié) vous a été donné :
-
L'indexation consiste, pour chacun des documents d'un répertoire et de ses sous-répertoires, à associer à chaque document,
une liste de mots avec le nombre d'occurrences de chaque mot.
-
Attention, il faut pouvoir traiter différents types de fichiers (TEXTE simple, XML, ZIP, etc)
et la manière de les lire et de repérer les mots sera évidemment différente.
Votre index sera plus tard utilisé par un module de recherche (pris en charge par une autre équipe).
Pour des besoins d'administration des index, il faudra pouvoir afficher le contenu de l'index,
soit document par document (avec la liste des mots et le nombre d'occurrences) soit globalement
pour l'ensemble de tous les documents.
Le sujet consiste à construire par étape un prototype de cet index.
Pour tester votre programme, nous vous fournissons un jeu de 4 fichiers
data.txt
data2.txt
data3.xml
data4.zip
Dans le but de faciliter la correction, on vous demande de dupliquer le code en le mettant
dans un package Java différent pour chaque question, v1 pour la question 1, v2 pour la question 2, etc.
Comme base, on vous donne le code qu'un stagiaire a écrit :
FullIndex.java,
WordMap.java,
Main.java
et qui permettent de construire un
Index pour des fichiers texte simples.
-
Adaptez le code pour traiter uniquement les fichiers "*.txt" d'un répertoire spécifié en ligne de commande.
dans le cas où un fichier ne possède pas l'extension ".txt", vous devrez écrire un message d'erreur
sur la sortie d'erreur standard et continuer le traitement avec le fichier suivant.
Note: Vous pouvez utiliser au choix soit Files.list()
Path directory = Paths.get(".");
try(Stream<Path> stream = Files.list(directory)) {
stream.forEach(p -> System.out.println(p.toString()));
}
soit Files.newDirectoryStream
try(DirectoryStream<Path> list = Files.newDirectoryStream(directory)) {
for(Path p: list) {
System.out.println(p.toString())
}
}
Le code que vous devez produire doit être dans le package "v1".
-
Le chef de projet vous demande de modifier le code pour inclure aussi des statistiques globales sur les fichiers traités.
Il vous faut changer le code pour supporter les nouvelles options :
-
-byfile qui affiche les mots et leur nombre d'occurences pour chaque fichier séparément (comme c'est le cas actuellement)
-
-global qui affiche les mots et leur nombre d'occurences dans l'ensemble des documents
De plus, quelque soit l'option choisie le nombre total de documents devra être affiché.
Votre chef de projet pose plusieurs contraintes :
-
il est interdit de parcourir plusieurs fois les fichiers
-
il est interdit de construire un index quand cela n'est pas nécessaire, donc on ne construit que ce qui est nécessaire pour les options -byfile et -global.
-
il est interdit de dupliquer du code.
-
le code doit permettre d'ajouter de futures options facilement.
Exemple de format attendu, avec l'option "-byfile"
-------------> 2 documents
data2.txt, #words= 96
Apple:1
Applications:1
As:1
BCP:1
Framework:1
---------------------
data.txt, #words= 124
ASCII:3
According:1
Although:1
Because:1
Common:1
---------------------
et avec l'option "-global"
-------------> 2 documents
#words= 196
ASCII:3
According:1
Although:1
Apple:1
Applications:1
---------------------
Pour l'instant, les options sont exclusives les unes des autres et si aucune option n'est fournie, le résultat devra être équivalent à "-byfile",
c-à-d produire le même resultat que le code que l'on vous a fourni initialement.
Conseil : avant d'ajouter une nouvelle fonctionnalité, faite du refactoring i.e. modifier le code courant sans changer le comportement de sorte qu'il soit facile
dans un second temps d'ajouter la fonctionnalité.
Faite dans un premier temps un diagramme de classes UML du code puis écrire le code dans le package "v2".
-
Il est maintenant temps de savoir traiter des fichiers de différents type; on va ici s'intéresser uniquement aux fichiers XML
mais votre design devra pouvoir supporter des fichiers d'autres types facilement.
Vous allez donc remplacer une fonction static unique processFile par l'appel à du code spécialisé pour chaque type de fichier.
Pour vous aider, voici un exemple de code permettant d'extraire des mots dans un fichier XML.
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
SAXParser saxParser;
try {
saxParser = parserFactory.newSAXParser();
} catch (ParserConfigurationException | SAXException e) {
throw new IOException(e);
}
try(InputStream input = Files.newInputStream(path)) {
saxParser.parse(input, new DefaultHandler() {
private final StringBuilder builder = new StringBuilder();
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (builder.length() != 0) {
textFound();
}
builder.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) {
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
textFound();
builder.setLength(0);
}
private void textFound() {
System.out.println("found a line of text " + buffer.toString());
}
});
} catch(SAXException e) {
throw new IOException(e);
}
Faites le schéma UML des modifications que vous proposez.
Avez-vous mis en place un design pattern ? si oui expliquez lequel et pourquoi ?
Puis écrire le code dans le package "v3".
-
Pas de chance, le client a toujours une nouvelle demande !
Il veut ajouter une option -all, qui affiche les stats globales puis les stats locales,
et il est bien sûr toujours interdit de parcourir plusieurs fois les fichiers.
Quel design pattern allez vous mettre en place ?
Ecrire le code dans le package "v4".
-
On veut maintenant être capable de traiter aussi les fichiers ZIP.
Expliquez comment vous allez adapter votre design et votre code ?
Quelles classes / interfaces avez-vous besoin de réouvrir ?
Etes vous capable de lire les fichiers ZIP dans les fichier ZIP ?
Faites le schéma UML de la solution que vous proposez et
écrire le code dans le package "v5".
Il y a deux façon d'ouvrir des fichiers ZIP, en utilisant un Stream
Path path = ...
try (ZipFile zipFile = new ZipFile(path.toFile())) {
zipFile.stream().forEach(zipEntry -> {
InputStream input = zipFile.getInputStream(zipEntry);
// use zipEntry and input here
});
}
ou en utilisant un ZipInputStream
Path path = ...
InputStream fileInput = Files.newInputStream(path);
try(ZipInputStream input = new ZipInputStream(fileInput)) {
ZipEntry zipEntry;
while((zipEntry = zipInputStream.getNextEntry()) != null) {
// use zipEntry and input here
}
}
En aucun cas vous ne devez décompresser le fichier ZIP sur le disque avant de le traiter !