Javaでのコマンドライン引数の処理:名探偵コナン

コマンドラインから起動する多くのJavaアプリケーションは、引数を使用して動作を制御します。これらの引数は、アプリケーションの静的main()メソッドに渡される文字列配列引数で使用できます。通常、引数には、オプション(またはスイッチ)と実際のデータ引数の2種類があります。Javaアプリケーションは、これらの引数を処理し、2つの基本的なタスクを実行する必要があります。

  1. 使用されている構文が有効でサポートされているかどうかを確認してください
  2. アプリケーションが操作を実行するために必要な実際のデータを取得します

多くの場合、これらのタスクを実行するコードはアプリケーションごとにカスタムメイドであるため、特に要件が1つまたは2つのオプションしかない単純なケースを超える場合は、作成と保守の両方にかなりの労力が必要になります。Optionsクラスは、この記事の実装に簡単に最も複雑な状況に対処するための一般的なアプローチを説明しました。このクラスは、必要なオプションとデータ引数の簡単な定義を可能にし、徹底的な構文チェックとこれらのチェックの結果への簡単なアクセスを提供します。このプロジェクトでは、ジェネリックスやタイプセーフ列挙型などの新しいJava5機能も使用されました。

コマンドライン引数の種類

何年にもわたって、コマンドライン引数を使用して動作を制御するJavaツールをいくつか作成してきました。早い段階で、さまざまなオプションを処理するためのコードを手動で作成して維持するのは面倒でした。これにより、このタスクを容易にするプロトタイプクラスが開発されましたが、綿密な調査の結果、コマンドライン引数に使用できるさまざまな種類の数が多いことが判明したため、そのクラスには確かに制限がありました。最終的に、私はこの問題の一般的な解決策を開発することにしました。

このソリューションを開発するにあたり、私は2つの主要な問題を解決する必要がありました。

  1. コマンドラインオプションが発生する可能性のあるすべての種類を特定する
  2. まだ開発されていないクラスを使用するときに、ユーザーがこれらの種類を表現できるようにする簡単な方法を見つけてください

問題1の分析により、次のことが観察されました。

  • コマンドラインデータ引数とは反対のコマンドラインオプション—それらを一意に識別するプレフィックスで始まります。プレフィックスの例には、-Unixプラットフォームのダッシュ()-a/Windowsプラットフォームのスラッシュ()などがあります。
  • オプションは、単純なスイッチ(つまり、-a存在するかどうか)にすることも、値を取ることもできます。例は次のとおりです。

    java MyTool -a -b logfile.inp 
  • 値を取るオプションは、実際のオプションキーと値の間に異なる区切り文字を持つことができます。このような区切り文字は、空白スペース、コロン(:)、または等号()にすることができます=

    java MyTool -a -b logfile.inp java MyTool -a -b:logfile.inp java MyTool -a -b = logfile.inp 
  • 値を取るオプションは、もう1つのレベルの複雑さを追加できます。例として、Javaが環境プロパティの定義をサポートする方法を考えてみましょう。

    java -Djava.library.path = / usr / lib..。 
  • したがって、実際のオプションキー(D)、区切り文字(=)、およびオプションの実際の値(/usr/lib)以外に、追加のパラメーター(java.library.path)は任意の数の値を取ることができます(上記の例では、この構文を使用して多数の環境プロパティを指定できます) )。この記事では、このパラメーターを「詳細」と呼びます。
  • オプションには多重度プロパティもあります。オプションは必須またはオプションであり、許可される回数も異なります(1回だけ、1回以上、またはその他の可能性など)。
  • データ引数はすべて、プレフィックスで始まらないコマンドライン引数です。ここで、そのようなデータ引数の許容可能な数は、最小数と最大数の間で変化する可能性があります(これらは必ずしも同じではありません)。さらに、通常、アプリケーションではこれらのデータ引数がコマンドラインの最後にある必要がありますが、必ずしもそうである必要はありません。例えば:

    java MyTool -a -b = logfile.inp data1 data2 data3 //最後のすべてのデータ 

    または

    java MyTool -a data1 data2 -b = logfile.inp data3 //アプリケーションに受け入れられる可能性があります 
  • より複雑なアプリケーションは、複数のオプションセットをサポートできます。

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -check -verify logfile.out 
  • 最後に、アプリケーションは不明なオプションを無視することを選択したり、そのようなオプションをエラーと見なしたりする場合があります。

そこで、ユーザーがこれらすべての種類を表現できるようにする方法を考案する際に、この記事の基礎として使用される次の一般的なオプションフォームを思いつきました。

[[]] 

この形式は、上記のように多重度プロパティと組み合わせる必要があります。

上記のオプションの一般的な形式の制約内で、Optionsこの記事で説明するクラスは、Javaアプリケーションが持つ可能性のあるコマンドライン処理のニーズに対する一般的なソリューションとなるように設計されています。

ヘルパークラス

Optionsこの資料に記載されているソリューションのコアクラスであるクラスは、2つのヘルパークラスが付属しています:

  1. OptionData:このクラスは、1つの特定のオプションのすべての情報を保持します
  2. OptionSet:このクラスは一連のオプションを保持します。Optionsそれ自体は、そのようなセットをいくつでも保持できます

Before describing the details of these classes, other important concepts of the Options class must be introduced.

Typesafe enums

The prefix, the separator, and the multiplicity property have been captured by enums, a feature provided for the first time by Java 5:

public enum Prefix { DASH('-'), SLASH('/'); private char c; private Prefix(char c) { this.c = c; } char getName() { return c; } } public enum Separator { COLON(':'), EQUALS('='), BLANK(' '), NONE('D'); private char c; private Separator(char c) { this.c = c; } char getName() { return c; } } public enum Multiplicity { ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; } 

Using enums has some advantages: increased type safety and tight, effortless control over the set of permissible values. Enums can also conveniently be used with genericized collections.

Note that the Prefix and Separator enums have their own constructors, allowing for the definition of an actual character representing this enum instance (versus the name used to refer to the particular enum instance). These characters can be retrieved using these enums' getName() methods, and the characters are used for the java.util.regex package's pattern syntax. This package is used to perform some of the syntax checks in the Options class, details of which will follow.

The Multiplicity enum currently supports four different values:

  1. ONCE: The option has to occur exactly once
  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 は、各オプションセットに渡されるサポートされているデータ引数のデフォルトの最大数ですが、もちろん、セットを追加するときにオーバーライドできます。 0