Efetuando parse de opções de linha de comando em Java

Veja nesse artigo como efetuar tratamento de parâmetros de entrada através do frameworks Commons CLI e JCommander.


Figura 1: Commons CLI da Apache


Introdução

O tratamento de parâmetros de linha de comando é uma atividade bastante rotineira em nossa área. Pensando nisso, foram criadas algumas APIs para implementar essa tarefa, facilitando a vida do desenvolvedor. Veremos nesse artigo como usar as APIs Commons CLI e JCommander.

Apache Commons CLI

O Commons CLI é uma biblioteca do grupo Apache, que realiza o parse de opções de entrada passadas ao programa. Essa biblioteca suporta os seguintes estilos de opções de linha:

No processamento da linha de comando, temos 3 estágios: definição, parse e o famoso help/interrogação.

Na fase de definição, determinamos qual estilo será usado (POSIX, GNU, etc), quais parâmetros são suportados, tipo dos parâmetros, se são opcionais ou não, etc. Para definirmos os parâmetros, utilizamos as classes Option e Options.

Na fase de parse, realizamos o tratamento da linha de comando. Nesse estágio, utilizamos as classes CommandLineParser e CommandLine.

Na fase de help/interrogação, fornecemos informações detalhadas ao usuário sobre as opções disponíveis. Para isso usamos a classe HelpFormatter.

O download da biblioteca pode ser feita em: http://commons.apache.org/cli/.

Nota: Nesse artigo usaremos a versão 1.2.

Listagem 1: Classe de Teste para parâmetros no estilo GNU

