1. 概要

このチュートリアルでは、 JCommanderを使用してコマンドラインパラメーターを解析する方法を学習します。単純なコマンドラインアプリケーションを構築する際に、その機能のいくつかを探ります。

2. なぜJCommander?

「コマンドラインパラメータを解析するには寿命が短すぎるため」–CédricBeust

CédricBeustによって作成されたJCommanderは、コマンドラインパラメーターの解析用の注釈ベースのライブラリです。 コマンドラインアプリケーションを構築する手間を軽減し、優れたユーザーエクスペリエンスを提供するのに役立ちます。

JCommanderを使用すると、解析、検証、型変換などのトリッキーなタスクをオフロードして、アプリケーションロジックに集中できるようになります。

3. JCommanderのセットアップ

3.1. Maven構成

pom.xmljcommander依存関係を追加することから始めましょう。

<dependency>
    <groupId>com.beust</groupId>
    <artifactId>jcommander</artifactId>
    <version>1.78</version>
</dependency>

3.2. こんにちは世界

簡単なものを作りましょう HelloWorldApp と呼ばれる単一の入力を取ります名前挨拶を印刷し、 “こんにちは

JCommanderはコマンドライン引数をJavaクラスのフィールドにバインドするため、最初に注釈付きのフィールドnameを使用してHelloWorldArgsクラスを定義します。 ] @Parameter

class HelloWorldArgs {

    @Parameter(
      names = "--name",
      description = "User name",
      required = true
    )
    private String name;
}

次に、 JCommander クラスを使用してコマンドライン引数を解析し、HelloWorldArgsオブジェクトのフィールドを割り当てます。

HelloWorldArgs jArgs = new HelloWorldArgs();
JCommander helloCmd = JCommander.newBuilder()
  .addObject(jArgs)
  .build();
helloCmd.parse(args);
System.out.println("Hello " + jArgs.getName());

最後に、コンソールから同じ引数を使用してメインクラスを呼び出しましょう。

$ java HelloWorldApp --name JavaWorld
Hello JavaWorld

4. JCommanderで実際のアプリケーションを構築する

これで稼働が開始されたので、より複雑なユースケースを考えてみましょう。 Stripe 、特に Metered (または使用量ベース)請求シナリオ。 このサードパーティの請求サービスは、サブスクリプションと請求を管理します。

私たちがSaaSビジネスを運営していると想像してみてください。このビジネスでは、顧客がサービスのサブスクリプションを購入し、1か月あたりのサービスへのAPI呼び出しの数が請求されます。 クライアントで2つの操作を実行します。

  • submit :特定のサブスクリプションに対する顧客の使用量と単価を送信します
  • fetch :当月のサブスクリプションの一部またはすべての消費量に基づいて顧客の料金を取得します—これらの料金は、すべてのサブスクリプションにわたって集計されるか、サブスクリプションごとに項目化されます。

ライブラリの機能を確認しながら、APIクライアントを構築します。

さぁ、始めよう!

5. パラメータの定義

アプリケーションが使用できるパラメーターを定義することから始めましょう。

5.1. @Parameterアノテーション

フィールドに@Parameterアノテーションを付けると、一致するコマンドライン引数をフィールドにバインドするようにJCommanderに指示します @Parameter には、次のようなメインパラメーターを説明する属性があります。

  • names – オプションの1つ以上の名前(例:「–name」または「-n」)
  • description –エンドユーザーを支援するためのオプションの背後にある意味
  • 必須–オプションが必須かどうか、デフォルトは false
  • arity –オプションが消費する追加パラメーターの数

従量制課金シナリオでパラメーターcustomerIdを構成しましょう。

@Parameter(
  names = { "--customer", "-C" },
  description = "Id of the Customer who's using the services",
  arity = 1,
  required = true
)
String customerId;

次に、新しい「–customer」パラメーターを使用してコマンドを実行しましょう。

$ java App --customer cust0000001A
Read CustomerId: cust0000001A.

同様に、短い「-C」パラメーターを使用して同じ効果を得ることができます。

$ java App -C cust0000001A
Read CustomerId: cust0000001A.

5.2. 必要なパラメータ

パラメーターが必須である場合、ユーザーがパラメーターを指定しないと、アプリケーションはParameterExceptionをスローして終了します。

