Java Getriebe

Java und NetBeans

Kommandozeile auswerten mit enums

Auch wenn die grundlegende Verwendung der beiden switch Anweisungen für die Auswertung der Kommandozeile nicht schlecht ist, gibt es noch eine Stufe mehr. Mit enums kann man sich sogar noch die Konstanten sparen.

Oder besser gesagt: Man spart sich die Stellen, die für die Definition von neuen Optionen in der Kommandozeile notwendig wären. Der Witz an der Geschichte ist, dass die Einträge des enums den Langversionen der Optionen entsprechen und auch direkt in den case-Anweisungen genutzt werden können. Außerdem lässt sich das enum auch dazu nutzen die entsprechende kurze Variante der Optionen gleich mit zu definieren.

Bevor jetzt alle aussteigen und meinen Gedanken gar nicht mehr folgen können will ich einfach mal ein bisschen Quellcode zeigen auf dem meine weitere Ausführungen basieren:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum CommandlineArgs {
  debug('d'),  lang;

  private final Character shortOption;

  private CommandlineArgs() {
    this(null);
  }

  private CommandlineArgs(Character c) {
    this.shortOption = c;
  }

  public Character getShortOption() {
    return shortOption;
  }
}

Wie zu sehen ist, steht in dem enum mehr als nur die Auflistung selber steht und die Einträge auch noch gegen alle Konventionen klein geschrieben sind. Das „Mehr“ innerhalb des Konstrukts besteht lediglich aus der inneren Variable shortOption und zwei Konstruktoren, die diese Variable mit einem Wert zu besetzen. In dem Beispiel sind hier die Optionen „debug“ und „lang“ angegeben. Erstere hat eine Kurzversion in Form des Buchstaben ‚d‚, die Option „lang“ gibt es nur ein der Langform.

Um später neue Optionen zu unserer Anwendung hinzuzufügen wird einfach nur der Name dieser Option in dem enum eingetragen und ein passender case Abschnitt mit dem, was mit dieser Option passieren soll ((dazu später mehr)). Man muss sich vor allem nicht mehr darum kümmern den „Statuskonstanten“ einen eindeutigen Wert zuzuweisen. Bei einer handvoll Optionen mag dies ja noch kein Problem sein, aber in Tools mit mehr als einem Dutzend Schaltern in der Kommandozeile wird es langsam hakelig.

Aber kommen wir zur Methode parseCommandline(). Die Schleife über alle Argumente bleibt zur bisherigen Version unverändert. Auch mit enums müssen wir alle Elemente testen. Allerdings ändern sich die switch Anweisungen. Wir wollen die Einträge aus dem enum nutzen statt der Konstanten. Also müssen wir die Strings der Kommandozeile in ein solches Objekt umwandeln. Die Variable status wird ersetzt durch eine Variable CommandlineArgs lastOption:

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
  public boolean parseCommandline(String[] args) {
    CommandlineArgs lastOption = null;
    for (String arg : args) {
      if (arg.charAt(0) == '-') {
        if (lastOption != null) {
          System.err.println(new IllegalArgumentException(
              "missing argument before " + arg));
        }
        CommandlineArgs option = null;
        if (arg.charAt(1) == '-') {
          try {
            // Langversion
            option = CommandlineArgs.valueOf(arg.substring(2));
          } catch (IllegalArgumentException e) {
            System.err.println(e.toString());
            return false;
          }
        } else {
          // Kurzversion
          char optionChar = arg.charAt(1);
          for (CommandlineArgs commandlineArg :
              CommandlineArgs.values()) {
            Character shortOption =
                commandlineArg.getShortOption();
            if (shortOption != null
                && shortOption == optionChar) {
              option = commandlineArg;
              break;
            }
          }
          if (option == null) {
            System.err.println(
                new IllegalArgumentException(arg));
            return false;
          }
        }

        lastOption = null;
        switch (option) {
          case debug:
            debugModus = true;
            break;
          default:
            lastOption = option;
            break;
        }
      } else {
        if (lastOption == null) {
          // Parameter ohne Option. Eventuell doch gewollt?
          System.err.println(new IllegalArgumentException(arg));
          return false;
        }
        switch (lastOption) {
          case lang:
            lang = arg;
            break;
        }
        lastOption = null;
      }
    }
    if (lastOption != null) {
      System.err.println(new IllegalArgumentException(
          "missing argument"));
      return false;
    }

    return true;
  }

Die Methode ist etwas länger geworden als die vorherige Version. Dies liegt neben ein paar Fehlerabfragen vor allem an der neuen Abfrage von Zeile 10 bis 36 in der die Umwandlung des Strings der Kommandozeile in eine CommandlineArgs Instanz erfolgt. Hier wird zunächst geprüft ob das Argument ein zweites Minuszeichen enthält, welches auf die Langversion der Option hinweist. In diesem Fall ist es recht einfach die passende Instanz zu ermitteln. Die enum Einträge entsprechen eben genau dieser Langversion und können so mittels CommandlineArgs.valueOf() ((Zeile 13)) ausgewertet werden. Für die Kurzversion der Option müssen wir alle Elemente von CommandlineArgs durchlaufen ((Zeile 21)) werden und jeweils über die Methode getShortOption() ((Zeile 23)) das passende Zeichen gesucht werden.

Auch in dieser Version finden wir die beiden switch Anweisungen wieder. In Zeile 39 werden die Optionen ausgewertet und in Zeile 53 sind die Parameter an der Reihe. Es fällt hier vielleicht auf, das CommandlineArgs.lang nur in der zweiten switch steht. Nicht aber in der ersten. Das liegt an dem default Fall der ersten switch. Statt uns einen individuellen Status zu merken wie es in der ersten Version der Methode noch war, können wir jetzt die CommandlineArgs Instanz selber als Status verwenden. Damit ist uns in der ersten switch relativ egal, welche Option gefunden wurde. Wir merken sie uns einfach. Damit haben wir erreicht, dass die bekannten Kommandozeilen Optionen tatsächlich nur an einer einzigen Stelle in unserer Methode eingetragen werden muss. Wir müssen nur noch entscheiden, ob die Option einen Parameter benötigt oder nicht.

One Response to Kommandozeile auswerten mit enums