Обработка аргументов с помощью JCommander #
Васильев Андрей Михайлович, 2025
Версии презентации
Аргументы приложения #
- Приложение при старте может получить некоторый набор строк, аргументов, которые были указаны при запуске приложения
- Приложение может использовать их для настройки собственного поведения
- Разработчик приложения волен самостоятельно определить формат обработки строк
- Разумно использовать подходы, которые известны пользователю в результате работы с другими приложениями, сделать приложение «интуитивно понятным»
- Существует множество библиотек для JVM, позволяющих выполнить обработку передаваемых строк. Рассмотрим JCommander
Официальная документация: https://jcommander.org/
JavaDoc-документация: https://javadoc.io/doc/com.beust/jcommander/latest/index.html
Альтернаитвное Java-решение: https://picocli.info/
Подключение библиотеки #
Для работы с библиотекой, её необходимо добавить в список зависимостей приложения
Gradle-описание зависимости для подключения: "org.jcommander:jcommander:2.0"
При использовании Gradle без дополнений её необходимо добавить в файл
build.gradle.kts
в раздел dependencies
:
dependencies {
implementation("org.jcommander:jcommander:2.0")
}
При использовании шаблона лабораторной добавьте строку в файл
build.properties.json
в список dependencies
Концептуальный подход JCommander #
- Необходимо описать и создать объекты-хранители аргументов
- Необходимо создать ключевой объект библиотеки JCommander
- Объект JCommander создаётся путём взаимодействия с объектом-строителем
- Объекту-строителю передаётся список объектов-хранителей аргументов
- Созданному объекту передаются аргументы командной строки
- При работе JCommander опирается на мета-информацию, расположенную в аннотациях к свойствам объектам-хранителям аргументов
- Объект-хранитель вместе с хранением описывает поддерживаемые аргументы
- В случае ошибки обработки аргументов выбрасывается исключение ParameterException
Описание объекта-хранителя аргументов #
@Parameters(separators = "=")
class Args {
@Parameter
var targets: List<String> = arrayListOf()
@Parameter(names = ["-bf", "--buildFile"],
description = "The build file")
var buildFile: String? = null
@Parameter(names = ["--checkVersions"],
description = "Check if there are any newer versions")
var checkVersions = false
}
- Аннотация Parameters определяет общие правила обработки строк для данного
объекта, не обязательна для использования
- С помощью аргументов
separators="="
включается обработка аргументов в форматеключ=значение
помимо форматаключ значение
- С помощью аргументов
- Конкретные свойства обозначаются для записи с помощью аннотации @Parameter
- Свойства должны быть изменяемыми
- Свойства могут иметь значения по умолчанию
- Названия ключей для свойств объектов определяется с помощью
names
- Рекомендуется добавлять описание параметра, чтобы предоставить пользователю содержательную информацию по назначению аргумента
Создание и вызов JCommander #
val commander: JCommander =
JCommander
.newBuilder()
.addObject(commonParameters)
.build()
- Создаём объект-строитель путём вызова статического метода
newBuilder()
- Добавляем объекты-хранители аргументов с помощью
addObject()
- Завершаем создание путём вызова метода
build()
- Передаём аргументы путём вызова метода
parse()
, принимающего произвольное количество строк
commander.parse("--enable", "--size", "15")
val arguments: Array<String> = ...
commander.parse(*arguments)
- Обработанные аргументы будут сохранены в объект
commonParameters
- Если строки не смогут быть обработаны, будет выброшено исключение ParameterException
Преобразование в сложные типы данных #
JCommander постарается автоматически преобразовать данные из строкового представления в тип данных свойства класса для: логических полей, целых чисел, вещественных чисел и строк
Для других сложных типов данных, классов можно предоставить класс-преобразователь, который должен реализовывать следующий Java-интерфейс:
public interface IStringConverter<T> {
T convert(String value);
}
Например для UUID на Kotlin такой класс может выглядеть следующим образом:
class UUIDConverter : IStringConverter<UUID> {
override fun convert(parameter: String): UUID =
UUID.fromString(parameter)
}
В целевом объекте-хранителе аргументов необходимо сослаться на данный преобразователь следующим образом:
@Parameter(names=["--id"], converter = UUIDConverter::class)
var someId: UUID? = null
- Если во время преобразования строки к UUID возникнет ошибка, то будет выброшено исключение IllegalArgumentException
- Конвертор необходимо явно указывать для каждого поля в параметрах аннотации Parameter, для которого необходимо выполнить преобразование
Обязательные аргументы #
Аннотация Parameter позволяет указать аргумент как обязательный:
@Parameter(names=["--data-one"], required = true)
var dataOne: Int = 0
Согласно документации в случае отсутствия аргумента будет выброшено соответствующее исключение. Однако в данном случае оно не будет выброшено
- JCommander получает уже инициализированный объект
- JCommander обрабатывает строки аргументов, записывая результаты в свойства объекта
- Если после обработки всех строк какие-то поля остались равными
null
, то будет выброшено исключение
Следовательно надо обязательные аргументы описывать так:
@Parameter(names=["--data-one"], required = true)
var dataOne: Int? = null
Факт отсутствия исключения не убеждает компилятор Kotlin в наличие
не-null
-значения в свойстве объекта.
Команды #
Приложения с командным интерфейсом зачастую предоставляют ряд связных между собой действий, каждое из которых имеет свой собственный набор аргументов
- Склонировать репозиторий:
git clone <REPOSITORY>
- Создать новую ветку в репозитории
git switch --create <BRANCH>
Названием команды обычно выступает первый позиционный аргумент приложения
Согласно концепции JCommander
- Список аргументов команды описывается в отдельном объекте
- Объекты регистрируются как команды, указывается уникальный идентификатор команды
- Объекты-команды описываются также как объекты-параметры, только регистрируются по-другому
- После обработки аргументов у объекта JCommander можно узнать строковый
идентификатор команды, которая была определена, через свойство
parsedCommand
JCommander с командами #
- Необходимо описать классы для сохранения аргументов для каждой команды
- При создании объекта JCommander необходимо связать название команды с объектами-хранителями аргумента команды
- Во время обработки аргументов первый используется для определения команды
- Данные будут записаны в объект-хранитель соответствующей команды
Описание объекта-хранителя команды #
Описание не отличается от обычного объекта-хранителя:
- Аннотация Parameters используется для описания поведения объекта-хранителя
- Аннотация Parameter используется для описания конкретного аргумента
@Parameters(separators = "=",
commandDescription = "List application entries")
class ListCommand {
@Parameter(names = ["-s", "--size"],
description = "Amount of items to display")
var size: Int = 10
}
Единственным отличием является возможность использования аргумента
commandDescription
у аннотации Parameters, которая позволяет добавить описание
назначения команды
Настройка JCommander для работы с командами #
val commander: JCommander =
JCommander
.newBuilder()
.addCommand("list", listCommand)
.addCommand("build", buildCommand)
.build()
- Создаём объект-строитель путём вызова метода
newBuilder()
- Добавляем список объектов-хранителей аргументов команд
- При добавлении команды указывается её уникальное имя
commander.parse("list", "--size")
val arguments: Array<String> = ...
commander.parse(*arguments)
if (commander.parsedCommand == "list") {
... // Обрабатываем данные внутри listCommand
} else if (commander.parsedCommand == "build") {
... // Обрабатываем данные внутри buildCommand
}
Справка по использованию #
JCommander способен сформировать справку по использованию всех доступных аргументов, их значений по умолчанию и т.д.
Для вывода этой информации на стандартный поток вывода, вызовите метод usage()
на объекте JCommander после добавления к нему объектов и команд
Usage: <main class> [command] [command options]
Commands:
list List application entries
Usage: list [options]
Options:
-s, --size
Amount of items to display
Default: 10
update Update application entry
Usage: update [options]
Options:
* -i, --id
Id of the entry to update
Аргументы, помеченные *
, являются обязательными
Размещение аргументов в файле #
При разработке приложения с большим количеством аргументов надо иметь возможность удобным образом переключаться между разными наборами аргументов
Помимо использования модульных тестов JCommander предоставляет возможность
считывания списка аргументов из файла с помощью @
-синтаксиса
commander.parse("@params.txt")
Из файла params.txt
, который находится в текущем рабочем каталоге:
- Будут считаны все строки
- Строки, начинающиеся с символа
#
, будут проигнорированы - Оставшиеся строки разбиты на слова, будет получен список аргументов
- Внутри данных аргументов могут находиться
@
-аргументы
Данный подход удобнее постоянного редактирования конфигурации запуска Gradle-задачи