2013年05月13日

Commons CLI vs. args4 - Java コマンドライン オプション・パーサー比較

JavaでCLIアプリケーションを開発し、少し凝った・本格的なオプション(引数)操作を実装する場合、貴方ならどうしますか?
 
知名度で言えば圧倒的に Apache Commons CLI ですが、今日は args4j という新世代(言い過ぎ?)オプション・パーサーも含めて紹介・比較してみます。

CLI option handling


Apache Commons CLIによるオプションのパースとハンドリング


Commons CLIはApache Commonsプロジェクトで開発されているCLI (Command Line Interface) アプリケーション用ライブラリです。

Commons CLI - Home

Apache Commons プロジェクトでは、基本的な機能・API(Low-Level-API)の提供まで、便利な反面、利用者を制限する可能性がある高機能API(Hight-Level-API)は作成せずユーザの判断・実装に委ねる、というポリシーがあるように思います。

バイナリ・ファイルをダウンロードし、commons-cli-x.x.jarをクラスパスに追加すれば準備はおしまい。

add apache commons cli to class path

さあ、このApache Commons CLIを使って、CLIアプリケーションを実装してみましょう。

例として、次にのような起動オプションを取り扱うアプリケーションを実装してみます。

$ java -jar MyApp.jar -i eth0 -d destination.com -s

-iオプション(必須)でネットワーク・インタフェース名、-dオプション(必須)で行き先ホスト名、-sオプション(オプション)でサーバー・モードで起動します。

Commons CLI ではGNU、POSIXなど特定のオプション形式に基づいた記述も可能ですが、今回は簡単に次のようなコードにします。

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class MyApp {

 private String ifName = null;
 private boolean isServer = false;
 private String dst = null;
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  new MyApp().start(args);
 }

 public void start(String[] args) {
  
  Options opts = new Options();
  opts.addOption("i", "interface", true, "Listen on interface.");
  opts.addOption("s", "server", false, "Run server mode.");
  opts.addOption("d", "dst", true, "Destination address (IP or Hostname)");
  BasicParser parser = new BasicParser();
  CommandLine cl;
  HelpFormatter help = new HelpFormatter();
  
  try {
   
   // parse options
   cl = parser.parse(opts, args);
   
   // handle server option.
   if ( cl.hasOption("-s") ) isServer = true;
   
   // handle interface option.
   ifName = cl.getOptionValue("i");
   if (ifName == null) {
    throw new ParseException("");
   }
   
   // handlet destination option.
   dst = cl.getOptionValue("d");
   if (dst == null) {
    throw new ParseException("");
   }
   
   System.out.println("Starting application...");
   System.out.println("Interface   : " + ifName);
   System.out.println("Server mode : " + isServer);
   System.out.println("Destination : " + dst);
   
   // your code
   
  } catch (ParseException e) {
   // print usage.
   help.printHelp("My Java Application", opts);
   System.exit(1);
  }
 }

}

まず、Options オブジェクトを作成し、これにaddOptionメソッドを使ってオプション定義を追加していきます。

addOptionメソッドで指定している引数は POSIXオプション名、エイリアス(GNUオプション)名、値の有無(true/false)、説明の計4つ。

オプションを表す Option オブジェクトを生成し、より詳細な設定を行い、同様にaddOptionメソッドで追加する事も可能ですが、ここでは最もシンプルな方法でオプションを定義しています。

このオプション定義とJava/mainメソッドの引数を BasicParser オブジェクトでパースし(不正な引数であればこの時点で例外が発生。ヘルプ・メッセージを表示)、結果を CommandLine オブジェクトとして受け取ります。

最後に、この CommandLine オブジェクトからオプションの値を取り出し、値の有無をチェック、全てのオプションが正しければアプリケーションを起動しているんですね。

このアプリケーションで故意に不正なオプションを指定し、起動すると次のようなメッセージが出力されます。

$ java -jar MyApp.jar
usage: My Java Application
 -d,--dst <arg>         Destination address (IP or Hostname)
 -i,--interface <arg>   Listen on interface.
 -s,--server            Run server mode.

以上が Apache Commons CLI を利用したオプションのパース及びハンドリングですが、皆さんはこのコードを見て何か不満を覚えたでしょうか?

