Posts JCommander - CLI com parâmetros de forma fácil
Post
Cancel

JCommander - CLI com parâmetros de forma fácil

Aplicações em linha de comando (ou CLI - Command Line Interface) são um tipo de aplicação que nunca morrem, e sempre temos que fazer uma aplicação desse tipo para executar um script, processar um arquivo ou até mesmo subir um servidor, e uma dificuldade geralmente encontrada em aplicações desse tipo são os parâmetros a serem passados para a aplicação que acabam nos obrigado a criar uma estrutura para parsear e interpretar esses parâmetros, gerando um grande problema que as vezes pode levar um tempo para ser resolvido.

Como forma de resolver esse problema vou apresentar a biblioteca JCommander, que auxilia muito na utilização de parâmetros em aplicações linha de comando, onde é possível de forma fácil definir quais os parâmetros a aplicação irá possuir, seus tipos e se são obrigatórios ou não.

Para demonstração vamos criar um projeto Java com o Maven que será uma calculadora bem simples, então vamos ao código:

Vamos criar o projeto jcommander-calc-sample com o Maven e adicionar o JCommander ao pom.xml (estou usando o Eclipse como IDE neste exemplo).

1
2
3
4
5
6
7
<dependencies>
      <dependency>
          <groupId>com.beust</groupId>
          <artifactId>jcommander</artifactId>
          <version>1.72</version>
      </dependency>
</dependencies>

Neste exemplo vamos trabalhar com três parâmetros, sendo eles:

Primeiro valor: tipo Double

Segundo valor: tipo Double

Operação: tipo Enum contendo as operações de soma, subtração, multiplicação e divisão

Vamos criar então a classe contendo a enum de operações:

1
2
3
4
5
6
7
8
package tech.murilo.jcommandercalcsample;

public enum Operation {
    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE;
}

Agora vamos criar uma classe que irá representar os parâmetros lidos na execução por linha de comando. O JCommander usará esta classe para saber quais os parâmetros devem ser lidos e suas configurações e esta classe também irá armazenar os valores inputados:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package tech.murilo.jcommandercalcsample;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;

@Parameters(separators = "=")
public class CalcParameters {
    
    @Parameter(names = "primeiro_valor", description = "Primeiro valor da operação", required = true)
    private double firstValue;
    
    @Parameter(names = "segundo_valor", description = "Segundo valor da operação", required = true)
    private double secondValue;
    
    @Parameter(names = "operacao", description = "Tipo da operação", required = true)
    private Operation operation;
    
    public double getFirstValue() {
        return firstValue;
    }
    
    public double getSecondValue() {
        return secondValue;
    }
    
    public Operation getOperation() {
        return operation;
    }
}

É importante observar que é nesta classe que definimos os nomes dos parâmetros, se são obrigatórios, o separador a ser utilizado e etc.

Com estas classes criadas agora podemos criar a clase Main, que será responsável por executar a aplicação:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package tech.murilo.jcommandercalcsample;

import com.beust.jcommander.JCommander;

public class Main {
    
    public static void main(String[] args) {
        CalcParameters parameters = new CalcParameters();
        
        try {
            JCommander.newBuilder()
                      .addObject(parameters)
                      .build()
                      .parse(args);
            
            executeOperation(parameters);
        } catch (Exception ex) {
            System.out.println("Erro no processamento: " + ex.getMessage());
        }
    }
    
    private static void executeOperation(CalcParameters parameters) {
        double firstValue   = parameters.getFirstValue();
        double secondValue  = parameters.getSecondValue();
        Operation operation = parameters.getOperation();
        
        System.out.println("Primeiro valor: " + firstValue);
        System.out.println("Segundo valor: " + secondValue);
        System.out.println("Operação: " + operation.name());
        
        double result = 0;
        
        switch (operation) {
        case ADD:
            result = firstValue + secondValue;
            break;
        case SUBTRACT:
            result = firstValue - secondValue;
            break;
        case MULTIPLY:
            result = firstValue * secondValue;
            break;
        case DIVIDE:
            result = firstValue / secondValue;
            break;
        }
        
        System.out.println("Resultado: " + result);
    }
}

