Verarbeiten von Befehlszeilenargumenten in Java: Fall geschlossen

Viele Java-Anwendungen, die über die Befehlszeile gestartet wurden, verwenden Argumente, um ihr Verhalten zu steuern. Diese Argumente sind im String-Array-Argument verfügbar, das an die statische main()Methode der Anwendung übergeben wird. In der Regel gibt es zwei Arten von Argumenten: Optionen (oder Schalter) und tatsächliche Datenargumente. Eine Java-Anwendung muss diese Argumente verarbeiten und zwei grundlegende Aufgaben ausführen:

  1. Überprüfen Sie, ob die verwendete Syntax gültig ist und unterstützt wird
  2. Rufen Sie die tatsächlichen Daten ab, die die Anwendung zur Ausführung ihrer Vorgänge benötigt

Häufig ist der Code, der diese Aufgaben ausführt, für jede Anwendung maßgeschneidert und erfordert daher einen erheblichen Aufwand beim Erstellen und Verwalten, insbesondere wenn die Anforderungen über einfache Fälle mit nur einer oder zwei Optionen hinausgehen. Die Optionsin diesem Artikel beschriebene Klasse implementiert einen generischen Ansatz, um die komplexesten Situationen einfach zu handhaben. Die Klasse ermöglicht eine einfache Definition der erforderlichen Optionen und Datenargumente und bietet gründliche Syntaxprüfungen und einfachen Zugriff auf die Ergebnisse dieser Prüfungen. Für dieses Projekt wurden auch neue Java 5-Funktionen wie Generika und typsichere Aufzählungen verwendet.

Befehlszeilenargumenttypen

Im Laufe der Jahre habe ich mehrere Java-Tools geschrieben, die Befehlszeilenargumente verwenden, um ihr Verhalten zu steuern. Schon früh fand ich es ärgerlich, den Code für die Verarbeitung der verschiedenen Optionen manuell zu erstellen und zu pflegen. Dies führte zur Entwicklung einer Prototypklasse, um diese Aufgabe zu erleichtern, aber diese Klasse hatte zugegebenermaßen ihre Grenzen, da sich bei genauer Betrachtung herausstellte, dass die Anzahl möglicher unterschiedlicher Varianten für Befehlszeilenargumente signifikant war. Schließlich beschloss ich, eine allgemeine Lösung für dieses Problem zu entwickeln.

Bei der Entwicklung dieser Lösung musste ich zwei Hauptprobleme lösen:

  1. Identifizieren Sie alle Varianten, in denen Befehlszeilenoptionen auftreten können
  2. Finden Sie eine einfache Möglichkeit, Benutzern das Ausdrücken dieser Sorten zu ermöglichen, wenn Sie die noch zu entwickelnde Klasse verwenden

Die Analyse von Problem 1 führte zu folgenden Beobachtungen:

  • Befehlszeilenoptionen im Gegensatz zu Argumenten für Befehlszeilendaten: Beginnen Sie mit einem Präfix, das sie eindeutig identifiziert. Präfixbeispiele umfassen einen Strich ( -) auf Unix-Plattformen für Optionen wie -aoder einen Schrägstrich ( /) auf Windows-Plattformen.
  • Optionen können entweder einfache Schalter sein (dh -avorhanden sein oder nicht) oder einen Wert annehmen. Ein Beispiel ist:

    Java MyTool -a -b logfile.inp 
  • Optionen, die einen Wert annehmen, können unterschiedliche Trennzeichen zwischen dem tatsächlichen Optionsschlüssel und dem Wert haben. Solche Trennzeichen können ein Leerzeichen, ein Doppelpunkt ( :) oder ein Gleichheitszeichen ( =) sein:

    java MyTool -a -b logfile.inp java MyTool -a -b: logfile.inp java MyTool -a -b = logfile.inp 
  • Optionen, die einen Wert annehmen, können eine weitere Komplexitätsstufe hinzufügen. Betrachten Sie als Beispiel die Art und Weise, wie Java die Definition von Umgebungseigenschaften unterstützt:

    java -Djava.library.path = / usr / lib ... 
  • Über den tatsächlichen Optionsschlüssel ( D), das Trennzeichen ( =) und den tatsächlichen Wert ( /usr/lib) der Option hinaus kann ein zusätzlicher Parameter ( java.library.path) eine beliebige Anzahl von Werten annehmen (im obigen Beispiel können mithilfe dieser Syntax zahlreiche Umgebungseigenschaften angegeben werden ). In diesem Artikel wird dieser Parameter als "Detail" bezeichnet.
  • Optionen haben auch eine Multiplizitätseigenschaft: Sie können erforderlich oder optional sein, und die Häufigkeit, mit der sie zulässig sind, kann ebenfalls variieren (z. B. genau einmal, einmal oder mehrmals oder andere Möglichkeiten).
  • Datenargumente sind alle Befehlszeilenargumente, die nicht mit einem Präfix beginnen. Hier kann die akzeptable Anzahl solcher Datenargumente zwischen einer minimalen und einer maximalen Anzahl variieren (die nicht unbedingt gleich sind). Darüber hinaus erfordert eine Anwendung normalerweise, dass diese Datenargumente zuletzt in der Befehlszeile stehen. Dies muss jedoch nicht immer der Fall sein. Zum Beispiel:

    java MyTool -a -b = logfile.inp data1 data2 data3 // Alle Daten am Ende 

    oder

    java MyTool -a data1 data2 -b = logfile.inp data3 // Kann für eine Anwendung akzeptabel sein 
  • Komplexere Anwendungen können mehrere Optionen unterstützen:

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -check -verify logfile.out 
  • Schließlich kann eine Anwendung unbekannte Optionen ignorieren oder solche Optionen als Fehler betrachten.

