Apache Commons CLI – да придет спаситель

Posted on: April 9th, 2011 by Spade 1 Comment »

Раз в год и палка стреляет… Приблизительно так же часто мне бывает нужно написать приложение без графического интерфейса, чтоб запускалось из командной строки и принимало кучу параметров-настроек. Тут же настроение падает – что может быть более нудным, чем парсить строки вида param=value или pvalue или еще каких-то причудливых форм. Но потом посещает мысль «неужели опять собирать велосипед из подручных средств?…». Кто-то ведь наверняка это уже делал. В наше время, за что ни возьмись – все стихи и песни давно написаны, все алгоритмы давно придуманы, а то, что вам приснилось сегодня ночью гениального – то приснилось прошлой ночью другому, и сегодня уже пишется командой трудолюбивых китайцев.

Так же с приемом параметров командной строки и их анализом – апаче придет на помощь. Обожаю апаче с их миллиародом проектов и библиотек, о существовании которых мало кто подозревает, еще меньше народу знает как их можно применить, и уж совсем мало – кто реально извлекает выгоду из них. Итак Apache Commons CLI – маленькая либа которая позволяет вашему приложению принимать параметры в разных формах, разбирает их и дает уже готовенькое. Скачиваем commons-cli-1.2.jar с официального апаче (и не забываем проверить md5 хеш, а то вдруг вам злоумышленник подсунул левак).

Формат опций – какой хош:

  1. POSIX like options (ie. tar -zxvf foo.tar.gz)
  2. GNU like long options (ie. du –human-readable –max-depth=1)
  3. Java like properties (ie. java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo)
  4. Short options with value attached (ie. gcc -O2 foo.c)
  5. long options with single hyphen (ie. ant -projecthelp)

Классов в библиотеке – можно пересчитать по пальцам. Все они находятся в пакете

import org.apache.commons.cli.*;

даже под-пакетов нет…

Для примера возьмем серверную часть чата, куда можно передавать опции для его настройки при запуске. К примеру, количество комнат которое позволено в чате, и максимальное количество пользователей в одной комнате:

  • rooms-limit
  • user-per-room

Теперь пример класса отвечающего за обработку командной строки.

package com.videochat.common.utils;
 
import org.apache.commons.cli.*;
 
public class CliHandler {
 
    public final static String ROOMS_LIMIT = "rooms-limit";
    public final static String USER_PER_ROOM = "user-per-room";
 
    private Options options;
    private CommandLine line;
 
    public CliHandler() {
 
        options = new Options();
        options.addOption( OptionBuilder.withLongOpt(ROOMS_LIMIT)
                                .withDescription( "If set - chat will limit number of rooms to that value" )
                                .hasArg()
                                .withArgName("NUMBER")
                                .create());
        options.addOption( OptionBuilder.withLongOpt(USER_PER_ROOM)
                                .withDescription( "If set - chat will limit number of users in one room" )
                                .hasArg()
                                .withArgName("NUMBER")
                                .create());
 
        options.addOption(OptionBuilder.withLongOpt("help")
                .withDescription("Print help")
                .create("h"));
    }
 
    public void parse(String[] args) throws Exception{
 
        CommandLineParser parser = new PosixParser();
        line = parser.parse(options, args);
        if(line.hasOption("help")) {
            throw new Exception("Print help and exit");
        }
    }
 
    public Integer getRoomsLimit() {
        return getIntOption(ROOMS_LIMIT);
    }
 
    public Integer getUserPerRoom() {
        return getIntOption(USER_PER_ROOM);
    }
 
    private Integer getIntOption(String optionName) {
        Integer val = 0;
        if( line.hasOption( optionName ) ) {
            try {
                val = Integer.parseInt(line.getOptionValue( optionName ));
                val = val > 0 ? val : -val;
            } catch (NumberFormatException e) {
            }
            return val;
        } else {
            return val;
        }
    }
 
    public void printCliHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("chat application", "Read following instructions for tuning chat work",
                options, "Developed by Acestime.Com");
    }
}

Главные классы: Options, OptionBuilder, CommandLine, CommandLineParser интерфейс и его реализация PosixParser.

Options – «сборище» опций для нашего чата. По своей сути контейнер, по которому потом будет устанавливаться соответствие между введенными данными и вашими настройками.

OptionBuilder – класс-помошник. Реализует паттерн для замены конструктора с переменным числом параметров.

CommandLine – содержит итоговую информацию о параметрах по типу Map.

PosixParser– парсер для разбора параметров вида:

--user-per-room=3

При создании опции с помощью билдера используется цепочка вызовов:

options.addOption( OptionBuilder.withLongOpt(ROOMS_LIMIT)
                                .withDescription( "If set - chat will limit number of rooms to that value" )
                                .hasArg()
                                .withArgName("NUMBER")
                                .create());

withLongOpt() – указывает «длинное имя параметра»

withDescription() – описание при выводе help

hasArg() – имеет ли параметр значение или используется просто как флаг

withArgName() – имя параметра при выводе help

create() – создает опцию. Можно передать как аргумент в эту функцию короткое имя параметра

Отдельного внимания заслуживает функция:

public void printCliHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("chat application", "Read following instructions for tuning chat work",
                options, "Developed by Acestime.Com");
    }

Как написать логику для вывода справки – вам решать. Эта функция направляет в стандартный поток следующую информацию:

usage: chat application
Read following instructions for tuning chat work
-h,--help                     Print help
--rooms-limit <NUMBER>     If set - chat will limit number of rooms to
that value
--user-per-room <NUMBER>   If set - chat will limit number of users in
one room
Developed by Acestime.Com

Понятно, что обработка параметров должна быть где-то близко к старту:

try {
    cliHandler.parse(args);
} catch (Exception e) {
    cliHandler.printCliHelp();
    return;
}
 
Integer roomsLimit = cliHandler.getRoomsLimit();
Integer userPerRoom = cliHandler.getUserPerRoom();

В принципе дополнительный класс для обработки опций необязателен, но те, кто любит яву с её принципом – делай класс из всего, что только есть под рукой – наверняка так и сделают. Удачи вам с командными строками и их параметрами.

One Response

  1. _house_md_ says:

    Спасибо !!!

Leave a Reply