$ java App
Exception in thread "main" com.beust.jcommander.ParameterException:
  The following option is required: [--customer | -C]

一般に、パラメーターの解析中にエラーが発生すると、JCommanderでParameterExceptionが発生することに注意してください。

6. ビルトインタイプ

6.1. IStringConverterインターフェース

JCommanderは、コマンドラインString入力からパラメータークラスのJava型への型変換を実行します。 IStringConverterインターフェースは、パラメーターのStringから任意の型への型変換を処理します。 したがって、JCommanderのすべての組み込みコンバーターはこのインターフェースを実装します。

箱から出して、JCommanderは、 String Integer Boolean BigDecimal などの一般的なデータ型をサポートしています。 ]列挙型

6.2. シングルアリティタイプ

アリティは、オプションが消費する追加のパラメーターの数に関連しています。 JCommanderの組み込みパラメータタイプのデフォルトのアリティは1です 、 を除いてブール値リスト。 したがって、 整数 BigDecimal 長いです、列挙型 、は単一アリティタイプです。

6.3. ブールタイプ

ブール型またはブール型のフィールドには追加のパラメーターは必要ありません–これらのオプションのarityはゼロです。

例を見てみましょう。 おそらく、サブスクリプションごとに項目化された顧客の料金を取得したいと思います。 booleanフィールドitemizedを追加できます。これは、デフォルトではfalseです。

@Parameter(
  names = { "--itemized" }
)
private boolean itemized;

このアプリケーションは、itemizedfalseに設定された集計料金を返します。 itemized パラメーターを使用してコマンドラインを呼び出す場合、フィールドをtrueに設定します。

$ java App --itemized
Read flag itemized: true.

これは、特に指定がない限り、常に項目別の料金が必要なユースケースがない限り、うまく機能します。 パラメータをnotItemized、に変更することもできますが、itemizedの値としてfalseを指定できる方が明確な場合があります。

フィールドにデフォルト値trueを使用し、その arity を1つに設定して、この動作を紹介しましょう。

@Parameter(
  names = { "--itemized" },
  arity = 1
)
private boolean itemized = true;

これで、オプションを指定すると、値がfalseに設定されます。

$ java App --itemized false
Read flag itemized: false.

7. リストタイプ

JCommanderは、リストフィールドに引数をバインドするいくつかの方法を提供します。

7.1. パラメータを複数回指定する

顧客のサブスクリプションのサブセットのみの料金を取得したいとします。

@Parameter(
  names = { "--subscription", "-S" }
)
private List<String> subscriptionIds;

このフィールドは必須ではなく、パラメーターが指定されていない場合、アプリケーションはすべてのサブスクリプションにわたって料金を取得します。 ただし、パラメータ名を複数回使用することで、複数のサブスクリプションを指定できます

$ java App -S subscriptionA001 -S subscriptionA002 -S subscriptionA003
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].

7.2. スプリッターを使用したリストのバインド

オプションを複数回指定する代わりに、コンマ区切りのStringを渡してリストをバインドしてみましょう。

$ java App -S subscriptionA001,subscriptionA002,subscriptionA003
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].

これは、単一のパラメーター値(arity = 1)を使用してリストを表します。 JCommanderは、クラス CommaParameterSplitter を使用して、コンマで区切られたStringListにバインドします。

7.3. カスタムスプリッターを使用したリストのバインド

IParameterSplitter インターフェースを実装することにより、デフォルトのスプリッターをオーバーライドできます。

class ColonParameterSplitter implements IParameterSplitter {

    @Override
    public List split(String value) {
        return asList(value.split(":"));
    }
}

次に、実装を@Parametersplitter属性にマッピングします。

@Parameter(
  names = { "--subscription", "-S" },
  splitter = ColonParameterSplitter.class
)
private List<String> subscriptionIds;

試してみましょう:

$ java App -S "subscriptionA001:subscriptionA002:subscriptionA003"
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].

7.4. 可変アリティリスト

可変アリティにより、次のオプションまで、無期限のパラメーターをとることができるリストを宣言できます。 属性variableAritytrueとして設定して、この動作を指定できます。

サブスクリプションを解析するためにこれを試してみましょう:

@Parameter(
  names = { "--subscription", "-S" },
  variableArity = true
)
private List<String> subscriptionIds;

