java-工具-JCommander


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}" }
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM