args4j

コマンドラインオプション/引数を簡単にパースできる小さなJavaクラスライブラリ。

javaclicommand-lineannotationargument-parsing

フレームワーク

args4j

概要

args4jは、Javaのコマンドライン引数を簡単に解析するための小さなクラスライブラリです。Kohsuke Kawaguchi氏によって開発され、アノテーションベースのシンプルなAPIを提供します。軽量で使いやすく、Javaプロジェクトにおいてコマンドライン引数の解析を行う際の定番ライブラリとして長年愛用されています。

なぜargs4jが選ばれるのか:

  • シンプルさ: 軽量で学習コストが低い
  • アノテーションベース: 直感的で宣言的なAPI
  • 成熟度: 長年にわたる安定した実績
  • 軽量性: 最小限の依存関係
  • 互換性: 幅広いJavaバージョンで動作

詳細

歴史と発展

args4jは2005年頃からKohsuke Kawaguchi氏によって開発が開始され、Java開発者にとってシンプルで使いやすいコマンドライン解析ライブラリとして成長しました。Hudson(現Jenkins)の作者でもある同氏により、実用的で堅実な設計が施されています。現在でも安定したライブラリとして多くのプロジェクトで使用されています。

エコシステムでの位置づけ

Java CLIライブラリのエコシステムにおいて、以下の特徴を持っています:

  • レガシープロジェクト: 古いJavaプロジェクトでの標準的な選択肢
  • シンプルなツール: 複雑でないCLIツールでの採用
  • 教育目的: Java学習者向けの入門ライブラリ
  • 軽量さ重視: 最小限の機能で十分な場合の選択肢

最新動向(2024-2025年)

  • メンテナンスモード: 大きな機能追加はないが、バグ修正は継続
  • 安定性重視: 既存APIの互換性を維持
  • Modern Java対応: Java11+での動作保証
  • Maven Central: 継続的なサポートとリリース
  • コミュニティ: 小規模だが安定したユーザーベース

主な特徴

コア機能

  • @Option: コマンドラインオプションの定義
  • @Argument: 位置引数の定義
  • 型変換: 基本的なJava型への自動変換
  • ヘルプ生成: 基本的なusage情報の自動生成
  • バリデーション: 簡単な入力値検証

サポートする型

  • 基本型: int, boolean, String, File
  • コレクション: List, Set, 配列
  • Enum: 列挙型のサポート
  • カスタム型: カスタムコンバーターによる拡張

設計思想

  • 軽量性: 最小限の機能に絞り込み
  • シンプルさ: 複雑な設定を避けた直感的なAPI
  • 実用性: 実際のプロジェクトでの使用を想定した設計
  • 安定性: 長期間にわたる安定したAPI

メリット・デメリット

メリット

  • 学習コストが低い: シンプルで直感的なAPI
  • 軽量: 小さなフットプリントと最小限の依存関係
  • 安定性: 長年にわたる実績と安定したAPI
  • 互換性: 古いJavaバージョンでも動作
  • 十分な機能: 基本的なCLI解析には十分
  • 成熟度: バグが少なく信頼性が高い

デメリット

  • 機能制限: 高度な機能やカスタマイズに制限
  • サブコマンド未対応: 複雑なコマンド構造に不向き
  • ヘルプ機能の制限: 自動生成されるヘルプがシンプル
  • 現代的でない: 新しいJava機能への対応が遅い
  • コミュニティが小さい: 積極的な開発やサポートが限定的

主要リンク

書き方の例

基本的な使用例

import org.kohsuke.args4j.*;

public class SampleMain {
    @Option(name = "-v", aliases = "--verbose", usage = "詳細出力を有効にする")
    private boolean verbose = false;

    @Option(name = "-o", aliases = "--output", usage = "出力ファイル名")
    private String outputFile = "output.txt";

    @Option(name = "-n", aliases = "--number", usage = "数値パラメータ")
    private int number = 1;

    @Argument(usage = "入力ファイル名", metaVar = "INPUT")
    private String inputFile;

    public static void main(String[] args) {
        new SampleMain().doMain(args);
    }

    public void doMain(String[] args) {
        CmdLineParser parser = new CmdLineParser(this);

        try {
            parser.parseArgument(args);

            if (inputFile == null) {
                throw new CmdLineException(parser, "入力ファイルが指定されていません");
            }

        } catch (CmdLineException e) {
            System.err.println(e.getMessage());
            System.err.println("java SampleMain [options...] INPUT");
            parser.printUsage(System.err);
            System.err.println();
            return;
        }

        // 実際の処理
        System.out.println("入力ファイル: " + inputFile);
        System.out.println("出力ファイル: " + outputFile);
        System.out.println("数値: " + number);
        if (verbose) {
            System.out.println("詳細モードが有効です");
        }
    }
}

