Apache Commons CLI OptionBuilder.withType(Object) のなぞ
【NOTE】 この記事はApache Commons CLI のバージョン1.2について記述しています。2015年5月時点の最新版であるバージョン1.3ではライブラリが提供するインターフェースに大きな変更がありました。この点についてはこちらの記事で若干言及しています。
先日から何度かとりあげているCommonsのCLI(その1、その2)。コマンドライン・オプションの引数として与えられた文字列を特定の型のインスタンスとして取得できたら便利だな、と考えてすこし調べた。
withType(Object)メソッドを使ってみる
コマンドライン・オプションの定義情報であるOptionクラス・インスタンスを生成するためのOptionBuilderクラス・メソッドには、withType(Object)=> OptionBuilderというものがあって、これに相応するものとしてCommandLineクラス(OptionのコレクションであるOptionsとString[]を引数に、CommandLineParser実装のparseメソッドを呼び出すことで取得できる)のgetParsedOptionValue(String)=> Objectもある。
package com.m12i.java.cli.study; import java.sql.Date; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; public class Main { // ひとによったら難色を示しそうなアノテーション・・・ @SuppressWarnings("static-access") public static void main(String[] args) throws ParseException { // Optionsインスタンスを用意 final Options options = new Options(); // まず日付型のオプションを定義 options.addOption(OptionBuilder .hasArg(true) .withType(Date.class) .withArgName("dateArg") .withLongOpt("date") .create('d')); // つぎに数値型のオプションを定義 options.addOption(OptionBuilder .hasArg(true) .withType(Number.class) .withArgName("numberArg") .withLongOpt("number") .create('n')); // それでは引数をパースしてみよう final CommandLine cmd = new PosixParser().parse(options, args); // それぞれの引数の値を取得してみる final Date dateValue = (Date) cmd.getParsedOptionValue("date"); final Number numberValue = (Number) cmd.getParsedOptionValue("number"); System.out.println(String.format("--date: %s, --number: %s", dateValue, numberValue.intValue())); } }
このプログラムを"-d 2013-03-01 -n 123.456"というオプションを引数に与えて実行すると、結果は"--date: null, --number: 123"という出力となる(使用したCLIのバージョンは1.2)。ちなみに123,456とやるとエラーとなる。
Date型でオプション引数値を取得できない
が、それはともかくとして、Date型を指定したオプションのほうはかならずnullである。おかしいなぁと思って、内部で使用されているらしいTypeHandlerというクラスの実装を確認したら以下のようになっていた。
public static Date createDate(String str) throws ParseException { throw new UnsupportedOperationException("Not yet implemented"); }
それでJavadocの説明文自体をよみなおすと、“This is a temporary implementation.”と堂々と書かれていた(バージョン1.3でも同じ記述になっていた)。
UnsupportedOperationExceptionなんてスローする実装になっているからには、CLIにあらかじめ同梱されたTypeHanderはあくまでもデフォルト実装というかテンプレート実装であって、これを継承して独自のクラスをつくってコマンドラインをパースすするときに使えるのか、というとそんなわけでもない。
ようするに、「なんだかイケてない」・・・。
「だけどまあ、どうせコマンドライン引数の処理は複雑になりがちで、個別のオプションに設定した型情報だけ検証できたって仕方ない、複数のオプション同士の関係が問題になるケースもあるわけで、TypeHandlerを完全に実装したとしても、それだけで解決できるようなはなしではない。だからコマンドライン引数の型解決のような高度な処理は、オプションの処理を専門とするサービスクラスの独自実装でこなすべきだ。」という考え方はできる。だけど現状の中途半端さに変わりはない・・・。