はじめに
Jakarta Commonsは、さまざまなJakartaプロジェクトで使われている再利用可能なクラスの集まりです。これらのクラスは、独立したコンポーネントとして自分のJavaプロジェクトで利用することができます。今回はJakarta Commonsを紹介する3回シリーズの最終回にあたり、便利なコンポーネントをあと4つ取り上げ、サンプルアプリケーションを通じてその使い方を説明していきます。まだパート1とパート2を読んでいない方は、先にそちらをご覧ください。これらのサンプルはJakarta Commonsコンポーネントを例示するだけのものではなく、典型的なJavaプロジェクトで再利用できる有用な機能を盛り込んだ完全なアプリケーションです。
本稿では次のコンポーネントを取り上げます。
- CLI(コマンドラインインターフェイス)
- VFS(仮想ファイルシステム)
- Configuration
- Pool
本稿には完全なソースコードが付属しており、各サンプルのテストケースをJUnitで起動することで実行できます。
CLI
CLI(コマンドラインインターフェイス)コンポーネントは、コマンドラインアプリケーションの引数解析に大変重宝します。こうした引数解析コードを書く作業は、時間がかかって煩わしいものです。また、このコンポーネントを使えば、既存のCLIアプリケーションの機能拡張も容易になります。コードのリファクタリングにより、新しい機能を効率的に追加できます(Jakartaのサイトには、CLIの使い方の概要がわかりやすく記されています)。
本シリーズの第1回では、Commons Chainコンポーネントの解説のところで、ネットワークコマンドを実行するコマンドラインツールのサンプルを使用しました。Common CLIの説明にも同じサンプルアプリケーションを使います。このサンプルのソースコードは、ダウンロードサンプルのパッケージ「in.co.narayanan.commons.cli」に含まれています。メインクラスCommandLine
を起動すれば、このサンプルアプリケーションの引数が確認できます。リスト1は、このサンプルアプリケーションのコマンドラインオプションです。
// java CommandLine -user admin -password manager -ping {host} // java CommandLine -user admin -password manager // -ftp {host} -get {path_to_file} // java CommandLine -user admin -password manager // -ftp {host} -ls {path_to_file}
このコードの処理レイヤには、CommonsのChainとCommandのパターンが使われています。コマンドラインのネットワークコマンドのそれぞれに、該当する処理を行うクラスが存在します。各クラスは連結してチェーンを形成します。コマンドが呼び出されると、まずチェーンの最初のコマンドに引数が与えられます。目的の処理を行うコマンドが見つかるまで、このチェーンに沿って実行が進みます。ここでの目標は、チェーン内の処理クラスがコンテキストオブジェクトを利用できるように、Commons CLIを使って引数の解析とコンテキストオブジェクトの作成を行うことです。
リスト2は、「in.co.narayanan.commons.cli.CommandLine.java」のコードの一部です。pingコマンドに渡された引数をCommons CLIを用いて解析する方法を示しています。
private void createPingCmdOptions() { // ping command // java CommandProcessor -user admin -password manager -ping {host} pingOptions = new Options(); Option user = createUserOption(); pingOptions.addOption(user); Option passwd = createPasswordOption(); pingOptions.addOption(passwd); Option ping = OptionBuilder.withArgName("ping") .hasArg() .isRequired() .withDescription("Ping a remote system") .create("ping"); pingOptions.addOption(ping); }
このコードでは、個々のコマンドラインオプションを表すOption
クラスのインスタンスを作成して、Options
クラスのインスタンスに追加していきます。OptionBuilder
クラスは、一連の静的メソッドを通じてOption
クラスのインスタンスを作成する機能を持ちます。リスト2では、pingという名前の、引数を1つ取る必須オプションのインスタンスを作成しています。このインスタンスには、オプションの説明("Ping a remote system"
)も含まれています。OptionBuilder
のwithArgName
メソッドに渡される文字列は、解析終了後にその引数の値を取り出すために使用されます(具体的な方法は後述)。OptionBuilder
を使わずにOption
クラスのインスタンスを直接作成して、オプションのもっと細かい設定をすることもできます。
createUserOption
メソッドとcreatePasswordOption
メソッドは、それぞれコマンドラインのuser引数とpassword引数を表す新しいインスタンスを返します。今回のサンプルでは、別のコマンド用にOption
クラスのインスタンスを作成するときに、これらのメソッドを使用します。
リスト3は、ftpコマンド用のCommons CLIのコードです。
Option ftp = OptionBuilder.withArgName("ftp") .hasArg() .isRequired() .withDescription("File transfer protocol") .create("ftp"); ftpOptions.addOption(ftp); // For additional ftp commands like ls, put, and mget, // a OptionGroup needs to be created // to indicate the options are mutually exclusive Option get = OptionBuilder.withArgName("get") .hasArg() .withDescription("Get a file from the server") .create("get"); Option ls = OptionBuilder.withArgName("ls") .hasArg() .withDescription("List the folder contents in the server") .create("ls"); OptionGroup ftpArgs = new OptionGroup(); ftpArgs.addOption(get); ftpArgs.addOption(ls); ftpArgs.setRequired(true); ftpOptions.addOptionGroup(ftpArgs);
このサンプルで扱うftpネットワークコマンドには、getとlsという相互排他的なオプションが含まれます。こうした相互排他的なオプションを表すために、リスト3ではOptionGroup
クラスを使用しています。具体的には、OptionGroup
クラスのsetRequired
メソッドを呼び出すときに、これらの相互排他的オプションが必須であることを示すtrue
を渡しています。これにより、ユーザーはgetとlsのどちらのオプションを必ず入力しなければならなくなります。
このコード全体において、Common CLIのフレームワークは次の処理を行っています。
- コマンドライン引数の文字列配列を解析し、渡された値に基づいて各種
Option
クラスのインスタンスを作成する - 検証を実施する(たとえば、必須の引数がユーザーから与えられなかった場合は例外をスローする)
- ユーザーから与えられた引数の数や種類が正しくない場合は用法メッセージを表示する
リスト4は、パーサークラスのparse
メソッドを呼び出す例です。
public void process(String args[]) { // remaining code CommandLineParser parser = new BasicParser(); org.apache.commons.cli.CommandLine line = null; Context chainContext = null; // remaining code case PING : { try { line = parser.parse(pingOptions, args); chainContext = getPingContext(line); } catch (ParseException e) { System.out.println(e.toString()); HelpFormatter formatter = new HelpFormatter(); formatter.printHelp( "Ping options", pingOptions); } } break; // remaining code } private Context getPingContext( org.apache.commons.cli.CommandLine line) { String user = line.getOptionValue("user"); String passwd = line.getOptionValue("password"); String host = line.getOptionValue("ping"); return new CommandlineContext( user, passwd, new CLICommand("-ping", new String[] {host})); }
太字のコードは、パーサーの使い方を理解するにあたって非常に重要な部分です。最初に、パーサークラスBasicParser
のインスタンスを作成しています。なお、Unix形式のCLIコマンドの場合はPosixParser
を使用します。カスタムの要件によっては、org.apache.commons.cli.Parser
クラスの拡張、またはorg.apache.commons.cli.CommandlineParser
インターフェイスの実装を行うことでパーサーを一から作ることもできます。
parse
メソッドを呼び出すと、コマンドラインオプションの値を取得するために利用するorg.apache.commons.cli.CommandLine
への参照が返されます。ここでは、この参照をgetPingContext
メソッドに渡すことで、個々のオプション値を含んだCommandlineContext
クラスのインスタンスを作成しています。このインスタンスを、以降の処理のためにチェーンに渡します。
parse
メソッドによる引数解析の途中で問題が生じた場合は、Commons CLIは例外をスローします。この場合は、org.apache.commons.cli.HelpFormatter
クラスによって、エラーを通知する書式設定済みのヘルプメッセージが表示されます。
Commons CLIのフレームワークは、単独のコマンドに対してのみオプションの解析を行うことができます。このサンプルと同じように、コマンドオプションのあるコマンドが複数存在するCLIアプリケーションの場合は、コマンドの種類を識別するために少し前処理を行う必要があります(リスト5を参照)。
private int classifyCommand(String args[]) throws CommandLineException { if(args != null && args.length > 0) { for(String arg : args) { if(arg.equals("-ping")) { return PING; } if(arg.equals("-ftp")) { return FTP; } } } else { throw new CommandLineException( "Invalid command options. See usage."); } throw new CommandLineException( "Invalid command options. See usage"); }
このサンプルアプリケーションでは、pingとftpは異なるコマンドであり、それぞれ異なるコマンドラインオプションを持っています。そのため、Options
クラスのインスタンスを1つ用意しただけでは、これらのコマンドライン引数を表現できません。コマンドの種類を識別したうえで、以降の解析に適切なOptions
クラスのインスタンスを用いることになります。
Commons CLIはJavaのあらゆるCLIアプリケーションにとって不可欠なすばらしいAPIです このAPIを使えば、CLIアプリケーションの機能拡張にかかる時間を節約し、煩わしさを軽減できます。Jakarta Antプロジェクトでは、コマンドライン引数の処理にCommons CLIが利用されています。