そして、コマンドを実行すると、次のようになります。

$ java App -S subscriptionA001 subscriptionA002 subscriptionA003 --itemized
Read Subscriptions: [subscriptionA001, subscriptionA002, subscriptionA003].

JCommanderは、オプション「-S」に続くすべての入力引数を、次のオプションまたはコマンドの終了までリストフィールドにバインドします。

7.5. 固定アリティリスト

これまで、無制限のリストを見てきました。ここでは、必要な数のリストアイテムを渡すことができます。 Listフィールドに渡されるアイテムの数を制限したい場合があります。 これを行うには、リストフィールドに整数のアリティ値を指定して、境界を設定します

@Parameter(
  names = { "--subscription", "-S" },
  arity = 2
)
private List<String> subscriptionIds;

固定アリティは、 List オプションに渡されたパラメーターの数を強制的にチェックし、違反の場合はParameterExceptionをスローします。

$ java App -S subscriptionA001 subscriptionA002 subscriptionA003
Was passed main parameter 'subscriptionA003' but no main parameter was defined in your arg class

エラーメッセージは、JCommanderが2つの引数のみを予期したため、次のオプションとして追加の入力パラメーター「subscriptionA003」を解析しようとしたことを示しています。

8. カスタムタイプ

カスタムコンバーターを作成してパラメーターをバインドすることもできます。 組み込みのコンバーターと同様に、カスタムコンバーターはIStringConverterインターフェイスを実装する必要があります。

ISO8601タイムスタンプを解析するためのコンバーターを書いてみましょう。

class ISO8601TimestampConverter implements IStringConverter<Instant> {

    private static final DateTimeFormatter TS_FORMATTER = 
      DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");

    @Override
    public Instant convert(String value) {
        try {
            return LocalDateTime
              .parse(value, TS_FORMATTER)
              .atOffset(ZoneOffset.UTC)
              .toInstant();
        } catch (DateTimeParseException e) {
            throw new ParameterException("Invalid timestamp");
        }
    }
}

このコードは、入力 String を解析し、 Instant を返し、変換エラーが発生した場合はParameterExceptionをスローします。 このコンバーターは、@Parameterconverter属性を使用して、タイプInstantのフィールドにバインドすることで使用できます。

@Parameter(
  names = { "--timestamp" },
  converter = ISO8601TimestampConverter.class
)
private Instant timestamp;

実際の動作を見てみましょう。

$ java App --timestamp 2019-10-03T10:58:00
Read timestamp: 2019-10-03T10:58:00Z.

9. パラメータの検証

JCommanderは、いくつかのデフォルトの検証を提供します。

  • 必要なパラメーターが提供されているかどうか
  • 指定されたパラメーターの数がフィールドのアリティと一致する場合
  • Stringパラメーターを対応するフィールドのタイプに変換できるかどうか

さらに、カスタム検証を追加したい場合があります。 たとえば、顧客IDがUUIDである必要があると仮定します。

インターフェイスIParameterValidatorを実装するcustomerフィールドのバリデーターを記述できます。

class UUIDValidator implements IParameterValidator {

    private static final String UUID_REGEX = 
      "[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}";

    @Override
    public void validate(String name, String value) throws ParameterException {
        if (!isValidUUID(value)) {
            throw new ParameterException(
              "String parameter " + value + " is not a valid UUID.");
        }
    }

    private boolean isValidUUID(String value) {
        return Pattern.compile(UUID_REGEX)
          .matcher(value)
          .matches();
    }
}

次に、パラメーターのvalidateWith属性を使用して接続できます。

@Parameter(
  names = { "--customer", "-C" },
  validateWith = UUIDValidator.class
)
private String customerId;

UUID以外の顧客IDを使用してコマンドを呼び出すと、アプリケーションは検証失敗メッセージで終了します。

$ java App --C customer001
String parameter customer001 is not a valid UUID.

10. サブコマンド

パラメータバインディングについて学習したので、すべてをまとめてコマンドを作成しましょう。

JCommanderでは、サブコマンドと呼ばれる複数のコマンドをサポートできます。各コマンドには、個別のオプションセットがあります。

10.1. @Parametersアノテーション

@Parametersを使用してサブコマンドを定義できます。  @Parameters には、コマンドを識別するための属性commandNamesが含まれています。

