:: Enseignements :: ESIPE :: E4INFO :: 2018-2019 :: Java Avancé ::
[LOGO]

TP noté de Java Avancé


Le but de ce TP noté est d'implanter une classe permettant de décrire une commande comme java ou javadoc avec ses arguments. Étant donné un objet qui contient les valeurs des arguments de la commande, elle permettra de facilement transférer ceux-ci en tant qu'option de la commande.

Vos sources Java produites pendant l'examen devront être placées sous le répertoire EXAM de votre compte ($HOME) (qui est vide dans l'environnement de TP noté). Sinon, elles ne seront pas récupérées.

Tout document papier est proscrit.
Vous pouvez consulter la javadoc à travers Eclipse (onglet Javadoc) ou en tapant jdoc dans un terminal. Sinon la doc est là: /usr/local/apps/java11/docs/api/index.html.
Les seuls documents électroniques autorisés sont les supports de cours à l'url http://igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

Exercice 1 - CmdLineModel

La classe CmdLineModel sert à décrire une ligne de commande avec sa commande et ses différentes options. À partir d'un objet décrivant les valeurs des arguments de la commande, CmdLineModel permet de créer une liste contenant les différentes valeurs de la ligne de commande.
Cette liste pourra ensuite être envoyée à un objet comme le java.lang.ProcessBuilder qui permet d'exécuter une commande en Java.
Dans un premier temps, on va supposer qu'une commande ne possède que des options sans argument (pour activer des comportements). Par exemple, la commande javadoc possède les options -quiet et -html5 qui permettent respectivement de demander de ne pas faire trop d'affichage et de générer du code HTML au format HTML 5.
On va aussi supposer qu'il existe un classe comme la classe Javadoc ci-dessous qui représente les arguments de la commande javadoc
  class Javadoc {
    private final boolean quiet;
    private final boolean html5;
    
    public Javadoc(boolean quiet, boolean html5) {
      this.quiet = quiet;
      this.html5 = html5;
    }
    
    public boolean isQuiet() {
      return quiet;
    }
    public boolean isHtml5() {
      return html5;
    }
  }   
     

Pour représenter ce type de commande, on va munir la classe CmdLineModel des 3 méthodes suivantes:
  • la méthode of qui prend en paramètre le nom de la commande et créé une instance de la classe CmdLineModel.
  • la méthode option qui prend en paramètre:
    • une fonction qui prend un objet en paramètre et renvoie un booléen;
    • une chaîne de caractères qui correspond au nom de l'option. C'est ce nom qui sera rajouté à la liste des arguments de la commande si la fonction (le 1er paramètre de option) renvoie vrai pour l'objet dont on demande la liste des options.
    La méthode option renvoie un objet de type CmdLineModel ce qui permet de chaîner les appels.
  • la méthode toList qui prend en paramètre un objet (c'est l'objet sur lequel on appellera les fonctions correspondant à chaque option) et renvoie une nouvelle liste contenant le nom de la commande suivie des noms des différentes options si elles ont été activées.

La classe CmdLineModel est paramétrée par le type de l'objet qui contient les arguments des différentes options.
Par exemple avec le code suivant
  var model = CmdLineModel.<Javadoc>of("javadoc")
      .option(Javadoc::isQuiet, "-quiet")
      .option(Javadoc::isHtml5, "-html5");
    
  var javadoc = new Javadoc(/*quiet*/true, /*html5*/true);
  var argumentList = model.toList(javadoc);
    
la liste argumentList contient les valeurs ["javadoc", "-quiet", "-html5"].
Le nom de la commande passée en paramètre de la méthode of est toujours le premier argument de la liste. L'ordre des options dans argumentList est le même que l'ordre des appels à la méthode option.
Si l'objet Javadoc n'active pas une des options (si la valeur est false), alors le nom de l'option correspondante n’apparaîtra pas dans la liste.
Par exemple, toujours avec le même objet model, si on exécute
  var argumentList2 = model.toList(new Javadoc(/*quiet*/false, /*html5*/true));
     
