Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.cmdline.ex1.
Depuis votre départ du projet Paint
, les fonctionnalités de l'application se sont étoffées. L'application accepte maintenant plusieurs options sur la ligne de commande ainsi que plusieurs fichiers de figures.
-legacy -no-borders -with-borders -border-width int -window-name name -min-size int int
Votre nouveau projet est de faire une librairie pour parser les arguments de l'application Paint
sur la ligne de commande. Votre librairie sera utilisée par tous les projets d'EvilCorp. Elle devra donc respecter le Open-Close Principle. C'est à dire qu'une fois livrée, son code ne pourra pas être modifié par les autres projets mais qu'elle devra pouvoir s'adapter aux besoins des différentes applications.
Dans cet exercice, on ne s'intérresse qu'aux options sans paramètres (c'est-à-dire les 3 premières).
Avant d'être congédié, un de vos collège a proposé la classe CmdLineParser.java qui est utilisée comme suit dans Application.java. Fonctionnellement, le code fait ce qu'on attend: tout ce qui n'est pas une option déclarée est traité comme un nom de fichiers.
Lisez et comprenez le code de la classe CmdLineParser
et regardez son utilisation dans Application
.
Quels problèmes posent le code proposé ?
Modifiez la classe CmdLineParser
pour résoudre le problème.
Adaptez la classe Application
à votre nouvelle classe.
Félicitations vous avez livré la version 1.0 de CmdLineParser
. A partir de maintenant, vous ne pouvez plus changer les services publiques fournis par votre classe. En effet, votre code va être utilisé par d'autres projets et le passage à la version 2.0 ne doit pas casser le code qui fonctionne avec la version 1.0. C'est-à-dire qu'on fait le choix de la compatibilité ascendante, ce n'est pas toujours le cas quand on change de version majeure.
Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.cmdline.ex2. Recopiez vos classes de l'exercice précédent dans ce nouveau package.
On souhaite maintenant gérer en plus les options prenant un paramètre comme:
-border-width int -window-name name
Enrichir votre classe CmdLineParser
avec une méthode addOptionWithOneParameter
.
Quel doit être le type de cette méthode ?
Que doit-on faire quand le paramètre d'une option n'est pas présent lors de l'appel à process
?
Dans un deuxième temps, on ne veut qu'une seule Map
, et pas une pour les options sans argument et une pour les options avec argument. On est en train de se préparer à gérer des options avec plusieurs paramètres.
Quel est le type que l'on doit stocker dans cette unique Map
?
Modifiez votre code pour respecter cette contrainte.
Félicitations vous avez livré la version 2.0 de CmdLineParser
.
Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.cmdline.ex3. Recopiez vos classes de l'exercice précédent dans ce nouveau package.
A ce moment du projet, votre classe CmdLineParser doit avoir comme seules méthodes publiques:
void addOptionWithOneParameter(String name,Consumer<String> action)
qui ajoute une option avec un argument;void addFlag(String name,Runnable action)
qui ajoute une option sans argument;List<String> process(List<String> args)
Pour réaliser la version 3.0 de CmdLineParser
, vous avez des retours de vos utilisateurs, qui trouvent que le comportement de la version 2.0 de CmdLineParser
n'est pas satisfaisant:
CmdLineParser
Pour cet exercice, on accepte la perte de compatibilité ascendante de CmdLineParser
sur l'interprétation des options (désormais, tout ce qui commence par un tiret est une option), mais vous devez conserver les méthodes addFlag
et addOptionWithOneParameter
pour que du code qui compilait avec la version 2.0 continue de compiler avec la version 3.0.
Proposez une modification de votre classe CmdLineParser
.
Vous allez devoir introduire une classe Option
pour représenter une option, ses propriétés et son comportement. Est-ce une bonne idée de rendre le constructeur de cette classe visible directement ? Pensez aux futures évolutions de vos options.
Félicitations vous avez livré la version 3.0 de CmdLineParser
.
Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.paint.ex4. Recopiez vos classes de l'exercice précédent dans ce nouveau package.
Ajoutez la possibilité de déclarer plusieurs noms pour une option. Par exemple -h ou "--help" ou "-v" ou "--version".
Vous devez continuer à gérer le fait que certaines options peuvent être obligatoires ou pas.
Ajoutez de la documentation à une option. Cette docummentation sera utilisé par la méthode usage
de CmdLineParser
qui affiche la liste des options déclarées dans l'ordre alphabétique avec leur documentation si il y a une.
(Facultatif) Ajoutez la possibilité de signaler qu'une option est en conflit avec d'autres options.
Le traitement des options dans CmdLineParser
devient
plus délicat car il faut gérer le fait qu'une option peut avoir plusieurs noms lors de la détection des conflicts.
Les utilisateurs voudraient maintenant un moyen simple de définir certaines options, comme
-border-width int
, -timeout int
ou -max-threads int
),-min-size int int
, -max-size int int
ou -range int int
),InetSocketAdress
(par exemple pour -remote-server name int
ou -proxy name int
)... Félicitations vous avez livré la version 4.0 de CmdLineParser
.
Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.cmdline.ex5.
Les versions 3 et 4 ont introduit la possibilité de personnaliser les options:
La classe CmdLineParser
a donc désormais plusieurs responsabilités:
Si vous n'avez pas introduit une (ou des) classes pour séparer les responsabilités, votre code ne respecte plus le Single Responsability Principle.
Le but de cet exercice est de présenter une architecture logicielle qui respecte ce principe et qui continuera à le respecter même si l'on ajoute d'autres possibilités de personnalisation des options.
Dans un premier temps, on va ajouter une classe interne OptionsManager
. Cette classe gère le stockage des options avec leurs différents noms. Les autres personnalisations ne sont pour l'instant pas prises en compte.
private static class OptionsManager { private final HashMap<String, Option> byName = new HashMap<>(); /** * Register the option with all its possible names * @param option */ void register(Option option) { register(option.name, option); for (var alias : option.aliases) { register(alias, option); } } private void register(String name, Option option) { if (byName.containsKey(name)) { throw new IllegalStateException("Option " + name + " is already registered."); } byName.put(name, option); } /** * This method is called to signal that an option is encountered during * a command line process * * @param optionName * @return the corresponding object option if it exists */ Optional<Option> processOption(String optionName) { return Optional.ofNullable(byName.get(optionName)); } /** * This method is called to signal the method process of the CmdLineParser is finished */ void finishProcess() { } }
La classe OptionsManager
stocke les options. Elle maintient une map qui associe à chaque nom ou alias l'option correspondante. L'idée est que la classe CmdLineParser
va contenir un objet OptionsManager
et lui déléguer la gestion des options:
cmdLineParser.addFlag
, cmdLineParser.addOptionWithOneParameter
ou cmdLineParser.addOption
), la méthode register
de OptionsManager
sera appelée.process
de CmdLineParser
rencontrera une option, elle appelera la méthode processOption
de OptionsManager
cmdLineParser.process
, on appelera la méthode finishProcess
de OptionsManager
pour le prevenir qu'il peut faire des vérifications de cohérence avec l'ensemble des options enregistrées.
Modifier votre classe CmdLineParser
pour qu'elle utilise la classe OptionsManager
et supprimez tout le code qui gérait les
les options obligatoires, les documentations sur les options et les conflits entre les options.
On veut maintenant rajouter la gestion des options obligatoires, des documentations... La tentation naturelle est de rajouter toutes ces responsabilités dans la classe OptionsManager
, mais on propose une solution beaucoup plus flexible qui utilise le patron Observer.
L'idée est de permettre à des objets d'observer OptionsManager
. A chaque fois qu'une option est enregistrée, qu'une option est traitée ou que le travail de la méthode process
est terminé, les observateurs vont être notifiés.
Il y aura:
Nous vous proposons l'interface suivante pour ces observateurs:
interface OptionsManagerObserver { void onRegisteredOption(OptionsManager optionsManager,Option option); void onProcessedOption(OptionsManager optionsManager,Option option); void onFinishedProcess(OptionsManager optionsManager); }
Tout comme la classe OptionsManager
, cette interface et toutes ses implémentations seront des classes internes de CmdLineParser
.
Modifiez la classe OptionsManager
pour pouvoir enregister et notifier des OptionsManagerObserver
.
Pour tester, vous pouvez utiliser le LoggerObserver
ci-dessous qui ne fait rien à part afficher toutes les notifications qu'il reçoit.
class LoggerObserver implements OptionsManagerObserver { @Override public void onRegisteredOption(OptionsManager optionsManager, Option option) { System.out.println("Option "+option+ " is registered"); } @Override public void onProcessedOption(OptionsManager optionsManager, Option option) { System.out.println("Option "+option+ " is processed"); } @Override public void onFinishedProcess(OptionsManager optionsManager) { System.out.println("Process method is finished"); } }
Ecrivez un OptionsManagerObserver
qui gère les options
obligatoires.
Ecrivez un OptionsManagerObserver
qui gère la documentation. Cet observateur fournira une méthode String usage()
qui sera appelée par la méthode usage
de CmdLineParser
. Cela veut dire que contrairement aux autres observateurs, celui-ci doit être stocké dans un champ de la classe CmdLineParser
. Les autres observateurs n'ont pas besoin d'être stocké directement, il suffit de les enregistrer auprès du gestionnaire d'options.
Ecrivez un OptionsManagerObserver
qui gère les conflits entre options.
Toutes vos classes pour cet exercice doivent être dans le package fr.uge.poo.cmdline.ex6. Recopiez vos classes de l'exercice précédent dans ce nouveau package.
Les utilisateurs de CmdLineParser
sont divisés sur le traitement des paramètres d'une option. Pour fixer les choses, considérons l'exemple suivant:
var hosts = new ArrayList(); var cmdParser = new CmdLineParser(); var optionHosts = new OptionBuilder("-hosts",2, hosts::addAll).build(); cmdParser.addOption(optionHosts); cmdParser.addFlag("-legacy",()->{}); String[] arguments = {"-hosts","localhost","-legacy","file"}; cmdParser.process(arguments);
Avec la politique actuelle, ce code lève une ParseException
.
En effet, l'option -hosts attend 2 arguments et elle n'a pas le droit
de consommer une autre option. C'est la politique de traitement des options
STANDARD.
Une partie des utilisateurs voudrait qu'il n'y ait pas d'exception levée. Ils voudraient
que le Consumer
de l'option -hosts soit appelé avec la liste contenant seulement "localhost"
même si l'option demande 2 paramètres.
C'est ce qu'on appelera dans la suite la politique de traitement des options RELAXED.
Enfin une autre partie des utilisateurs voudrait que comme dans la version 1.0 de la librairie, les options puissent consommer d'autres options comme paramètres. Dans notre
exemple, le Consumer
de l'option -hosts serait appelé avec une liste contenant "localhost"
et "-legacy"
.
C'est ce qu'on appelera dans la suite la politique de traitement des options OLDSCHOOL.
On va utiliser le patron Strategy pour satisfaire tous les utilisateurs. La méthode process
de CmdLineParser
va prendre en deuxième argument une ParameterRetrievalStrategy
qui est simplement le code qui s'occupe de récupérer les arguments d'une option.
Quelle est l'interface de ParameterRetrievalStrategy
dans votre code ?
Implémentez les politiques STANDARD, RELAXED et OLDSCHOOL comme des ParameterRetrivalStrategy
Que devient la méthode process
à un argument ? Pensez à la compatibilité ascendante.