submitfetchをサブコマンドとしてモデル化しましょう。

@Parameters(
  commandNames = { "submit" },
  commandDescription = "Submit usage for a given customer and subscription, " +
    "accepts one usage item"
)
class SubmitUsageCommand {
    //...
}

@Parameters(
  commandNames = { "fetch" },
  commandDescription = "Fetch charges for a customer in the current month, " +
    "can be itemized or aggregated"
)
class FetchCurrentChargesCommand {
    //...
}

JCommanderは、 @Parameters の属性を使用して、次のようなサブコマンドを構成します。

  • commandNames –サブコマンドの名前。 コマンドライン引数を@Parametersで注釈が付けられたクラスにバインドします
  • commandDescription –サブコマンドの目的を文書化します

10.2. JCommanderへのサブコマンドの追加

addCommand メソッドを使用して、サブコマンドをJCommanderに追加します。

SubmitUsageCommand submitUsageCmd = new SubmitUsageCommand();
FetchCurrentChargesCommand fetchChargesCmd = new FetchCurrentChargesCommand();

JCommander jc = JCommander.newBuilder()
  .addCommand(submitUsageCmd)
  .addCommand(fetchChargesCmd)
  .build();

addCommand メソッドは、@ParametersアノテーションのcommandNames属性で指定されたそれぞれの名前でサブコマンドを登録します。

10.3. サブコマンドの解析

ユーザーが選択したコマンドにアクセスするには、最初に引数を解析する必要があります。

jc.parse(args);

次に、getParsedCommandを使用してサブコマンドを抽出できます。

String parsedCmdStr = jc.getParsedCommand();

JCommanderは、コマンドの識別に加えて、残りのコマンドラインパラメーターをサブコマンドのフィールドにバインドします。 ここで、使用するコマンドを呼び出す必要があります。

switch (parsedCmdStr) {
    case "submit":
        submitUsageCmd.submit();
        break;

    case "fetch":
        fetchChargesCmd.fetch();
        break;

    default:
        System.err.println("Invalid command: " + parsedCmdStr);
}

11. JCommander使用法ヘルプ

Usage を呼び出して、使用ガイドを表示できます。 これは、アプリケーションが使用するすべてのオプションの要約です。 このアプリケーションでは、mainコマンドで使用法を呼び出すことも、2つのコマンド「submit」と「fetch」のそれぞれで別々に使用法を呼び出すこともできます。

使用状況の表示は、ヘルプオプションの表示とエラー処理中の2つの方法で役立ちます。

11.1. ヘルプオプションの表示

booleanパラメーターとtrueに設定された属性helpを使用して、コマンドでヘルプオプションをバインドできます。

@Parameter(names = "--help", help = true)
private boolean help;

次に、引数に「–help」が渡されたかどうかを検出し、usingを呼び出すことができます。

if (cmd.help) {
  jc.usage();
}

「submit」サブコマンドのヘルプ出力を見てみましょう。

$ java App submit --help
Usage: submit [options]
  Options:
  * --customer, -C     Id of the Customer who's using the services
  * --subscription, -S Id of the Subscription that was purchased
  * --quantity         Used quantity; reported quantity is added over the 
                       billing period
  * --pricing-type, -P Pricing type of the usage reported (values: [PRE_RATED, 
                       UNRATED]) 
  * --timestamp        Timestamp of the usage event, must lie in the current 
                       billing period
    --price            If PRE_RATED, unit price to be applied per unit of 
                       usage quantity reported

Usage メソッドは、descriptionなどの@Parameter属性を使用して役立つ要約を表示します。 アスタリスク(*)が付いているパラメーターは必須です。

11.2. エラー処理

ParameterException をキャッチし、 Usage を呼び出して、ユーザーが入力が正しくなかった理由を理解できるようにします。 ParameterException には、ヘルプを表示するためのJCommanderインスタンスが含まれています。

try {
  jc.parse(args);

} catch (ParameterException e) {
  System.err.println(e.getLocalizedMessage());
  jc.usage();
}

12. 結論

このチュートリアルでは、JCommanderを使用してコマンドラインアプリケーションを構築しました。 主な機能の多くを取り上げましたが、公式のドキュメントにはさらに多くの機能があります。

いつものように、すべての例のソースコードは、GitHubから入手できます。