alors argumentList2 contient uniquement les valeurs ["javadoc", "-html5"].
Note: le fait d'avoir séparé l'objet qui contient les arguments (Javadoc) de l'objet qui modélise une commande (CmdLineModel) permet de réutiliser un même modèle avec plusieurs valeurs d'arguments différentes comme le montre l'exemple ci-dessus.
Note 2: nous verrons dans la suite du sujet comment gérer les options qui ne sont pas représentables par des variables booléennes.
Des tests unitaires correspondant à l'implantation sont ici:
CmdLineModelTest.java.

  1. Implanter la classe CmdLineModel ainsi que ses 3 méthodes of, option et toList.

    Note: l'idée est que lors de l'appel à option on va stocker l'action (une lambda) à effectuer sur l'argument pris en paramètre de toList lorsque toList sera appelée.

    Note 2: si vous n'arrivez pas à utiliser une lambda et son interface fonctionnelle ici, une autre solution, plus lourde, consiste à utiliser une paire (une classe, correctement typée, que vous créez vous-même) pour stocker les arguments passés à la méthode option.

    Consigne: Copier le code que vous avez écrit dans une classe CmdLineModelQ1 avant de passer à la suite. Pour les questions suivantes, continuer à écrire le code dans la classe CmdLineModel.

  2. Désormais, on veut pouvoir également modéliser des options qui peuvent prendre des paramètres (on a donc besoin de fonctions qui renvoient d'autres types de valeur que des booléens). De plus, pour représenter le fait que, pour une option, on puisse ajouter plusieurs chaînes de caractères à la liste, on introduit l'interface fonctionnelle StringProvider avec une méthode abstraite provide qui prend en paramètre une valeur (le résultat de l'appel à un getter) et renvoie un Stream de chaînes de caractères (les différentes chaînes de caractères représentant une option).
    Pour garantir que la valeur du getter est bien celle prise en paramètre, l'interface StringProvider est typée par le type de retour du getter.
    Pour prendre en compte ce nouveau type d'option, on introduit dans la classe CmdLineModel une autre méthode, également nommée option qui prend en paramètre une fonction (qui s'applique à un objet commande comme Javadoc et peut renvoyer n'importe quelle valeur) et un StringProvider qui est capable, pour une valeur donnée, de produire zéro, une ou plusieurs chaînes de caractères représentant l'option associée dans la ligne de commande.
    Enfin à l'intérieur de l'interface StringProvider, ajouter une méthode ifExists qui prend en paramètre un nom d'option et renvoie un StringProvider de booléen produisant le nom de l'option si jamais le booléen est vrai.
    Le code précédent peut maintenant s'écrire comme ci-dessous en utilisant la nouvelle méthode option qui prend en paramètre une fonction et un StringProvider
      var model = CmdLineModel.<Javadoc>of("javadoc")
          .option(Javadoc::isQuiet, StringProvider.ifExists("-quiet"))
          .option(Javadoc::isHtml5, StringProvider.ifExists("-html5"));
        
      var javadoc = new Javadoc(/*quiet*/true, /*html5*/true);
      var argumentList = model.toList(javadoc);
          

    Écrire l'interface StringProvider, les méthodes provide et ifExists et implanter la nouvelle méthode option. Faire en sorte que l'ancienne méthode option appelle la nouvelle.

    Note: la nouvelle méthode doit être écrite de telle façon que le compilateur vérifie que le type de retour de la fonction et le type du StringProvider sont reliés pour que l'on ne puisse utiliser un StringProvider pour un getter de booléen comme ifExists avec une fonction qui renverrait par exemple une String .

    Consigne: Copier le code que vous avez écrit jusque là dans une classe CmdLineModelQ2 avant de passer à la suite. Pour les questions suivantes, continuer à écrire le code dans la classe CmdLineModel.

  3. On souhaite généraliser le code pour que l'on puisse utiliser non pas uniquement une liste pour stocker les chaînes de caractères de la ligne de commande mais aussi un StringBuilder ou un StringJoiner, ou n'importe quel autre structure de données, pourvu que l'on sache ajouter une chaîne de caractères à celle-ci.
    Pour garder la compatibilité avec les codes déjà existants, on ne va pas supprimer la méthode toList mais ajouter une méthode supplémentaire collect qui prend en paramètre l'objet sur lequel on appelle les getters, une fonction permettant de créer une structure de données (un Supplier), une fonction prenant en paramètre la structure de données précédemment créée et une chaîne de caractères indiquant comment on fait pour ajouter une chaîne de caractères à la structure de données (un Consumer à deux paramètres). Enfin, la méthode collect renvoie la structure de données créée par le Supplier.
    Voici un exemple d'utilisation avec un StringJoiner comme structure de données:
      var model = CmdLineModel.<Javadoc>of("javadoc")
          .option(Javadoc::isQuiet, StringProvider.ifExists("-quiet"))
          .option(Javadoc::isHtml5, StringProvider.ifExists("-html5"));
        
      var javadoc = new Javadoc(/*quiet*/true, /*html5*/true);
      var joiner = model.collect(javadoc, () -> new StringJoiner(), (StringJoiner joiner, String text) -> joiner.add(text));      
          
    Si on appelle toString sur le joiner, on obtient "javadoc -quiet -html5".
    Écrire la méthode collect et ré-écrivez la méthode toList pour quelle utilise la méthode collect.

    Note: si vous n'y arrivez vraiment pas, vous pouvez passer à la question suivante, les tests pour les questions suivantes ne pré-supposent pas que la méthode collect a été implantée correctement.

    Consigne: Copier le code que vous avez écrit jusque là dans une classe CmdLineModelQ3 avant de passer à la suite. Pour les questions suivantes, continuer à écrire le code dans la classe CmdLineModel.

  4. Dans StringProvider, écrire une méthode ifOptionalPresent qui prend en paramètre un nom d'option et renvoie un StringProvider qui fournit l'argument contenu dans l'Optional à la suite du nom de l'option si la valeur de l'Optional est présente.
    Voici un exemple d'utilisation en présupposant qu'il existe dans la classe Javadoc une méthode version qui renvoie un OptionalString>.
      var model = CmdLineModel.<Javadoc>of("javadoc")
          .option(Javadoc::version, StringProvider.ifOptionalPresent("-version"));
        
      var javadoc = new Javadoc(/*quiet*/true, /*html5*/true).version(11);
      var argumentList = model.toList(javadoc);      
          
    Dans ce cas, argumentList contient ["javadoc", "-version", "11"].

  5. Dans StringProvider, écrire une méthode ifNotEmptyList qui prend en paramètre un nom d'option et un délimiteur (une chaîne de caractères) et renvoie un StringProvider fournit une chaîne de caractères correspondant à la liste des valeurs séparées par le délimiteur, à la suite du nom de l'option, si la liste n'est pas vide.
    Voici un exemple d'utilisation en présupposant qu'il existe dans la classe Javadoc une méthode classpath qui renvoie une ListString>.
      var model = CmdLineModel.<Javadoc>of("javadoc")
          .option(Javadoc::classpath, StringProvider.ifNotEmptyList("-classpath", ":"));
        
      var javadoc = new Javadoc(/*quiet*/true, /*html5*/true, /*classpath*/List.of("foo", "bar"));
      var argumentList = model.toList(javadoc);      
          
    Dans ce cas, argumentList contient ["javadoc", "-classpath", "foo:bar"].

  6. La méthode collect a un fonctionnement assez proche du fonctionnement d'un java.util.stream.Collector. Ajouter une autre méthode collect qui prend en paramètre un Object (sur lequel on appellera les getters) ainsi qu'un Collector et faire en sorte que l'on puisse appeler cette méthode avec l'ensemble des collectors définis dans la classe java.util.stream.Collectors (toList, joining, groupingBy, etc).

    Note: on ne vous demande pas de ré-écrire l'ancienne méthode collect avec la nouvelle.

  7. Bonus: on peut voir CmLineModel comme une interface fonctionnelle en abstrayant le fonctionnement des méthode of, option et collect.
    Ré-écrire CmLineModel sous forme d'interface fonctionnelle.

    Consigne: Copier le code que vous avez écrit jusque là dans une classe CmdLineModelQ6 avant de faire cette question.