IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

Conception d'une CLI (command line interface)


Sujet :

Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert

    Homme Profil pro
    SDE
    Inscrit en
    Août 2007
    Messages
    2 013
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : SDE

    Informations forums :
    Inscription : Août 2007
    Messages : 2 013
    Par défaut Conception d'une CLI (command line interface)
    Bonjour,

    Je souhaiterais rentre administrable une application via CLI, pour cela j'ai donc créer ma propre liste de commande avec mes propres listes d'options par commande.

    J'ai déjà créer quelque chose de fonctionnel mais je me rend compte que ma méthode a des limites et sera avec le temps très difficile a maintenir.
    Pour le moment j'ai un Thread CLI qui écoute constamment System.in
    a chaque saisie de ligne j'instancie une nouvelle class de type Cmd qui reçoit en prametre la ligne insérré. Cette classe commande découpe la chaine et instancie une nouvelle FormatedCmd qui peut être stocké dans un générics et assure que la commande soit correcte.

    Voici le code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class CLI extends Thread {
        private Scanner kbi;
     
        public CLI() {
            this.kbi = new Scanner(System.in);
        }
     
        @Override
        public void run() {
            while(this.kbi.hasNext()) {
                new Cmd(this.kbi.nextLine());
            }
        }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    public class Cmd {
     
        public Cmd(String str) {
            interpret(format(str));
        }
     
        private FormatedCmd format(String str) {
            String cmdName = null;
            Map<String, String> cmdOpts = new HashMap<String, String>();
     
            String[] cmd = str.split(" ");
            for(int i = 0; i < cmd.length; ++i) {
                if(i == 0) {
                    cmdName = cmd[i];
                } else {
                    if(Character.valueOf(cmd[i].charAt(0)).equals('-')) {
                        if(i + 1 < cmd.length && !Character.valueOf(cmd[i+1].charAt(0)).equals('-')) {
                            cmdOpts.put(cmd[i], cmd[i+1]);
                        } else {
                            cmdOpts.put(cmd[i], null);
                        }
                    }
                }
            }
            return new FormatedCmd(cmdName, cmdOpts);
        }
     
        private void interpret(FormatedCmd cmd) {
            if(cmd.getCmdName().equals("server")) {
                cmdServer(cmd.getOpts());
            } else if(cmd.getCmdName().equals("help")) {
                cmdHelp();
            } else if(cmd.getCmdName().equals("user")) {
                cmdUser(cmd.getOpts());
            } else if(cmd.getCmdName().equals("db")) {
                cmdDB(cmd.getOpts());
            }
        }
     
        private void cmdDB(Map<String, String> opts) {
            if(opts.containsKey("-save")) {
                DB.getInstance().save();
            }
            else if(opts.containsKey("-load")) {
                DB.getInstance().load();
            }
        }
     
        private void cmdUser(Map<String, String> opts) {
            String tmp;
            if(opts.containsKey("-add")) {
                tmp = opts.get("-add");
                if (tmp == null) { System.out.println("manque les valeurs"); }
                else {
                    User newUser = new User(tmp);
                    DB.getInstance().getUsers().put(newUser.getLogin(), newUser);
                }
            }
     
            if(opts.containsKey("-list")) {
                DB.getInstance().userList();
            }
        }
     
        private void cmdServer(Map<String, String> opts) {
            String tmp;
            if(opts.containsKey("-port")) {
                tmp = opts.get("-port");
                if (tmp == null) { System.out.println("paramètre p merdique"); }
                else { Cfg.getCfg().setPort(Integer.parseInt(tmp)); }
            }
            if(opts.containsKey("-save")) {
                Cfg.getCfg().save();
            }
            if(opts.containsKey("-show")) {
                System.out.println(Cfg.getCfg());
            }
            if(opts.containsKey("-load")) {
                Cfg.getCfg().load();
            }
            if(opts.containsKey("-start")) {
                Listen.getListen().start();
            }
            if(opts.containsKey("-stop")) {
                //mtsListen.getListen().close();
            }
        }
     
        private void cmdHelp() {
            System.out.println("<<< Help >>>");
            System.out.println("server : configuration du serveur");
            System.out.println("     -p : port");
            System.out.println("     -a : arret");
            System.out.println("     -d : demarrage");
        }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class FormatedCmd {
        private String cmdName;
        private Map<String, String> opts;
     
        public FormatedCmd(String cmdName, Map<String, String> opts) {
            this.cmdName = cmdName;
            this.opts = opts;
        }
     
        public String getCmdName() {
            return cmdName;
        }
     
        public void setCmdName(String cmdName) {
            this.cmdName = cmdName;
        }
     
        public Map<String, String> getOpts() {
            return opts;
        }
     
        public void setOpts(Map<String, String> opts) {
            this.opts = opts;
        }
     
     
    }
    Cette conception me limite déjà dans mes possibilités (par exemple soulever une exception si la commande n'existe pas, si je veux faire cela je suis obligé de gérer une liste de commande autorisé, et duplique donc mes sources de données.

    Il n'est pas trop tard pour revoir entièrement la conception, aussi je fais appel a vous pour m'aiguiller.

    Merci par avance.

  2. #2
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 18
    Par défaut
    Bonjour,

    Je pense que l'utilisation d'un Enum peut être la solution à votre problème. J'ai réarchitecturé le tout et ça donne ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    public class CLI extends Thread {
      private Scanner kbi;
     
      public CLI() {
        this.kbi = new Scanner(System.in);
      }
     
      @Override
      public void run() {
        while (this.kbi.hasNext()) {
          CmdFactory.parseCmd(this.kbi.nextLine()).execute();
        }
      }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    public interface Cmd {
      public void setOptions(Map<String, String> opts);
     
      public void execute();
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    public class CmdFactory {
      public final static Cmd parseCmd(String str) throws IllegalArgumentException {
        ValidCommand command = null;
        Map<String, String> cmdOpts = new HashMap<String, String>();
     
        String[] commandTokens = str.split(" ");
        for(int i = 0; i < commandTokens.length; ++i) {
          // Ignore empty String caused by split
          if (commandTokens[i].trim().length() == 0) {
            continue;
          }
          if(command == null) {
            command = ValidCommand.valueOf(commandTokens[i]);
          } else {
            if(commandTokens[i].charAt(0) == '-') {
              if(i + 1 < commandTokens.length && commandTokens[i+1].charAt(0) != '-') {
                cmdOpts.put(commandTokens[i], commandTokens[i+1]);
              } else {
                cmdOpts.put(commandTokens[i], null);
              }
            }
          }
        }
        return ValidCommand.getCmd(command, cmdOpts);
      }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     
    public enum ValidCommand {
      server,
      help,
      user,
      db;
     
      public static Cmd getCmd(ValidCommand command, Map<String, String> opts) {
        Cmd cmd;
        switch(command)
        {
          case server:
            cmd = new ServerCmd();
            break;
          case help:
            cmd = new HelpCmd();
            break;
          case user:
            cmd = new UserCmd();
            break;
          case db:
            cmd = new DbCmd();
            break;				
        }
        cmd.setOptions(opts);
     
        return cmd;
      }
    }
    Je vous laisse faire l'implémentation des différentes classes *Cmd (qui doivent implémenter l'interface Cmd).

    Dans le cas où l'utilisateur entre une commande qui n'existe pas, une IllegalArgumentException sera levée par le valueOf.

    Par la suite, pour rajouter des commandes, il suffira d'ajouter l'entrée dans l'enum et le case correspondant pour créer l'implémentation adaptée.

    Je me suis également permis de faire deux modifications à votre méthode qui analyse la ligne de commande :
    * Pour comparer des caractères, il n'est pas utile de créer des object Character puis d'utiliser la méthode equals. Attention : Ceci est vrai pour les caractères, pas pour les String.
    * trim() peut retourner des chaines vides si, par exemple, vous mettez deux caractères espace dans votre ligne de commande, il faut donc bien vérifier que la String n'est pas vide.

    Bon courage et n'hésitez pas pour des questions.

  3. #3
    Expert confirmé
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Par défaut
    Bonjour,

    Sauf qu'au lieu de passer par un switch case vraiment peu souple, il vaut mieux passer par quelque chose de plus dynamique, genre une collection de commandes tout en modifiant l'interface commande de la façon suivante:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    public interface Cmd {
      public void setOptions(Map<String, String> opts);
      public String getCmdName();    
      public String getCmdDescription();    
      public String getCmdHelp();    
      public List<String> getCmdPametersName();
      public int getCmdPametersNumber();
      public void execute();
    }
    Avec la méthode getCmdName retournant le nom de la commande.
    Puis tu enregistre tes commandes dans un singleton au démarrage de ton application (tu peux par exemple lister tes classes de commandes dans un fichier de conf, ou encore balayer le classpath ou certains packages pour trouver les classes implémentant l'interface commande, puis le charger par réflexion dans les deux cas). Puis à l'appel d'une commande tu parcoure ta liste de commande contenue dans ton singleton et tu la vérifie par rapport au nom de commande ainsi que la validité des params.

  4. #4
    Membre Expert

    Homme Profil pro
    SDE
    Inscrit en
    Août 2007
    Messages
    2 013
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : SDE

    Informations forums :
    Inscription : Août 2007
    Messages : 2 013
    Par défaut
    Un grand merci à vous, ça m'as permis de résoudre mes problèmes

    J'ai appliqué la structure de feuxeu77, et avec avec l'aide d'un pote a moi j'ai trouvé la solution au switch quelque peu figé de cette façon.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public enum ValidCmd {
          SERVER(ServerCmd.class),
          HELP(HelpCmd.class),
          USER(UserCmd.class),
          DB(DBCmd.class);
     
          private final Class<? extends Cmd> commandClass;
     
          private ValidCmd(Class<? extends Cmd> commandClass) {
              this.commandClass = commandClass;
          }
     
        public Cmd getCmd(Map<String, String> opts) throws InstantiationException, IllegalAccessException {
            Cmd cmd = commandClass.newInstance();
            cmd.setOptions(opts);
            return cmd;
        }
    }
    Quant à au générics pour les commandes, j'y avait pensé, mais ça me parait asses lourd comme manière de faire. Après tout, peut-être que je n'imagine pas sa mise en place de la bonne façon.

    Merci beaucoup a vous, et si vous avez des idées pour améliorer toussa n'hésitez pas

Discussions similaires

  1. Visualiser les concepts d'une ontologie sur l'interface
    Par lilyou dans le forum Ontologies
    Réponses: 3
    Dernier message: 04/06/2014, 18h13
  2. [MCD] Conception d'une BD Commande
    Par Ylvin dans le forum Schéma
    Réponses: 3
    Dernier message: 08/02/2010, 17h01
  3. conception d'une interface graphique
    Par lucky31 dans le forum JBuilder
    Réponses: 11
    Dernier message: 04/06/2007, 05h48
  4. conception d'une interface
    Par lucky31 dans le forum C++Builder
    Réponses: 32
    Dernier message: 02/06/2007, 17h52
  5. Réponses: 5
    Dernier message: 05/03/2007, 15h30

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo