:: Enseignements :: ESIPE :: E3INFO :: 2021-2022 :: Programmation Objet avec Java ::
[LOGO]

Héritage, appel de constructeurs, visibilité


Exercice 1 - Découpage de la ligne de commande

On souhaite écrite un petit programme qui découpe les arguments de la ligne de commande, en séparent les arguments classiques ("foo.txt", "bar.png"), des options qui généralement commence 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 correspond à la valeur d'enum OptionInfo.VERBOSE et les deux dernières à la valeur d'enum Option.ALL. Avec l'enum OptionInfo défini comme ceci
     package fr.uge.cmdline1;

     public enum OptionInfo {
       ALL, VERBOSE
     }
    

Toutes les classes sont a 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ù en va !

  1. On souhaite écrire une classe Argument avec un champ text de tel 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' }
           ...
         }
        
    Ecrire la classe Argument avec les préconditions habituelles.

  2. On souhaite écrire une méthode parseCmdLine dans la classe CmdLine1 qui renvoie pour chaque chaine de caractère 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' }]
           ...
         
    Ecrire 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'argument varible est d'utiliser les "...", comme en C ou en JavaScript.

  3. 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 }
          ...
        
    Ecrire 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.

  4. 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.

  5. 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ération, 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 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.

  6. On souhaite valider que la ligne de commande ne contient pas deux fois les même 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 si il y a de la duplication d'objet 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

  7. En fait, les méthodes equals que vous avez écritent 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 execute le code suivant ? Pourquoi ? Comment doit-on corriger votre implantation ? Faite les modifications qui s'imposent.

  8. En fait, avoir des arguments identiques sur la ligne de commande 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çon 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 !
        

  9. On souhaite maintenant implanter la version avec un switch sur Argument et Option. Ce switch est en preview dans la version Java 19 que vous utilisez, pour cela, si ce n' est pas déjà le cas, aller dans les options du compilateur et activer les preview features.
    Ecrire 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é.

  10. 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'impose pour empécher de nouveau sous-type.

Exercice 2 - Découpage de la ligne de commande 2 (le retour de la vengeance)

En fait, utiliser l'héritage est souvent plus compliqué (faire attention au equals()), à la visibilité des champs et donne un code moins maintenable (si on utilise Argument dans le code, il est pas clair si on parle d'un argument l'implantation ou d'un argument le type qui représente Argument | Option.
Utiliser une interface résoud ces problèmes.
On se propose de ré-implanter l'exercice précédent en utilisant une interface et des records. On utilisera Argument comme no pour l'interface et Plain pour les arguments pas spéciaux et Option pour les arguments qui sont des options.

  1. Créer un package fr.uge.cmdline2 ainsi qu'une classe CmdLine2 avec le main ci dessous (Note: pour dupliquer un package vous pouvez faire un Control+C puis un Control+V dans la vue en projet).
    Faire en sorte que le main ci-dessous fonctionne.
    Note: attention à ce que deux options soient indentiques si elles ont le même OptionIndo !
      public static void main(String[] args) {
        // 1
        var argument1 = new Plain("foo.txt");
        var argument2 = new Plain("bar.png");
        System.out.println(argument1);  // Argument{ text:'foo.txt' }
        System.out.println(argument2);  // Argument{ text:'bar.png' }
    
        // 2
        var arguments1 = parseCmdLine("foo.txt", "bar.png");
        System.out.println(arguments1);  // [Argument{ text:'foo.txt' }, Argument{ text:'bar.png' }]
    
        // 3
        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 }
    
        // 4 & 5
        var arguments2 = parseCmdLine("-v", "bar.png");
        System.out.println(arguments2);  // [Option{ text: '-v', info: VERBOSE }, Argument{ text:'bar.png' }]
    
        // 6
        var arguments3 = parseCmdLine("-v", "bar.png", "--verbose");
        //checkCmdLine(arguments3);  // java.lang.IllegalArgumentException: duplicate argument Option{ text: '--verbose', info: VERBOSE }
    
        // 7
        var argument3 = new Plain("-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
    
        // 8, 9 & 10
        var arguments4 = parseCmdLine("-v", "bar.png", "bar.png");
        checkCmdLine(arguments4);  // ok !
      }