僕は CommandLine オブジェクトから getOptionValue メソッドで値を取得した後、それがnullでは無いか?、nullでなければ値をIntegerに変換(キャスト)する、といった値のハンドリングを行うコードが無意味に繰り返しがちになるのが、Commons CLI への不満。

いえ、勿論、書き方(利用するクラス)次第かもしれません。ただ、Apache Commons らしいと言いますか、別の書き方をしても、結局の所、Low-Level-APIであってコード量は多くなる、そんな印象でした。

このような不満の中で生まれたのが args4j なんです(多分・・・)。


args4j によるCLIオプションのパースとハンドリング


args4j のプロジェクト・ホームはこちら。

args4j parent - Args4j

こちらにあるダウンロード・リンクを辿り、args4j-x.x.x.jarをクラスパスに追加されば準備はおしまい。

add args4j to class path

先程と同じCLIオプションを扱うアプリケーションは次のようなコードになります。

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;


public class MyApp {

 @Option(name="-i", aliases="--interface", required=true, usage="Listen on interface.")
 private String ifName = null;
 
 @Option(name="-s", aliases="--server", usage="Run server mode.")
 private boolean isServer = false;
 
 @Option(name="-d", aliases="--dst", required=true, usage="Destination address (IP or Hostname)")
 private String dst = null;
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  new MyApp().start(args);
 }
 
 public void start(String[] args) {
  
  CmdLineParser parser = new CmdLineParser(this);
  
  try {
   
   // parse options
   parser.parseArgument(args);
   
   System.out.println("Starting application...");
   System.out.println("Interface   : " + ifName);
   System.out.println("Server mode : " + isServer);
   System.out.println("Destination : " + dst);
   
   // your code
   
  } catch (CmdLineException e) {
   // print usage.
   parser.printUsage(System.out);
   System.exit(1);
  }
 }

}

どうでしょうか?、一見するだけでコード量が減っている事が分かります。

減る理由は、先程、単調・退屈と指摘したオプション値のハンドリングが省略されているから。

Java 1.5 以上でサポートされたアノテーションを使い、@Optionアノテーションと変数(引数に相当するJavaオブジェクト/プリミティブ)を定義したインスタンスを CmdLineParser オブジェクトに渡すとオプションの有無から値のキャストまでフレームワーク側で行なってくれるんですね。

勿論、独自のHandlerクラスも定義可能です。

CmdLineParser オブジェクトにアノテーションを定義したオブジェクトを渡す必要があるのでmainメソッドだけでCLIオプションのハンドリングを完結させることは出来ませんが、このような手順も慣れさえすれば問題は無いはずです。

このアプリケーションをビルド、故意に異常な引数を指定・起動すると次のような出力になります。

$ java -jar MyApp.jar 
 -d (--dst) VAL       : Destination address (IP or Hostname)
 -i (--interface) VAL : Listen on interface.
 -s (--server)        : Run server mode.

表示されるヘルプ・メッセージの見やすさは Commons CLI に軍配が上がる(?)気がしますね。


結局のところ Commons CLI と args4j ではどちらがオススメなのか?


うーん、個人的には args4j の試みは評価しつつも、実際には Commons CLI を使う機会が多くなりそうです。

args4jは単調・繰り返し出現するコードを消し込んではくれますが、このメリットが Commons CLI の歴史・知名度、その結果としてのネットワーク外部性(コーディング方法の一般性、コニュニティの将来性)を上回るかと言われればノー、文句は無いが巨人に対して絶対的なメリットも無いという感想。

個人の範囲で管理・開発する、ちょっとした趣味アプリケーションから採用するのが良いのかもしれません。

日本人の方も開発に参加されているようですし、応援したいプロジェクトですね。

それでは、また今度!

Jakarta Commonsクックブック ―Javaプロジェクト必須のレシピ集
Timothy M. O'Brien
オライリージャパン
売り上げランキング: 442,210

Jenkins実践入門 ~ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)
佐藤 聖規 和田 貴久 河村 雅人 米沢 弘樹 山岸 啓
技術評論社
売り上げランキング: 30,741

Posted by netbuffalo at 20:30│Comments(0)TrackBack(0)Java | プログラミング


この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/netbuffalo/4463292

コメントする

名前
URL
 
  絵文字