Bei der Entwicklung einer Möglichkeit, Benutzern das Ausdrücken all dieser Sorten zu ermöglichen, habe ich das folgende allgemeine Optionsformular entwickelt, das als Grundlage für diesen Artikel dient:

[[]] 

Diese Form muss wie oben beschrieben mit der Multiplizitätseigenschaft kombiniert werden.

Im Rahmen der oben beschriebenen allgemeinen Form einer Option ist die Optionsin diesem Artikel beschriebene Klasse die allgemeine Lösung für alle Befehlszeilenverarbeitungsanforderungen, die eine Java-Anwendung möglicherweise hat.

Die Helferklassen

Die OptionsKlasse, die die Kernklasse für die in diesem Artikel beschriebene Lösung darstellt, enthält zwei Hilfsklassen:

  1. OptionData: Diese Klasse enthält alle Informationen für eine bestimmte Option
  2. OptionSet: Diese Klasse enthält eine Reihe von Optionen. Optionsselbst kann eine beliebige Anzahl solcher Sätze enthalten

Bevor die Details dieser Klassen beschrieben werden, müssen andere wichtige Konzepte der OptionsKlasse vorgestellt werden.

Typesichere Aufzählungen

Das Präfix, das Trennzeichen und die Multiplizitätseigenschaft wurden von Aufzählungen erfasst, eine Funktion, die erstmals von Java 5 bereitgestellt wurde:

öffentliches Enum-Präfix {DASH ('-'), SLASH ('/'); privates Zeichen c; privates Präfix (char c) {this.c = c; } char getName () {return c; }} public enum Separator {COLON (':'), EQUALS ('='), BLANK (''), NONE ('D'); privates Zeichen c; privater Separator (char c) {this.c = c; } char getName () {return c; }} öffentliche Aufzählung Multiplizität {ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; }}

Die Verwendung von Aufzählungen hat einige Vorteile: Erhöhte Typensicherheit und strenge, mühelose Kontrolle über den Satz zulässiger Werte. Aufzählungen können auch bequem mit generisierten Sammlungen verwendet werden.

Beachten Sie, dass die Aufzählungen Prefixund Separatorenums ihre eigenen Konstruktoren haben, die die Definition eines tatsächlichen Zeichens ermöglichen, das diese Aufzählungsinstanz darstellt (im Vergleich zu dem Namen, der verwendet wird, um auf die bestimmte Aufzählungsinstanz zu verweisen). Diese Zeichen können mit den getName()Methoden dieser Aufzählungen abgerufen werden , und die Zeichen werden für die Mustersyntax des java.util.regexPakets verwendet. Dieses Paket wird verwendet, um einige der Syntaxprüfungen in der OptionsKlasse durchzuführen , deren Details folgen werden.

Die MultiplicityAufzählung unterstützt derzeit vier verschiedene Werte:

  1. ONCE: Die Option muss genau einmal vorkommen
  2. ONCE_OR_MORE: The option has to occur at least once
  3. ZERO_OR_ONCE: The option can either be absent or present exactly once
  4. ZERO_OR_MORE: The option can either be absent or present any number of times

More definitions can easily be added should the need arise.

The OptionData class

The OptionData class is basically a data container: firstly, for the data describing the option itself, and secondly, for the actual data found on the command line for that option. This design is already reflected in the constructor:

OptionData(Options.Prefix prefix, String key, boolean detail, Options.Separator separator, boolean value, Options.Multiplicity multiplicity) 

