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