On souhaite écrite un petit programme qui découpe les arguments de la ligne de commande,
en séparant les arguments classiques ("foo.txt", "bar.png"), des options qui généralement
commencent par "-" ou "--". En fait, c'est un peu plus compliqué, un argument peut commencer
par "-" ou "--" si jamais il n'est pas listé dans la liste des options.
Pour l'exercice, on considérera qu'il existe les options "-v", "--verbose", "-a" et "--all",
les deux premières correspondent à la valeur d'enum
OptionInfo.VERBOSE et les deux dernières
à la valeur d'enum
OptionInfo.ALL. Avec l'enum
OptionInfo défini comme ceci
package fr.uge.cmdline1;
public enum OptionInfo {
ALL, VERBOSE
}
Toutes les classes sont à créer dans le package
fr.uge.cmdline1, avec
le
main est dans la classe
CmdLine1.
Vous avez le droit de lire l'énoncé jusqu'au bout pour avoir une idée de là où nous allons !
-
Mais avant d'utiliser les OptionInfo, nous allons commencer par un code plus simple.
On souhaite écrire une classe Argument avec un champ text
de telle sorte à ce que le code suivant fonctionne et affiche les bonnes valeurs
public static void main(String[] args) {
var argument1 = new Argument("foo.txt");
var argument2 = new Argument("bar.png");
System.out.println(argument1); // Argument{ text:'foo.txt' }
System.out.println(argument2); // Argument{ text:'bar.png' }
...
}
Écrire la classe Argument avec les préconditions habituelles.
-
On souhaite écrire une méthode parseCmdLine dans la classe CmdLine1 qui
renvoie pour chaque chaine de caractères l'argument correspondant, le tout dans une liste.
...
List<Argument> arguments1 = parseCmdLine("foo.txt", "bar.png");
System.out.println(arguments1); // [Argument{ text:'foo.txt' }, Argument{ text:'bar.png' }]
...
Doit-on renvoyer une liste mutable ou non mutable ?
Écrire la méthode parseCmdLine en remarquant que l'on peut passer autant de chaines
de caractères que l'on veut séparées par des virgules.
Note: en Java, la syntaxe pour passer un nombre d'arguments variable est d'utiliser les "...",
comme en C ou en JavaScript.
-
On souhaite maintenant écrire une classe Option qui va représenter les options
de la ligne de commande avec un texte (text) et la valeur de l'enum OptionInfo
correspondant (nommé info) tel que le code suivant fonctionne correctement.
Comme c'est un TP sur l'héritage, on vous demande d'utiliser l'héritage.
...
var option1 = new Option("--verbose", OptionInfo.VERBOSE);
var option2 = new Option("-v", OptionInfo.VERBOSE);
System.out.println(option1); // Option{ text: '--verbose', info: VERBOSE }
System.out.println(option2); // Option{ text: '-v', info: VERBOSE }
...
Écrire le code de la classe Option sachant que l'on veut que l'affichage
soit exactement celui demandé et que vous ne devez pas utiliser de getter sous peine
de mort lente à coup de petite cuillère.
-
On veut maintenant modifier la méthode parseCmdLine pour reconnaitre les arguments
et les options. Pour cela, nous allons introduire une méthode intermédiaire asOptionInfo
qui prend en paramètre un argument sous forme de chaine de caractère et renvoie
soit la bonne valeur de l'enum OptionInfo soit null si la chaine de caractère
ne correspond pas à une des options "-v", "--verbose", "-a" ou "--all".
...
var arguments2 = parseCmdLine("-v", "bar.png");
System.out.println(arguments2); // [Option{ text: '-v', info: VERBOSE }, Argument{ text:'bar.png' }]
...
Modifier le code de la méthode parseCmdLine.
-
En fait, au lieu de renvoyer null, on voudrait que asOptionInfo utilise
un Optional. En terme d'utilisation de l'API d'Optional, au lieu d'utiiser
isEmpty/isPresent, vous pouvez utiliser map et orElseGet
pour chainer les opérations, comme ceci:
Optional<OptionInfo> optionInfoOpt = asOptionInfo(arg);
Argument argument = optionInfoOpt.map(...).orElseGet(...);
Modifier le code de parseCmdLine en conséquence.
Note: si vous avez du mal avec le typage des appels map() et orElseGet(), c'est normal,
l'inférence ne marche pas tout le temps comme on veut, à vous d'expliquer au compilateur
ce que vous voulez.
Note2: pourquoi on utilise orElseGet() et pas orElse() ?
Remarque: ici, utiliser un Optional dans ce contexte est inutile, car la méthode asOptionInfo
n'est pas publique, et le but de Optional est de demander aux utilisateurs de la méthode de faire attention,
mais bon, c'est un TP.
-
On souhaite valider que la ligne de commande ne contient pas deux fois
les mêmes arguments/les mêmes options, pour cela, on va écrire une méthode checkCmdLine
qui prend en paramètre une liste d'arguments et lève une exception si un des arguments est dupliqué.
var arguments3 = parseCmdLine("-v", "bar.png", "--verbose");
checkCmdLine(arguments3); // java.lang.IllegalArgumentException: duplicate argument Option{ text: '--verbose', info: VERBOSE }
Pour détecter, s'il y a de la duplication d'objets en Java, l'astuce est d'utiliser un Set,
et sa méthode add, celle-ci renvoie false si essaye un objet déjà présent.
Implanter la méthode checkCmdLine
-
En fait, les méthodes equals que vous avez écrites sont fausses, car non reflexif,
c-a-d, a.equals(b) et b.equals(a) devrait renvoyer la même valeur.
var argument3 = new Argument("-v");
var option3 = new Option("-v", OptionInfo.VERBOSE);
System.out.println(argument3.equals(argument3)); // true
System.out.println(argument3.equals(option3)); // false
System.out.println(option3.equals(option3)); // true
System.out.println(option3.equals(argument3)); // false
Qu'affiche votre implantation si on exécute le code ci-dessus ? Pourquoi ?
Comment doit-on corriger votre implantation ? Faites les modifications qui s'imposent.
-
En fait, avoir des arguments identiques sur la ligne de commande n'est pas un vrai problème,
avoir des options identiques est le vrai problème, on se propose de modifier
checkCmdLine pour tester uniquement si les options sont les mêmes.
Pour cela, on doit savoir si un Argument est une Option ou pas.
Il existe deux façons d'implanter ce test, avec une méthode isOption dans
Argument et Option en utilisant le polymorphisme ou
en faisant un switch sur Argument et Option.
Ìmplanter la version utilisant le polymorphisme et vérifier que votre code fonctionne
var arguments4 = parseCmdLine("-v", "bar.png", "bar.png");
checkCmdLine(arguments4); // ok !
-
On souhaite maintenant implanter la version avec un switch sur Argument et Option.
Écrire une méthode isOption dans CmdLine1 qui prend un en paramètre
un Argument et renvoie vrai si l'argument est une Option en utilisant
un switch. Puis modifier checkCmdLine() pour cette méthode et enfin
vérifier que le comportement du code n'a pas changé.
-
Si on compare le polymporhisme et le pattern-matching,
avec le polymorphisme, si on ajoute un nouveau sous-type, on va devoir écrire une nouvelle
implantation de isOption pour ce sous-type. Avec le switch sur Argument,
il faudrait que le code ne compile pas si on ajoute un nouveau sous-type pour forcer
le programmeur à le prendre en compte ce nouveau sous-type, en Java, on utilise le
mot-clé sealed pour empêcher de nouveau sous-type.
Faire les modifications de code qui s'imposent pour empêcher de nouveaux sous-types.