コレクション型の使用例

import org.kohsuke.args4j.*;
import java.util.ArrayList;
import java.util.List;

public class CollectionExample {
    @Option(name = "-f", aliases = "--file", usage = "処理するファイルのリスト")
    private List<String> files = new ArrayList<>();

    @Option(name = "-D", usage = "プロパティの設定 (-Dkey=value)")
    private List<String> properties = new ArrayList<>();

    @Option(name = "-e", aliases = "--exclude", usage = "除外パターン")
    private String[] excludePatterns;

    @Argument(usage = "処理対象のディレクトリ")
    private List<String> directories = new ArrayList<>();

    public static void main(String[] args) {
        CollectionExample example = new CollectionExample();
        CmdLineParser parser = new CmdLineParser(example);

        try {
            parser.parseArgument(args);
        } catch (CmdLineException e) {
            System.err.println(e.getMessage());
            parser.printUsage(System.err);
            return;
        }

        System.out.println("ファイル: " + example.files);
        System.out.println("プロパティ: " + example.properties);
        System.out.println("除外パターン: " + java.util.Arrays.toString(example.excludePatterns));
        System.out.println("ディレクトリ: " + example.directories);
    }
}

Enum型とカスタム型の例

import org.kohsuke.args4j.*;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;

public class TypeExample {
    // Enum型
    public enum LogLevel {
        DEBUG, INFO, WARN, ERROR
    }

    @Option(name = "--log-level", usage = "ログレベル")
    private LogLevel logLevel = LogLevel.INFO;

    @Option(name = "-f", aliases = "--file", usage = "設定ファイル")
    private File configFile;

    @Option(name = "--url", usage = "リソースURL", handler = URLOptionHandler.class)
    private URL resourceUrl;

    @Option(name = "-q", aliases = "--quiet", usage = "静寂モード")
    private boolean quiet = false;

    // カスタムOptionHandler
    public static class URLOptionHandler extends OptionHandler<URL> {
        public URLOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super URL> setter) {
            super(parser, option, setter);
        }

        @Override
        public int parseArguments(Parameters params) throws CmdLineException {
            String param = params.getParameter(0);
            try {
                setter.addValue(new URL(param));
                return 1;
            } catch (MalformedURLException e) {
                throw new CmdLineException(owner, "無効なURL: " + param);
            }
        }

        @Override
        public String getDefaultMetaVariable() {
            return "URL";
        }
    }

    public static void main(String[] args) {
        TypeExample example = new TypeExample();
        CmdLineParser parser = new CmdLineParser(example);

        try {
            parser.parseArgument(args);
        } catch (CmdLineException e) {
            System.err.println(e.getMessage());
            parser.printUsage(System.err);
            return;
        }

        System.out.println("ログレベル: " + example.logLevel);
        System.out.println("設定ファイル: " + example.configFile);
        System.out.println("リソースURL: " + example.resourceUrl);
        System.out.println("静寂モード: " + example.quiet);
    }
}

バリデーションの例

import org.kohsuke.args4j.*;
import java.io.File;

public class ValidationExample {
    @Option(name = "-p", aliases = "--port", usage = "サーバーポート番号", required = true)
    private int port;

    @Option(name = "-h", aliases = "--host", usage = "ホスト名")
    private String host = "localhost";

    @Option(name = "--config", usage = "設定ファイルパス")
    private File configFile;

    @Option(name = "--threads", usage = "スレッド数")
    private int threads = 1;

    @Argument(usage = "作業ディレクトリ", required = true)
    private String workDir;

    public static void main(String[] args) {
        ValidationExample example = new ValidationExample();
        CmdLineParser parser = new CmdLineParser(example);

        try {
            parser.parseArgument(args);
            
            // カスタムバリデーション
            example.validate();
            
        } catch (CmdLineException e) {
            System.err.println(e.getMessage());
            System.err.println("java ValidationExample [options...] WORKDIR");
            parser.printUsage(System.err);
            return;
        }

        System.out.println("サーバー開始: " + example.host + ":" + example.port);
        System.out.println("作業ディレクトリ: " + example.workDir);
        System.out.println("スレッド数: " + example.threads);
        if (example.configFile != null) {
            System.out.println("設定ファイル: " + example.configFile);
        }
    }

