Обработка аргументов с помощью JCommander

Обработка аргументов с помощью JCommander #

Васильев Андрей Михайлович, 2024

Версии презентации


Аргументы приложения #

  • Приложение при старте может получить некоторый набор строк, аргументов, которые пользователь указал при запуске приложения
  • Приложение может использовать их для настройки собственного поведения
  • Разработчик приложения волен самостоятельно определить формат обработки строк
  • Разумно использовать подходы, которые известны пользователю в результате работы с другими приложениями, сделать приложение «интуитивно понятным»
  • Существует множество библиотек для JVM, позволяющих выполнить обработку передаваемых строк. Рассмотрим JCommander

Официальная документация: https://jcommander.org/

JavaDoc-документация: https://javadoc.io/doc/com.beust/jcommander/latest/index.html

Альтернаитвное Java-решение: https://picocli.info/


Подключение библиотеки #

Для работы с библиотекой, её необходимо добавить в список зависимостей приложения

Maven-строка для подключения: "org.jcommander:jcommander:2.0"

При использовании Gradle без дополнений её необходимо добавить в файл build.gradle.kts в раздел dependencies:

dependencies {
    implementation("org.jcommander:jcommander:2.0")
}

При использовании шаблона лабораторной добавьте строку в файл build.properties.json в список dependencies


Концептуальный подход JCommander #

diagram

  • Необходимо создать и сконфигурировать ключевой объект библиотеки
  • При работе JCommander опирается на мета-информацию, расположенную в аннотациях к свойствам объектов
  • В случае ошибки обработки параметров выбрасывается исключение ParameterException

Создание и вызов JCommander #

val commander: JCommander = 
    JCommander
        .newBuilder()
        .addObject(commonParameters)
        .addCommand("list", listCommand)
        .addCommand("build", buildCommand)
        .build()
  • Создаём объек-строитель путём вызвоа метода newBuilder()
  • Добавляем список объектов, в которые должны быть записаны данные
  • Часть объектов помечаем как команды
  • Завершаем создание путём вызова метода build()
commander.parse("--some", "list", "--size")
val arguments: Array<String> = ...
commander.parse(*arguments)

Обработка аргументов возможна из списка строк, массива строк


Описание объекта для сохранения аргументов #

@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="=" включается обработка аргументов в формате ключ=значение помимо формата ключ значение
  • Конкретные свойства обозначаются для записи с помощью аннотации @Parameters
  • Свойства должны быть изменяемыми
  • Названия ключей для свойств объектов определяется с помощью names
  • Рекомендуется добавлять комментарии, чтобы не забыть назначение свойств и предоставить пользователю содержательные комментарии в случае ошибки

Преобразование строк #

JCommander постарается автоматически преобразовать данные из строкового представления в тип данных свойства класса для: логических полей, целые числа, вещественные числа и строки

Для других сложных классов можно предоставить класс-преобразователь, который должен реализовывать следующий интерфейс:

public interface IStringConverter<T> {
  T convert(String value);
}

Например для UUID такой класс может выглядеть следующим образом:

class UUIDConverter : IStringConverter<UUID> {
    override fun convert(parameter: String): UUID = 
        UUID.fromString(parameter)
}


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

@Parameter(names=["--id"], converter = UUIDConverter::class)
var someId: UUID = UUID(0, 0)
  • Если во время преобразования строки к UUID возникнет ошибка, то будет выброшено исключение IllegalArgumentException
  • Конвертор необходимо указывать для каждого поля в параметрах аннотации Parameter

Обязательные аргументы #

Аннотация Parameter позволяет указать аргумент как обязательный:

@Parameter(names=["--data-one"], required = true)
var dataOne: Int = 0

Согласно документации в случае отсутствия аргумента будет выброшено соответствующее исключение. Однако в данном случае оно не будет выброшено

  1. JCommander получает уже инициализированный объект
  2. JCommander обрабатывает строки аргументов, записывая результаты в свойства объекта
  3. Если после обработки всех строк какие-то поля остались равными null, то будет выброшено исключение

Следовательно надо писать так:

@Parameter(names=["--data-one"], required = true)
var dataOne: Int? = null

Факт отсутствия исключения не убеждает компилятор Kotlin в наличие не-null-значения в свойстве объекта


Команды #

Приложения с командным интерфейсом зачастую предоставляют ряд связных между собой действий, каждое из которых имеет свой собственный набор аргументов

  • Склонировать репозиторий: git clone <REPOSITORY>
  • Создать новую ветку в репозитории git switch --create <BRANCH>

Названием команды обычно выступает первый позиционный аргумент приложения

Согласно концепции JCommander

  • Список аргументов команды описывается в отдельном объекте
  • Объекты регистрируются как команды, указывается уникальный идентификатор команды
  • После обработки аргументов у объекта JCommander можно узнать строковый идентификатор команды, которая была определена, через свойство parsedCommand

Справка по использованию #

JCommander способен сформировать справку по использованию всех доступных аргументов, их значений по умолчанию и т.д.

Для вывода этой информации на стандартный поток вывода, вызовите метод usage() на объекте JCommander после добавления к нему объектов и команд


Размещение аргументов в файле #

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

Помимо использования модульных тестов JCommander предоставляет возможность считывания списка аргументов из файла с помощью @-синтаксиса

commander.parse("@params.txt")

Из файла params.txt, который находится в текущем рабочем каталоге:

  • Будут считаны все строки
  • Строки, начинающиеся с символа #, будут проигнорированы
  • Оставшиеся строки разбиты на слова, будет получен список аргументов
  • Внутри данных аргументов могут находиться @-аргументы

Данный подход удобнее постоянного редактирования конфигурации запуска Gradle-задачи

© A. M. Васильев, 2024, CC BY-SA 4.0, andrey@crafted.su