The key is used as the unique identifier for this option. Note that these arguments directly reflect the findings described earlier: a full option description must have at least a prefix, a key, and multiplicity. Options taking a value also have a separator and might accept details. Note also that this constructor has package access, so applications cannot directly use it. Class OptionSet's addOption() method adds the options. This design principle has the advantage that we have much better control on the actual possible combinations of arguments used to create OptionData instances. For example, if this constructor were public, you could create an instance with detail set to true and value set to false, which is of course nonsense. Rather than having elaborate checks in the constructor itself, I decided to provide a controlled set of addOption() methods.

The constructor also creates an instance of java.util.regex.Pattern, which is used for this option's pattern-matching process. One example would be the pattern for an option taking a value, no details, and a nonblank separator:

pattern = java.util.regex.Pattern.compile(prefix.getName() + key + separator.getName() + "(.+)$"); 

The OptionData class, as already mentioned, also holds the results of the checks performed by the Options class. It provides the following public methods to access these results:

int getResultCount() String getResultValue(int index) String getResultDetail(int index) 

The first method, getResultCount(), returns the number of times an option was found. This method design directly ties in with the multiplicity defined for the option. For options taking a value, this value can be retrieved using the getResultValue(int index) method, where the index can range between 0 and getResultCount() - 1. For value options that also accept details, these can be similarly accessed using the getResultDetail(int index) method.

The OptionSet class

The OptionSet class is basically a container for a set of OptionData instances and also the data arguments found on the command line.

The constructor has the form:

OptionSet(Options.Prefix prefix, Options.Multiplicity defaultMultiplicity, String setName, int minData, int maxData) 

Again, this constructor has package access. Option sets can only be created through the Options class's different addSet() methods. The default multiplicity for the options specified here can be overridden when adding an option to the set. The set name specified here is a unique identifier used to refer to the set. minData and maxData are the minimum and maximum number of acceptable data arguments for this set.

The public API for OptionSet contains the following methods:

General access methods:

String getSetName() int getMinData() int getMaxData() 

Methods to add options:

OptionSet addOption(String key) OptionSet addOption(String key, Multiplicity multiplicity) OptionSet addOption(String key, Separator separator) OptionSet addOption(String key, Separator separator, Multiplicity multiplicity) OptionSet addOption(String key, boolean details, Separator separator) OptionSet addOption(String key, boolean details, Separator separator, Multiplicity multiplicity) 

Methods to access check result data:

java.util.ArrayList getOptionData() OptionData getOption(String key) boolean isSet(String key) java.util.ArrayList getData() java.util.ArrayList getUnmatched() 

Note that the methods for adding options that take a Separator argument create an OptionData instance accepting a value. The addOption() methods return the set instance itself, which allows invocation chaining:

Options options = new Options(args); options.addSet("MySet").addOption("a").addOption("b"); 

After the checks have been performed, their results are available through the remaining methods. getOptionData() returns a list of all OptionData instances, while getOption() allows direct access to a specific option. isSet(String key) is a convenience method that checks whether an options was found at least once on the command line. getData() provides access to the data arguments found, while getUnmatched() lists all options found on the command line for which no matching OptionData instances were found.

The Options class

Options is the core class with which applications will interact. It provides several constructors, all of which take the command line argument string array that the main() method provides as the first argument:

Options(String args[]) Options(String args[], int data) Options(String args[], int defMinData, int defMaxData) Options(String args[], Multiplicity defaultMultiplicity) Options(String args[], Multiplicity defaultMultiplicity, int data) Options(String args[], Multiplicity defaultMultiplicity, int defMinData, int defMaxData) Options(String args[], Prefix prefix) Options(String args[], Prefix prefix, int data) Options(String args[], Prefix prefix, int defMinData, int defMaxData) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity, int data) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity, int defMinData, int defMaxData) 

The first constructor in this list is the simplest one using all the default values, while the last one is the most generic.

Table 1: Arguments for the Options() constructors and their meaning

Value Description Default
prefix This constructor argument is the only place where a prefix can be specified. This value is passed on to any option set and any option created subsequently. The idea behind this approach is that within a given application, it proves unlikely that different prefixes will need to be used. Prefix.DASH
defaultMultiplicity This default multiplicity is passed to each option set and used as the default for options added to a set without specifying a multiplicity. Of course, this multiplicity can be overridden for each option added. Multiplicity.ONCE
defMinData defMinData is the default minimum number of supported data arguments passed to each option set, but it can of course be overridden when adding a set. 0
defMaxData defMaxData ist die standardmäßige maximale Anzahl unterstützter Datenargumente, die an jeden Optionssatz übergeben werden. Sie kann jedoch natürlich beim Hinzufügen eines Satzes überschrieben werden. 0