    private void validate() throws CmdLineException {
        // ポート番号の範囲チェック
        if (port < 1 || port > 65535) {
            throw new CmdLineException(null, "ポート番号は1-65535の範囲で指定してください: " + port);
        }

        // スレッド数のチェック
        if (threads < 1 || threads > 100) {
            throw new CmdLineException(null, "スレッド数は1-100の範囲で指定してください: " + threads);
        }

        // 設定ファイルの存在確認
        if (configFile != null && !configFile.exists()) {
            throw new CmdLineException(null, "設定ファイルが見つかりません: " + configFile);
        }

        // 作業ディレクトリの確認
        File workDirFile = new File(workDir);
        if (!workDirFile.exists()) {
            throw new CmdLineException(null, "作業ディレクトリが見つかりません: " + workDir);
        }
        if (!workDirFile.isDirectory()) {
            throw new CmdLineException(null, "作業ディレクトリがディレクトリではありません: " + workDir);
        }
    }
}

高度な使用例(ヘルプとエラーハンドリング)

import org.kohsuke.args4j.*;
import java.io.File;
import java.io.StringWriter;
import java.io.PrintWriter;

public class AdvancedExample {
    @Option(name = "-h", aliases = "--help", usage = "ヘルプを表示")
    private boolean help = false;

    @Option(name = "-v", aliases = "--version", usage = "バージョンを表示")
    private boolean version = false;

    @Option(name = "-i", aliases = "--input", usage = "入力ファイル", metaVar = "FILE")
    private File inputFile;

    @Option(name = "-o", aliases = "--output", usage = "出力ファイル", metaVar = "FILE")
    private File outputFile;

    @Option(name = "--format", usage = "出力形式")
    private String format = "text";

    @Option(name = "--verbose", usage = "詳細出力")
    private boolean verbose = false;

    private static final String VERSION = "1.0.0";
    private static final String PROGRAM_NAME = "advanced-example";

    public static void main(String[] args) {
        new AdvancedExample().run(args);
    }

    private void run(String[] args) {
        CmdLineParser parser = new CmdLineParser(this);
        
        try {
            parser.parseArgument(args);
            
            if (help) {
                printHelp(parser);
                return;
            }
            
            if (version) {
                printVersion();
                return;
            }
            
            // 必要な引数のチェック
            if (inputFile == null) {
                throw new CmdLineException(parser, "入力ファイルが指定されていません");
            }
            
            execute();
            
        } catch (CmdLineException e) {
            System.err.println("エラー: " + e.getMessage());
            printUsage(parser);
            System.exit(1);
        } catch (Exception e) {
            System.err.println("実行エラー: " + e.getMessage());
            if (verbose) {
                e.printStackTrace();
            }
            System.exit(2);
        }
    }

    private void execute() {
        System.out.println("処理開始...");
        System.out.println("入力ファイル: " + inputFile);
        if (outputFile != null) {
            System.out.println("出力ファイル: " + outputFile);
        }
        System.out.println("出力形式: " + format);
        
        if (verbose) {
            System.out.println("詳細モードで実行中...");
        }
        
        // 実際の処理をここに記述
        System.out.println("処理完了");
    }

    private void printHelp(CmdLineParser parser) {
        System.out.println(PROGRAM_NAME + " v" + VERSION);
        System.out.println();
        System.out.println("使用法: java " + getClass().getSimpleName() + " [options...]");
        System.out.println();
        System.out.println("オプション:");
        
        // StringWriterを使って使用法を取得
        StringWriter sw = new StringWriter();
        parser.printUsage(new PrintWriter(sw));
        System.out.print(sw.toString());
        
        System.out.println();
        System.out.println("例:");
        System.out.println("  java " + getClass().getSimpleName() + " -i input.txt -o output.txt");
        System.out.println("  java " + getClass().getSimpleName() + " --input data.csv --format json --verbose");
    }

    private void printVersion() {
        System.out.println(PROGRAM_NAME + " version " + VERSION);
    }

    private void printUsage(CmdLineParser parser) {
        System.err.println();
        System.err.println("使用法: java " + getClass().getSimpleName() + " [options...]");
        parser.printUsage(System.err);
        System.err.println();
        System.err.println("詳細については --help を使用してください");
    }
}

Maven設定例

<dependency>
    <groupId>args4j</groupId>
    <artifactId>args4j</artifactId>
    <version>2.37</version>
</dependency>

Gradle設定例

dependencies {
    implementation 'args4j:args4j:2.37'
}