Because life is too short to parse command line parameters. From JCommander official site: http://jcommander.org/
以下基本翻译自jcommander官网
JCommander是一个用于解析命令行参数的Java框架,支持解析所有基本的数据类型,也支持将命令行解析成用户自定义的类型,只需要写一个转变函数。
1. JCommander用法
将用来表示参数的fields用@Parameter标识:
import com.beust.jcommander.Parameter;
public class JCommanderExample {
@Parameter
private List<String> parameters = new ArrayList<String>();
@Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
private Integer verbose = 1;
@Parameter(names = "-groups", description = "Comma-separated list of group names to be run")
private String groups;
@Parameter(names = "-debug", description = "Debug mode")
private boolean debug = false;
}
JCommanderExample jct = new JCommanderExample();
String[] argv = { "-log", "2", "-groups", "unit" };
new JCommander(jct, argv);
Assert.assertEquals(jct.verbose.intValue(), 2);
2. 选项类型
Boolean:
@Parameter(names = "-debug", description = "Debug mode", arity = 1)
private boolean debug = true;
boolean类型默认arity=0,这样的话不要用户为参数设置value,只要参数中有-debug就被parse成true,没有就parse成false。但是如果加上 arity=1,则需要用户明确指定value:
program -debug true
program -debug false
对这几种类型,JCommander会尝试parse随后的参数,并上溯成参数的类型,如果参数不能成功上溯则会产生exception
@Parameter(names = "-log", description = "Level of verbosity")
private Integer verbose = 1;
java Main -log 3
//got exception for the following one
java Main -log text
如果类型是List,则这个参数可出现多次
@Parameter(names = "-host", description = "The host")
private List<String> hosts = new ArrayList<String>();
下例中fields hosts包含了两个字符串
java Main -host host1 -verbose -host host2
指明password为true表明这个field是password,我们不想他出现在命令行上,遇到password的参数,用户会被要求在prompt中输入密码
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true)
private String password;
}
当你想让password显示在prompt中时,需要设置echoInput=true,这个setting只有在password=true时才有效
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true, echoInput = true)
private String password;
}
3. 自定义类型
当需要更负责的参数时,比如files,host names,lists等,我们可以写一个转换器(converter)来将参数转换成我们想要的格式或类型。转换器需要实现IStringConverter接口
public interface IStringConverter<T> {
T convert(String value);
}
a. 通过annotation实现:
下面的converter将string转换成file
public class FileConverter implements IStringConverter<File> {
@Override
public File convert(String value) {
return new File(value);
}
}
@Parameter(names = "-file", converter = FileConverter.class)
File file;
在每个parameter annotation中都指定converter有点烦,可以通过factory解决这个问题。
比如你要将下面的参数解析成host和port java App -target example.com:8080
定义一个类来放参数
public class HostPort {
private String host;
private Integer port;
}
class HostPortConverter implements IStringConverter<HostPort> {
@Override
public HostPort convert(String value) {
HostPort result = new HostPort();
String[] s = value.split(":");
result.host = s[0];
result.port = Integer.parseInt(s[1]);
return result;
}
}
写一个Factory,作用是返回converter
public class Factory implements IStringConverterFactory {
public Class<? extends IStringConverter<?>> getConverter(Class forType) {
if (forType.equals(HostPort.class)) return HostPortConverter.class;
else return null;
}
参数类也很简单,参数类型直接用HostPort就好了
public class ArgsConverterFactory {
@Parameter(names = "-hostport")
private HostPort hostPort;
}
没有看到Factory用在什么地方?是在你的程序创建JCommander时传进去的
ArgsConverterFactory a = new ArgsConverterFactory();
JCommander jc = new JCommander(a);
jc.addConverterFactory(new Factory());
jc.parse("-hostport", "example.com:8080");
Assert.assertEquals(a.hostPort.host, "example.com");
Assert.assertEquals(a.hostPort.port.intValue(), 8080);
public interface IParameterValidator {
/**
* Validate the parameter.
*
* @param name The name of the parameter (e.g. "-host").
* @param value The value of the parameter that we need to validate
*
* @throws ParameterException Thrown if the value of the parameter is invalid.
*/
void validate(String name, String value) throws ParameterException;
}
下面这个validator可以验证提供的参数为正整数
public class PositiveInteger implements IParameterValidator {
public void validate(String name, String value)
throws ParameterException {
int n = Integer.parseInt(value);
if (n < 0) {
throw new ParameterException("Parameter " + name + " should be positive (found " + value +")");
}
}
}
在parameter annotation中用validateWith来指定验证器
@Parameter(names = "-age", validateWith = PositiveInteger.class)
private Integer age;
我们可以定义一个main parameter来放所有参数,这个参数不用指定names,必须为List<String>类型
@Parameter(description = "Files")
private List<String> files = new ArrayList<String>();
@Parameter(names = "-debug", description = "Debugging level")
private Integer debug = 1;
run下面命令行,files会保存‘file1’和‘file2’
java Main -debug file1 file2
参数可以是private的
public class ArgsPrivate {
@Parameter(names = "-verbose")
private Integer verbose = 1;
public Integer getVerbose() {
return verbose;
}
}
ArgsPrivate args = new ArgsPrivate();
new JCommander(args, "-verbose", "3");
Assert.assertEquals(args.getVerbose().intValue(), 3);
参数默认用空格分隔,但是我们也可以指定自己的分隔符,比如用“:”, “=”等,java Main -log:3
可以在参数类定义中使用@Parameter annotation指定分隔符
@Parameters(separators = "=")
public class SeparatorEqual {
@Parameter(names = "-level")
private Integer level = 2;
}
8. 多重描述(多个参数类)
你可以将参数放在不同的参数类中,比如定义以下两个含有不同参数的参数类
public class ArgsMaster {
@Parameter(names = "-master")
private String master;
}
public class ArgsSlave {
@Parameter(names = "-slave")
private String slave;
}
使用时将两个类都传入JCommander
ArgsMaster m = new ArgsMaster();
ArgsSlave s = new ArgsSlave();
String[] argv = { "-master", "master", "-slave", "slave" };
new JCommander(new Object[] { m , s }, argv);
Assert.assertEquals(m.master, "master");
Assert.assertEquals(s.slave, "slave");
JCommander支持从文件中获取并解析参数,比如文件/tmp/params中包含了以下内容
-verbose
file1
file2
file3
在命令行中用@指定文件路径即可
java Main @/tmp/parameters
有时候你想为一个参数设置多个值,这时就要用到arity,比如-pairs需要两个values,则设置arity=2
@Parameter(names = "-pairs", arity = 2, description = "Pairs")
private List<String> pairs;
JCommander就会为pairs接收两个values
java Main -pairs slave master
注意想接收多个value的参数需要设置成List<String>类型,boolean类型默认arity为0,String、Integer、int、Long、long默认arity都为1。
可变参数值数量,设置variableArity=true,你可以指定一个参数接收不定数量的values
@Parameter(names = "-foo", variableArity = true)
public List<String> foo = new ArrayList<String>();
下面两个例子一个接收2个参数,一个接收3个参数
program -foo a1 a2 a3 -bar
program -foo a1 -bar
11. 多重选项名
同一个选项(option)可以有不同别名,比如--recursive,可以有一个缩写-r,可以通过给names设置多个值实现
@Parameter(names = { "-d", "--outputDirectory" }, description = "Directory")
private String outputDirectory;
下面两种都可以给outputDirectory赋值
</pre><pre name="code" class="java">java Main -d /tmp
java Main --outputDirectory /tmp
JCommander#setCaseSensitiveOptions(boolean):选项是否大小写敏感,如果设置成true,则‘-option' 和'-OPTION'是不同的选项
JCommander#setAllowAbbreviatedOptions(boolean):打开选项缩写功能,’-op‘可以表示’-option‘选项,如果选项缩写太模糊不能定位到某个选项,则会throw一个ParameterException
13. 必须和可选选项
@Parameter annotation的required设置为true则表示此选项为必须的,否则为可选(默认为可选)
@Parameter(names = "-host", required = true)
private String host;
14. 默认值
一般情况下直接给选项赋值就可以了
private Integer logLevel = 3;
如果有更复杂的情况,比如对某些选项可以再不同的类里重复使用,或者你想把所有的default value都存在同一个地方比如一个XML文件,这些可以通过实现IDefaultProvider接口达成
public interface IDefaultProvider {
/**
* @param optionName The name of the option as specified in the names() attribute
* of the @Parameter option (e.g. "-file").
*
* @return the default value for this option.
*/
String getDefaultValueFor(String optionName);
}
实例
//这是个内部类
private static final IDefaultProvider DEFAULT_PROVIDER = new IDefaultProvider() {
@Override
public String getDefaultValueFor(String optionName) {
return "-debug".equals(optionName) ? "false" : "42";
}
};
// ...
JCommander jc = new JCommander(new Args());
jc.setDefaultProvider(DEFAULT_PROVIDER); //把default provider传入
非常简单
@Parameter(names = "--help", help = true)
private boolean help;
16. 更多复杂的命令语法
想git之类的工具,除了git命令本身需要选项,它还包含一些子命令,如git commit,子命令本身也包含一些选项
git commit --amend -m "Bug fix"
上面的命令中,commit在JCommander中叫做’commands‘, 我们要为这个子命令单独创建一个类
@Parameters(separators = "=", commandDescription = "Record changes to the repository")
private class CommandCommit {
@Parameter(description = "The list of files to commit")
private List<String> files;
@Parameter(names = "--amend", description = "Amend")
private Boolean amend = false;
@Parameter(names = "--author")
private String author;
}
@Parameters(commandDescription = "Add file contents to the index")
public class CommandAdd {
@Parameter(description = "File patterns to add to the index")
private List<String> patterns;
@Parameter(names = "-i")
private Boolean interactive = false;
}
CommandMain cm = new CommandMain();
JCommander jc = new JCommander(cm);
CommandAdd add = new CommandAdd();
jc.addCommand("add", add);
CommandCommit commit = new CommandCommit();
jc.addCommand("commit", commit);
jc.parse("-v", "commit", "--amend", "--author=cbeust", "A.java", "B.java");
Assert.assertTrue(cm.verbose);
Assert.assertEquals(jc.getParsedCommand(), "commit");
Assert.assertTrue(commit.amend);
Assert.assertEquals(commit.author, "cbeust");
Assert.assertEquals(commit.files, Arrays.asList("A.java", "B.java"));
17. Exception
JCommander发现错误时会抛出ParameterExcpetion,这是一个runtime exception
18. usage
可以再JCommander实例中调用usage()方法获取命令行使用方法
19. 隐藏参数
如果不想某些选项出现在usage里,可以隐藏他们
@Parameter(names = "-debug", description = "Debug mode", hidden = true)
private boolean debug = false;
20. 国际化
选项的描述(description)可以国际化,首先在类上使用@Parameter指定消息bundle的名字,然后在选项的@Parameter中使用descriptionKey指定bundle中的key
@Parameters(resourceBundle = "MessageBundle")
private class ArgsI18N2 {
@Parameter(names = "-host", description = "Host", descriptionKey = "host")
String hostName;
}
在MessageBundle中,定义host如下
host:主机
使用@ParameterDelegate可以在主参数类中引用其他的代理参数类
class Delegate {
@Parameter(names = "-port")
private int port;
}
class MainParams {
@Parameter(names = "-v")
private boolean verbose;
@ParametersDelegate
private Delegate delegate = new Delegate();
}
使用时只需在JCommander中指定主参数类
MainParams p = new MainParams();
new JCommander(p).parse("-v", "-port", "1234");
Assert.assertTrue(p.isVerbose);
Assert.assertEquals(p.delegate.port, 1234);
JCommander允许接收没有预先定义的选项,比如’-Da=b -Dc=d‘, Da和Dc没有在参数类中定义,这些参数要用@DynamicParameter annotation标记的类型为Map<String, String>的选项接收,动态参数可以重复多次
@DynamicParameter(names = "-D", description = "Dynamic parameters go here")
private Map<String, String> params = new HashMap<String, String>();
通过使用assignment属性,可以用其他符号代替’=‘作为赋值符号
java Main --outputDirectory /tmp |