Como pode ser visto a utilização do JCommander é muito simples, bastando somente chamar seu builder com a instrução JCommander.newBuilder(), adicionar o objeto da classe de parâmetros com o addObject(), chamar o build() e finalizar com o parse(), passando o array de strings que contem os parâmetros de execução via linha de comando, onde o JCommander irá parseá-los de acordo com a classe de configuração.

Sendo assim, podemos gerar um jar executável e rodar um teste para ver o resultado.

Para gerar um jar executável, adicione a seguinte instrução no pom.xml e depois execute o comando mvn package:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>jcommander-calc-sample</finalName>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>tech.murilo.jcommandercalcsample.Main</mainClass>
                    </manifest>
                </archive>

            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
1
2
cd jcommander-calc-sample
mvn package

Assim podemos executar a aplicação:

1
2
3
4
5
$>java -jar target/jcommander-calc-sample.jar primeiro_valor=1 segundo_valor=2 operacao=ADD
Primeiro valor: 1.0
Segundo valor: 2.0
Operação: ADD
Resultado: 3.0

Acredito que você tenha percebido um problema na aplicação com relação a enum Operation, pois apesar do JCommander conseguir interpretar nativamente uma enum, não existe uma validação para verificar se é um valor válido de enum, e ficou bem esquisito chamar a enum pelo nome nos parâmetros, como ADD, SUBTRACT e DIVIDE.

Para resolver esse problema o JCommander permite que criemos um mecanismo para converter um determinado parâmetro para um tipo específico de objeto, facilitando assim a entrada do usuário. Neste exemplo vamos criar esse mecanismo para a enum de operações, melhorando sua utilização.

Para começar vamos incrementar a enum adicionando nomes melhores as operações (em português) e adicionando um método para se obter um valor de enum a partir do nome.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package tech.murilo.jcommandercalcsample;

public enum Operation {
    
    ADD("Somar"),
    SUBTRACT("Subtrair"),
    MULTIPLY("Multiplicar"),
    DIVIDE("Dividir");
    
    private String name;
    
    private Operation(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public static Operation getOperationByName(String name) {
        for (Operation operation : Operation.values()) {
            if (operation.getName().equals(name)) {
                return operation;
            }
        }
        
        throw new IllegalArgumentException("Nome não encontrado na lista de operações: " + name);
    }
}

Agora vamos criar uma classe Converter que será usada pelo JCommander para converter o parâmetro do input para nosso tipo específico. Neste caso o parâmetro será um string contendo o nome da operação e o converter irá converter essa string para um valor da enum usando o método criado anteriormente. Para fazer isso criamos a classe implementando a interface IStringConverter\<T\>.

1
2
3
4
5
6
7
8
9
package tech.murilo.jcommandercalcsample;

import com.beust.jcommander.IStringConverter;

public class OperationEnumConverter implements IStringConverter<Operation> {
    public Operation convert(String name) {
        return Operation.getOperationByName(name);
    }
}

E agora adicionamos esse converter na classe de parâmetros:

1
2
@Parameter(names = "operacao", description = "Tipo da operação", required = true, converter = OperationEnumConverter.class)
private Operation operation;

Por fim alteramos a classe Main para exibir o nome da operação:

1
2
3
System.out.println("Primeiro valor: " + firstValue);
System.out.println("Segundo valor: " + secondValue);
System.out.println("Operação: " + operation.getName());

E pronto, agora podemos gerar o jar novamente e testar usando os nomes definidos na enum.

1
2
3
4
5
$>java -jar target/jcommander-calc-sample.jar primeiro_valor=1 segundo_valor=2 operacao=Somar
Primeiro valor: 1.0
Segundo valor: 2.0
Operação: Somar
Resultado: 3.0

Podemos agora também utilizar um nome de operação que não existe, onde de acordo com o tratamento feito no enum será exibida uma mensagem mais informativa ao usuário:

1
2
$>java -jar target/jcommander-calc-sample.jar primeiro_valor=1 segundo_valor=2 operacao=RaizQuadrada
Erro no processamento: Nome não encontrado na lista de operações: RaizQuadrada

Assim podemos ver como uma biblioteca simples pode trazer uma facilidade enorme na criação de aplicações CLI, evitando a escrita de várias linhas de código para tratamento de parâmetros e focando mais nas lógicas de negócio de sua aplicação.

Para saber mais sobre o JCommander o site.

Projeto completo está no GitHub.

Este post está licenciado sob a CC BY 4.0 pelo autor.