import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; /** * * @author marcelo */ public class GNUStyleTest { /** * Define as opcoes que o programa aceita * * @return Options */ private static Options defineOptions() { final Options options = new Options(); // Podemos adicionar uma opção através do método addOption options.addOption("d", "debug", false, "Opcao para habilitar modo debug"); // Ou, criando um objeto Option Option option1 = new Option("h", "help", false, "Opcao para exibir help"); options.addOption(option1); // Ou ainda, usando OptionBuilder - Padrão Builder Option option2 = OptionBuilder.withLongOpt("print"). hasArg(true).isRequired().withDescription("Opcao para imprimir um valor").create("p"); options.addOption(option2); return options; } /** * Imprime Help */ private static void printHelp(final Options options) { final String cmdLineSyntax = "java -cp CliTest.jar"; final HelpFormatter helpFormatter = new HelpFormatter(); helpFormatter.printHelp(cmdLineSyntax, options); } /** * Efetua o parse das opções */ private static void parseOptions(final Options options, final String[] args) { final CommandLineParser cmdLineGnuParser = new GnuParser(); CommandLine commandLine; boolean debug; try { commandLine = cmdLineGnuParser.parse(options, args); // Exibir help? if (commandLine.hasOption("h")) { printHelp(options); System.exit(1); } // Modo debug ativo? debug = commandLine.hasOption("d"); // Opção p presente? if (commandLine.hasOption("p")) { if(debug) { System.out.println("Ativando debug..."); } // Recupera valor do parâmetro String argument = commandLine.getOptionValue("p"); // Imprime System.out.println("Argumento = [" + argument + ']'); } } catch (ParseException parseException) { System.err.println(parseException.getMessage()); } } public static void main(String ... args) { final Options options = defineOptions(); if (args.length < 1) { printHelp(options); } else { parseOptions(options, args); } } }

Analisando o código:

Fase de definição

Listagem 2: Definindo as opções

private static Options defineOptions() { final Options options = new Options(); // Podemos adicionar uma opção através do método addOption options.addOption("d", "debug", false, "Opcao para habilitar modo debug"); // Ou, criando um objeto Option Option option1 = new Option("h", "help", false, "Opcao para exibir help"); options.addOption(option1); // Ou ainda, usando OptionBuilder - Padrão Builder Option option2 = OptionBuilder.withLongOpt("print"). hasArg(true).isRequired().withDescription("Opcao para imprimir um valor").create("p"); options.addOption(option2); return options; }

A classe Options representa a lista de todas as opções que a aplicação suporta. Cada opção é definida através da classe Option. Pelo código, verificamos 3 maneiras de se criar um Option:

Listagem 3: Alternativa 1: Através do método addOption da classe Options

options.addOption("d", "debug", false, "Opcao para habilitar modo debug");

No caso, estamos dizendo que o nome curto do parâmetro é d, o nome longo é debug, esse parâmetro não contém argumento (false) e a descrição do parâmetro.

A classe Option possui 3 construtores:

Onde:

Listagem 4: Alternativa 2: Criando um objeto Option e adicionando-o

Option option1 = new Option("h", "help", false, "Opcao para exibir help"); options.addOption(option1);

Ao invés de usar o método addOption passando valores, criamos o objeto Option, e pelo seu construtor configuramos algumas de suas propriedades, e depois o adicionamos ao objeto Options.

Listagem 5: Alternativa 3: Usando o OptionBuilder

Option option2 = OptionBuilder.withLongOpt("print"). hasArg(true).isRequired().withDescription("Opcao para imprimir um valor").create("p"); options.addOption(option2);

Nesse caso, estamos informando que o nome curto do parâmetro é p, o nome longo é print, esse parâmetro requer argumento (hasArg(true)), o campo é obrigatório (isRequired()) e a descrição do parâmetro.

Note que através da classe OptionBuilder, podemos configurar todos os atributos possíveis de Option (obrigatório/não-obrigatório, com/sem argumentos, etc). Através do padrão Builder, utilizamos elegantemente interface fluente para construir/configurar o objeto.

Fase de Interrogação/Help

Listagem 6: Help do programa

/** * Imprime Help */ private static void printHelp(final Options options) { final String cmdLineSyntax = "java -cp CliTest.jar"; final HelpFormatter helpFormatter = new HelpFormatter(); helpFormatter.printHelp(cmdLineSyntax, options); }

Listagem 7: Saída (No caso, ao executar o programa sem passar nenhum parâmetro)

usage: java -cp CliTest.jar -d,--debug Opcao para habilitar modo debug -h,--help Opcao para exibir help -p,--print Opcao para imprimir um valor A classe HelpFormatter imprime todas as opções definidas e contidas no objeto Options, além de imprimir a linha de comando passada como argumento.

Fase de Parse

Listagem 8: Método de Parse no estilo GNU

private static void parseOptions(final Options options, final String[] args) { final CommandLineParser cmdLineGnuParser = new GnuParser(); CommandLine commandLine; boolean debug; try { commandLine = cmdLineGnuParser.parse(options, args); // Exibir help? if (commandLine.hasOption("h")) { printHelp(options); System.exit(1); } // Modo debug ativo? debug = commandLine.hasOption("d"); // Opção p presente? if (commandLine.hasOption("p")) { if(debug) { System.out.println("Ativando debug..."); } // Recupera valor do parâmetro String argument = commandLine.getOptionValue("p"); // Imprime System.out.println("Argumento = [" + argument + ']'); } } catch (ParseException parseException) { System.err.println(parseException.getMessage()); } }

Esse método é responsável por efetuar o parse dos parâmetros da linha de comando e realizar as ações necessárias.

Na linha:

Listagem 9: Definindo tipo de parse

final CommandLineParser cmdLineGnuParser = new GnuParser();

Definimos o tipo de Parser a ser utilizado, no caso GnuParser. Além desse, temos também o PosixParse e o BasicParser. Todos os Parsers implementam a interface CommandLineParser.

Listagem 10: Criando objeto CommandLine

commandLine = cmdLineGnuParser.parse(options, args);

A linha acima cria o objeto CommandLine, que é uma classe de conveniência que dá acesso ao resultado do parse, ou seja, todos os parâmetros e valores associados a eles.

O método parse, entre outras coisas, valida se todos os atributos obrigatórios estão presentes, se os parâmetros com argumento possuem valor, etc. Em caso de erro, ele lança a checked exception org.apache.commons.cli.ParseException.

Listagem 11: Processando parâmetros

// Exibir help? if (commandLine.hasOption("h")) { printHelp(options); System.exit(1); } // Modo debug ativo? debug = commandLine.hasOption("d"); // Opção p presente? if (commandLine.hasOption("p")) { if(debug) { System.out.println("Ativando debug..."); } // Recupera valor do parâmetro String argument = commandLine.getOptionValue("p"); // Imprime System.out.println("Argumento = [" + argument + ']'); }

Através do método hasOption, verificamos se uma opção está presente. E para acessar um valor de uma opção com argumento, utiliza-se o método getOptionValue().

Vejamos agora alguns exemplos de uso:

Listagem 12: Exemplos de uso

// Passando p sem argumento java -jar CliTest.jar --p Missing argument for option: p // Passando p com argumento java -jar CliTest.jar --p=teste Argumento = [teste] // Passando help: Observe que o parâmetro p é obrigatório, por isso o erro java -jar CliTest.jar –h Missing required option: p // Passando help com p java -jar CliTest.jar –h --p=teste usage: java -cp CliTest.jar -d,--debug Opcao para habilitar modo debug -h,--help Opcao para exibir help -p,--print Opcao para imprimir um valor // Passando help com nome longo java -jar CliTest.jar –help --p=teste usage: java -cp CliTest.jar -d,--debug Opcao para habilitar modo debug -h,--help Opcao para exibir help -p,--print Opcao para imprimir um valor

JCommander

Desenvolvido pelo criador do TestNG, Cédric Beust, o JCommander é uma alternativa simples e poderosa para implementar tratamento de parâmetros. Diferente do Commons CLI, nessa solução é usada Annotations para definir os parâmetros de entrada.

Para baixar a biblioteca:

Através do Git: https://github.com/cbeust/jcommander.

Ou via Maven:

<dependency><br/>
<groupId>com.beust</groupId><br/>
<artifactId>jcommander</artifactId><br/>
<version>1.30</version><br/>
</dependency><br/>

Listagem 13: Exemplo 1 de JCommander

import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import java.util.ArrayList; import java.util.List; public class JCommanderTest1 { @Parameter private List<String> parameters = new ArrayList<String>(); @Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity") private Integer verbose = 1; @Parameter(names = "-debug", description = "Debug mode") private boolean debug = false; @Parameter(names = "-print", description = "Print mode") private String print; public static void main(String ... args) { // Teste 1 // O sinal de menos indica que eh parametro String[] argv = { "-log", "2", "-debug", "-print", "Hello World" }; JCommanderTest1 jct = new JCommanderTest1(); new JCommander(jct, argv); System.out.println("Teste 1"); System.out.println("jct.debug = " + jct.debug); System.out.println("jct.verbose = " + jct.verbose); System.out.println("jct.print = " + jct.print); System.out.println(); // Teste 2 // Sem o sinal de menos no log, ele ignora o valor 3 (imprime 1) argv = new String[] { "log", "3"}; jct = new JCommanderTest1(); new JCommander(jct, argv); System.out.println("Teste 2"); System.out.println("jct.verbose = " + jct.verbose); System.out.println(); // Teste 3 // Usando verbose ao invés de log argv = new String[] { "-verbose", "3"}; jct = new JCommanderTest1(); new JCommander(jct, argv); System.out.println("Teste 3"); System.out.println("jct.verbose = " + jct.verbose); System.out.println(); // Teste 4 // Usando debug argv = new String[] { "-debug" }; jct = new JCommanderTest1(); new JCommander(jct, argv); System.out.println("Teste 4"); System.out.println("jct.debug = " + jct.debug); System.out.println(); // Teste 5 // Todos os parametros que não são opções são gravados em parameters argv = new String[] { "-debug", "a", "b", "c" }; jct = new JCommanderTest1(); new JCommander(jct, argv); System.out.println("Teste 5"); System.out.println("jct.parameters = " + jct.parameters); System.out.println(); } }

Listagem 14: Saídas do Exemplo 1 de JCommander

Teste 1 jct.debug = true jct.verbose = 2 jct.print = Hello World Teste 2 jct.verbose = 1 Teste 3 jct.verbose = 3 Teste 4 jct.debug = true Teste 5 jct.parameters = [a, b, c]

A API é realmente muito simples. Basta anotarmos os atributos da classe com a anotação @Parameter, configurando os diversos aspectos do parâmetro, como nome(s), descrição, etc.

Listagem 14: Exemplo 2 de JCommander

import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import java.util.ArrayList; import java.util.List; @Parameters(separators = "=") public class JCommanderTest2 { @Parameter private List<String> parameters = new ArrayList<String>(); @Parameter(names = {"-dir", "--directory"}, description = "Directory") private String dir; @Parameter(names = "-debug", description = "Debug mode", required = true) private boolean debug = false; @Parameter(names = "-age", description = "Age") private Integer age; public static void main(String... args) { // Teste 1 // Usando dir (erro: debug é obrigatório) String[] argv = {"-dir=/etc"}; JCommanderTest2 jct = new JCommanderTest2(); System.out.println("Teste 1"); try { new JCommander(jct, argv); } catch (ParameterException e) { System.out.println(e.getMessage()); } System.out.println("jct.dir = " + jct.dir); System.out.println(); // Teste 2 // Usando dir argv = new String[]{"-dir=/etc", "-debug=true"}; jct = new JCommanderTest2(); System.out.println("Teste 2"); try { new JCommander(jct, argv); } catch (ParameterException e) { System.out.println(e.getMessage()); } System.out.println("jct.dir = " + jct.dir); System.out.println("jct.debug = " + jct.debug); System.out.println(); // Teste 3 // Usando directory argv = new String[]{"--directory=/etc", "-debug=true"}; jct = new JCommanderTest2(); System.out.println("Teste 3"); try { new JCommander(jct, argv); } catch (ParameterException e) { System.out.println(e.getMessage()); } System.out.println("jct.dir = " + jct.dir); System.out.println("jct.debug = " + jct.debug); System.out.println(); // Teste 4 // Passando um valor não-inteiro para age (erro) argv = new String[]{"-age=ABC", "-debug=true"}; jct = new JCommanderTest2(); System.out.println("Teste 4"); try { new JCommander(jct, argv); } catch (ParameterException e) { System.out.println(e.getMessage()); } System.out.println("jct.age = " + jct.age); System.out.println("jct.debug = " + jct.debug); System.out.println(); } }

Listagem 15: Saídas do Exemplo 2 de JCommander

Teste 1 The following option is required: -debug jct.dir = /etc Teste 2 jct.dir = /etc jct.debug = true Teste 3 jct.dir = /etc jct.debug = true Teste 4 "-age": couldn't convert "ABC" to an integer jct.age = null jct.debug = false

Fácil, não? Os exemplos são bem intuitivos e auto-explicativos.

A seguir, um resumo de tudo que a API fornece:

Para ver todos os recursos disponíveis: http://jcommander.org/.

Conclusão

Vimos nesse artigo duas ótimas alternativas para realizar o parse de opções de linha de comando. Apesar de ser menos conhecido que o CLI, o JCommander se mostrou uma ótima opção também (na minha opinião oferece até mais recursos).

Obrigado pessoal, e até a próxima!

Referências:


Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados