JCommander
一. 使用例子
非常小的java框架,用于解析命令行参数
注解描述符例子
import com.beust.jcommander.Parameter;
public class JCommanderExample {
@Parameter
private List<String> parameters = new ArrayList<>();
@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;
}
使用JCommander解析
JCommanderExample jct = new JCommanderExample();
String[] argv = { "-log", "2", "-groups", "unit" };
new JCommander(jct, argv);
Assert.assertEquals(jct.verbose.intValue(), 2);
实际观察到的结果入下
class Main {
@Parameter(names={"--length", "-l"})
int length;
@Parameter(names={"--pattern", "-p"})
int pattern;
public static void main(String ... args) {
Main main = new Main();
new JCommander(main, args);
main.run();
}
public void run() {
System.out.printf("%d %d", length, pattern);
}
}
二.支持的类型
Boolean
可以设置默认值,不传这个参数使用默认值
@Parameter(names = "-debug", description = "Debug mode")
private boolean debug = false;
String, Integer, Long
这三种类型,JCommander会将它们转化为相应的类型
Lists
list类型,可以使用很多次标记传入
@Parameter(names = "-host", description = "The host")
private List<String> hosts = new ArrayList<>();
使用
java Main -host host1 -verbose -host host2
Password
密码之类不做记录的参数,设置为password类型,JCommander会让你在终端中填入
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true)
private String password;
}
使用例子
Value for -password (Connection password):
显示输入
设置echoInput
为true
public class ArgsPassword {
@Parameter(names = "-password", description = "Connection password", password = true, echoInput = true)
private String password;
}
三. 定制类型
注解
需要文件、主机名、等参数时需要定制
需要实现IStringConverter
接口写一个类型转换器
public interface IStringConverter<T> {
T convert(String value);
}
string转文件
public class FileConverter implements IStringConverter<File> {
@Override
public File convert(String value) {
return new File(value);
}
}
然后定义转换的类型即可
@Parameter(names = "-file", converter = FileConverter.class)
File file;
工厂方法
实现接口
public interface IStringConverterFactory {
<T> Class<? extends IStringConverter<T>> getConverter(Class<T> forType);
}
比如转换host和port:java App -target example.com:8080
定义holder类
public class HostPort {
private String host;
private Integer port;
}
定义string converter
在类中创建实例
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;
}
}
工厂如下
public class Factory implements IStringConverterFactory {
public Class<? extends IStringConverter<?>> getConverter(Class forType) {
if (forType.equals(HostPort.class)) return HostPortConverter.class;
else return null;
}
可以直接使用,不增加额外的参数
public class ArgsConverterFactory {
@Parameter(names = "-hostport")
private HostPort hostPort;
}
只需要在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);
四. 参数校验
两种方式:
- 全局
- 局部
局部校验
实现接口IParameterValidator可以做早期校验
public interface IParameterValidator {
void validate(String name, String value) throws ParameterException;
}
验证数为正数的例子
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 +")");
}
}
}
在注解中,在validateWith中指定实现接口的类名
@Parameter(names = "-age", validateWith = PositiveInteger.class)
private Integer age;
如果校验不成功会抛异常
全局校验
JCommander解析后,需要额外的校验,比如校验两个互斥参数;
java实现非常局限,JCommander不提供解决方案
五. Main参数
目前参数都通过names属性定义,可以定义一个没有这个属性的参数,这样的参数需要List类型,它会包含所有没有包含的参数,比如
@Parameter(description = "Files")
private List<String> files = new ArrayList<>();
@Parameter(names = "-debug", description = "Debugging level")
private Integer debug = 1;
传入参数
java Main -debug file1 file2
由于file1不能解析为Integer,因此files会存进file1和file2
六. private参数
参数可以设置为私有,这样提供get方法
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
java Main -level=42
在参数里配置分隔符
@Parameters(separators = "=")
public class SeparatorEqual {
@Parameter(names = "-level")
private Integer level = 2;
}
八. 多种描述
可以把描述参数分配到多个类中,比如
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");
九. @语法
@语法可以把参数放到文件中,然后通过参数把文件传递过去
/tmp/parameters
文件如下
-verbose
file1
file2
file3
传递参数
java Main @/tmp/parameters
十. 多值参数
固定
如果参数为多个值
java Main -pairs slave master foo.xml
这时,参数需要存入list,并且制定arity
@Parameter(names = "-pairs", arity = 2, description = "Pairs")
private List<String> pairs;
只有list支持多值
可变
参数的数量不确定
program -foo a1 a2 a3 -bar
program -foo a1 -bar
这样的参数,类型为list,并且设置variableArity为true
@Parameter(names = "-foo", variableArity = true)
public List<String> foo = new ArrayList<>();
十一. 多个参数名
@Parameter(names = { "-d", "--outputDirectory" }, description = "Directory")
private String outputDirectory;
使用定义中的参数名都可以
java Main -d /tmp
java Main --outputDirectory /tmp
十二. 其他参数配置
参数的查找方式
JCommander.setCaseSensitiveOptions(boolean) //是否大小写敏感
JCommander.setAllowAbbreviatedOptions(boolean)//参数是否可以简写
简写参数时,比如-param
可用参数-par
替代,如果出错会抛异常
十三. 是否必填
required=true,为必填,如果空,抛异常
@Parameter(names = "-host", required = true)
private String host;
十四. 默认值
定义时,可以设置默认值
也可以使用IDefaultProvider,把配置放在xml或.properties中
public interface IDefaultProvider {
String getDefaultValueFor(String optionName);
}
下面是一个默认的IDefaultProvider,将所有的-debug
设置为42
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);
十五. help参数
显示用法和帮助信息
@Parameter(names = "--help", help = true)
private boolean help;
十六. 更复杂的语法
复杂的工具比如git,svn包含一系列的命令,具有自己的语法
比如
git commit --amend -m "Bug fix"
上述commit关键字在JCommander中叫做commands
,可以为每个命令设置一个arg对象
@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;
}
然后注册进JCommander,在JCommander对象上调用getParsedCommand()
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"));
十七. 异常
捕获到任何错误,都会抛ParameterException,都是运行时异常
十八. 用法
在JCommander实例上调用,usage()方法,显示所有的使用方法
Usage: <main class> [options]
Options:
-debug Debug mode (default: false)
-groups Comma-separated list of group names to be run
* -log, -verbose Level of verbosity (default: 1)
-long A long number (default: 0)
十九. 隐藏参数
不想让参数在help中显示
@Parameter(names = "-debug", description = "Debug mode", hidden = true)
private boolean debug = false;
二十. 国际化
可以国家化描述
使用@Parameters
注解,定义名字,然后使用descriptionKey 属性,而不是description
@Parameters(resourceBundle = "MessageBundle")
private class ArgsI18N2 {
@Parameter(names = "-host", description = "Host", descriptionKey = "host")
String hostName;
}
JCommander会使用默认的locale解析描述
二一. 参数delegates
如果在同一project中写了很多工具,工具之间可以共享配置,可以继承对象,避免重复代码;
单继承可能会限制灵活性,JCommander支持参数delegates
@ParameterDelegate
注解
class Delegate {
@Parameter(names = "-port")
private int port;
}
class MainParams {
@Parameter(names = "-v")
private boolean verbose;
@ParametersDelegate
private Delegate delegate = new Delegate();
}
只需要添加MainParams对象到JCommander配置中,就能够使用这个delegate
MainParams p = new MainParams();
new JCommander(p).parse("-v", "-port", "1234");
Assert.assertTrue(p.isVerbose);
Assert.assertEquals(p.delegate.port, 1234);
二二. 动态参数
允许使用编译时不知道的参数,比如-Da=b -Dc=d
,必须是Map<String, String>类型,可以在命令行中出现多次
@DynamicParameter(names = "-D", description = "Dynamic parameters go here")
private Map<String, String> params = new HashMap<>();
也可以使用除=的分配符
二三. scala中使用
import java.io.File
import com.beust.jcommander.{JCommander, Parameter}
import collection.JavaConversions._
object Main {
object Args {
// Declared as var because JCommander assigns a new collection declared
// as java.util.List because that's what JCommander will replace it with.
// It'd be nice if JCommander would just use the provided List so this
// could be a val and a Scala LinkedList.
@Parameter(
names = Array("-f", "--file"),
description = "File to load. Can be specified multiple times.")
var file: java.util.List[String] = null
}
def main(args: Array[String]): Unit = {
new JCommander(Args, args.toArray: _*)
for (filename <- Args.file) {
val f = new File(filename)
printf("file: %s\n", f.getName)
}
}
}
二四. groovy中使用
import com.beust.jcommander.*
class Args {
@Parameter(names = ["-f", "--file"], description = "File to load. Can be specified multiple times.")
List<String> file
}
new Args().with {
new JCommander(it, args)
file.each { println "file: ${new File(it).name}" }
}