Input/Output Befehl aus Benutzereingabe herausfiltern

Diskutiere Befehl aus Benutzereingabe herausfiltern im Java Basics - Anfänger-Themen Bereich.
D

DagobertDuck

Guten Abend,

ich versuche mir seit Tagen einen ordentlichen Command-Handler zu basteln. Es scheitert leider immer daran, dass ich zwischen Befehlen unterscheiden muss, die ein, zwei oder drei Wörter lang sind.

Hier ist mein derzeitiger Code bei dem ich nicht weiter komme:
Java:
        register = new Register();
        CommandParser parser = new CommandParser(this);
        while (running) {
            String input = Terminal.readLine();
            try {
                final List<Command> commands = parser.initialiseCommands(register);
                final List<String> inputSplit = Arrays.asList(input.split(" "));
                final Command command = commands.stream()
                        .filter(cmd -> cmd.getName().equals(???)
                        .findAny()
                        .orElseThrow(() -> new InvalidInputException("no such command");
                final List<String> arguments = parser.getArguments(inputSplit, command);
                command.setArguments(arguments);
                command.execute();
            } catch (InvalidInputException | BadSyntaxException e) {
                Terminal.printError(e.getMessage());
            }
        }

und hier ist meine getArguments() Methode:

Java:
    public List<String> getArguments(final List<String> inputSplit, final Command command) throws BadSyntaxException {
        int commandCount = command.getName().split(" ").length;
        final List<String> arguments = inputSplit.subList(commandCount, inputSplit.size());
        if (arguments.size() != command.getNumberOfArguments()) {
            throw new BadSyntaxException("invalid number of arguments. Expected %d arguments",
                    command.getNumberOfArguments());
        }
        return arguments;
    }
initialiseCommands() enthält alle registrierten Befehle (jeder Befehl ist in einer eigenen Klasse). Des Weiteren verfügt jeder Befehl über die Methode getName(), die eine Zeichenkette des Befehlsnamens zurückgibt (z. B. für den Befehl add track <point> -> <point> ist es "add track").

Hier sind ein paar Befehle mit unterschiedlicher Wortlänge des Befehlsnamens:
- add track <point> -> <point> - Länge 2
- exit - Länge 1
- delete rolling stock <id> - Länge 3

Wie kann ich jetzt gescheit die Eingaben von Benutzer parsen und den potenziell richtig eingegeben Befehl rausfiltern?

Beispiel:
add track (1,2) -> (3,2) -> Command command = AddTrackCommand().
Sobald ich den Befehl habe, bekomme ich die Argumente automatisch mit meiner getArguments() Methode. Es geht also wirklich nur darum, den Befehl zu filtern, den der Benutzer eingibt.

Falls irgendetwas unklar sein sollte, fragt bitte nach. Ich mache mir seit Tagen Gedanken um dieses scheinbar unlösbare Problem...
 
D

DagobertDuck

Seitdem habe ich so viele andere Alternativen versucht, dass ich das schon alles gar nicht mehr im Kopf habe.

Ein Problem war aber, dass z. B. auch "exit 42" angenommen wird, obwohl nur "exit" angenommen werden sollte. "exit 42" -> "unknown command".
 
D

DagobertDuck

@mihe7 Eventuell ist das ja sogar gar nicht so schwierig das zu beheben? Dann würde ich es noch mal mit deiner Variante versuchen und falls weitere Probleme aufkommen sollten, melde ich mich erneut.
 
D

DagobertDuck

Außerdem verstehe ich deine Klassenaufteilung auch noch nicht so ganz.

Ich würde gerne eine Klasse CommandParser, in der die Befehle registriert werden und eine Main Klasse, in der die Befehle durch die Eingabe ausgeführt werden, haben.

Egal wie ich es versuche, ich schaffe es nicht deine Implementierung darauf anzupassen. Wenn ich das nicht tue, passt das alles mit meinem Register und der Session nicht.
 
D

DagobertDuck

OK. Ich habe es jetzt letztendlich doch noch so hinbekommen wie ich es wollte.
 
M

Mika34

Auf welche Weise hast du es gelöst, weil bei mir scheitert es momentan auch noch gewaltig an dieser Stelle
 
D

DagobertDuck

@Mika34 Ich habe es letztendlich auf eine ganz andere Weise gelöst. Woran scheitert es denn genau?
 
M

Mika34

Ich habe momentan für jede valide Eingabe einen Regex, welcher gecheckt wird, ob die Eingabe diesem entspricht.
Jedoch bin ich mir nicht sicher, ob dies nicht unter Hard-Codierung fällt und und zum anderen ist dies doch bestimmt schlechter Stil, wenn man für jede potentielle Eingabe einen regulären Ausdruck definiert, oder nicht?
Denn, wenn eine Eingabe nicht in das Muster fällt, soll das Programm beendet werden, aber wenn es passt, soll es einfach weiterlaufen...
Was meint ihr dazu
 
mihe7

mihe7

Ich verstehe Euer Problem nicht: eine Befehlszeile besteht einfach aus mehreren Zeichenketten, nennen wir sie mal Tokens, die durch Whitespace voneinander getrennt sind: <token> <token> <token>...

Die Liste der Tokens erhält man also über:
Java:
List<String> getTokens(String commandLine) {
    return Arrays.asList(commandLine.split("\\s"));
}
Jetzt ist die Frage, wie man die Tokens interpretiert. Da man zwischen verschiedenen Befehlen unterscheiden können muss, gibt das erste Token den Befehl (bzw. den ersten Teil eines Befehls) an. D. h. um einen Befehl zu bestimmen, hat man einen Entscheidungsbaum der Form:
Code:
          Start
            |
  +---------+----------+
  |         |          |
  v         v          v
 exit      step      delete
                       |
                       +--------+
                       |        |
                       v        v
                     rolling   track
                       |         
                       v         
                     stock
Das kann man nun auf unterschiedliche Weise implementieren. Eine Möglichkeit besteht darin, den Baum einfach als Objektgraph umzusetzen. Dazu betrachtet man einen Command als Befehl, der zur Ausführung eine Tokenliste erhält (s. https://www.java-forum.org/thema/implementierung-eines-commandhandlers-parsers-fuer-viele-eingaben.187285/#post-1211057)

Der erste Befehl (dessen Name uninteressant ist) erhält also alle Tokens und wählt nun anhand des ersten Tokens den nächsten Befehl aus. Dieser erhält zur Ausführung nur noch den Rest der Liste als Argument.

Also:
Befehlerhält ArgumenteAktion
Start-Befehl"delete", "rolling", "stock", "<id>"wählt delete-Befehl aus und übergibt die verbleibenden Argumente
delete"rolling", "stock", "<id>"wählt rolling-Befehl aus und übergibt die verbleibenden Argumente
rolling"stock", "<id>"wählt stock-Befehl aus und übergibt die verbleibenden Argumente
stock"<id>"Führt den Befehle "delete rolling stock" aus.

Das ergibt dann Code ähnlich zu dem im genannten Link. Programmiert man noch ein Fluent Interface, dann ließe sich der Baum oben z. B. darstellen mit:

Java:
DefaultCommand root = new DefaultCommand()
    .register("exit", args -> System.exit(0))
    .register("step", new StepCommand(...))
    .register("delete", new DefaultCommand()
        .register("rolling", new DefaultCommand()
            .register("stock", new DeleteRollingStockCommand(...))
        )
    );
Die Pünktchen als Platzhalter sollen anzeigen, dass die Befehle ggf. mit weiteren Objekten zusammenarbeiten müssen, die an der Stelle übergeben werden können.

Ausgeführt wird dann eine Befehlszeile durch
Java:
root.execute(getTokens(commandLine));
 
M

Mika34

Hier wird dann aber gar nicht geprüft, ob es eine valide Eingabe war oder nicht. Das irritiert mich gerade noch sehr oder wird geprüft und ich verstehe es nur nicht
 
Thema: 

Befehl aus Benutzereingabe herausfiltern

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben