package edu.stanford.nlp.util;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.annotation.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import edu.stanford.nlp.pipeline.Annotator;
import static edu.stanford.nlp.util.logging.Redwood.Util.*;
/**
* A class to set command line options. To use, create a static class into which you'd like
* to put your properties. Then, for each field, set the annotation:
*
* <pre><code>
* import edu.stanford.nlp.util.ArgumentParser.Option
*
* class Props {
* @Option(name="anIntOption", required=false, gloss="This is an int")
* public static int anIntOption = 7; // default value is 7
* @Option(name="anotherOption", required=false)
* public static File aCastableOption = new File("/foo");
* }
* </code></pre>
*
* <p>
* You can then set options with {@link ArgumentParser#fillOptions(String...)},
* or with {@link ArgumentParser#fillOptions(java.util.Properties)}.
* </p>
*
* <p>
* If your default classpath has many classes in it, you can select a subset of them
* by using {@link ArgumentParser#fillOptions(Class[], java.util.Properties)}, or some variant.
* </p>
*
* <p>
* A complete toy example looks like this:
* </p>
*
* <pre><code>
* import java.util.Properties;
*
* import edu.stanford.nlp.util.ArgumentParser;
* import edu.stanford.nlp.util.StringUtils;
*
* public class Foo {
*
* @ArgumentParser.Option(name="bar", gloss="This is a string option.", required=true)
* private static String BAR = null;
*
* public static void main(String[] args) {
* // Parse the arguments
* Properties props = StringUtils.argsToProperties(args);
* ArgumentParser.fillOptions(new Class[]{ Foo.class, ArgumentParser.class }, props);
*
* log.info(INPUT);
* }
* }
* </code></pre>
*
* @author Gabor Angeli
*/
@SuppressWarnings("HtmlTagCanBeJavadocTag")
public class ArgumentParser {
private ArgumentParser() {} // static class
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Option {
String name() default "";
String gloss() default "";
boolean required() default false;
String alt() default "";
}
@SuppressWarnings("MismatchedReadAndWriteOfArray")
private static final String[] IGNORED_JARS = {
};
private static final Class[] BOOTSTRAP_CLASSES = {
ArgumentParser.class,
};
@Option(name = "option_classes", gloss = "Fill options from these classes")
public static Class<?>[] optionClasses = null;
@Option(name = "threads", gloss = "Number of threads on machine")
public static int threads = Runtime.getRuntime().availableProcessors();
@Option(name = "host", gloss = "Name of computer we are running on")
public static String host = "(unknown)";
@SuppressWarnings("FieldCanBeLocal")
@Option(name = "strict", gloss = "If true, make sure that all options passed in are used somewhere")
private static boolean strict = false;
@SuppressWarnings("FieldCanBeLocal")
@Option(name = "exec.verbose", gloss = "If true, print options as they are set.")
private static boolean verbose = false;
static {
try {
host = InetAddress.getLocalHost().getHostName();
} catch (Exception ignored) {
}
}
/**
* A lazy iterator over files, not loading all of them into memory at once.
*/
public static class LazyFileIterator implements Iterator<File> {
private FilenameFilter filter;
private File[] dir;
private Stack<File[]> parents = new Stack<>();
private Stack<Integer> indices = new Stack<>();
private int toReturn = -1;
public LazyFileIterator(File path, final String filter) {
this(path, (file, name) -> {
String filePath = (file.getPath() + "/" + name);
return new File(filePath).isDirectory() || filePath.matches(filter);
});
}
public LazyFileIterator(File dir, FilenameFilter filter) {
if (!dir.exists()) throw new IllegalArgumentException("Could not find directory: " + dir.getPath());
if (!dir.isDirectory()) throw new IllegalArgumentException("Not a directory: " + dir.getPath());
this.filter = filter;
this.dir = dir.listFiles(filter);
enqueue();
}
private void enqueue() {
toReturn += 1;
boolean good = (toReturn < dir.length && !dir[toReturn].isDirectory());
while (!good) {
if (toReturn >= dir.length) {
//(case: pop)
if (parents.isEmpty()) {
toReturn = -1;
return; //this is where we exit
} else {
dir = parents.pop();
toReturn = indices.pop();
}
} else if (dir[toReturn].isDirectory()) {
//(case: push)
parents.push(dir);
indices.push(toReturn + 1);
dir = dir[toReturn].listFiles(filter);
toReturn = 0;
} else {
throw new IllegalStateException("File is invalid, but in range and not a directory: " + dir[toReturn]);
}
//(check if good)
good = (toReturn < dir.length && !dir[toReturn].isDirectory());
}
// if we reach here we found something
}
@Override
public boolean hasNext() {
return toReturn >= 0;
}
@Override
public File next() {
if (toReturn >= dir.length || toReturn < 0) {
throw new NoSuchElementException("No more elements!");
}
File rtn = dir[toReturn];
enqueue();
return rtn;
}
@Override
public void remove() {
throw new IllegalArgumentException("NOT IMPLEMENTED");
}
}
/*
* ----------
* OPTIONS
* ----------
*/
private static void fillField(Object instance, Field f, String value) {
//--Verbose
if (verbose) {
Option opt = f.getAnnotation(Option.class);
StringBuilder b = new StringBuilder("setting ").append(f.getDeclaringClass().getName()).append("#").append(f.getName()).append(" ");
if (opt != null) {
b.append("[").append(opt.name()).append("] ");
}
b.append("to: ").append(value);
log(b.toString());
}
try {
//--Permissions
boolean accessState = true;
if (Modifier.isFinal(f.getModifiers())) {
runtimeException("Option cannot be final: " + f);
}
if (!f.isAccessible()) {
accessState = false;
f.setAccessible(true);
}
//--Set Value
Object objVal = MetaClass.cast(value, f.getGenericType());
if (objVal != null) {
if (objVal.getClass().isArray()) {
//(case: array)
Object[] array = (Object[]) objVal;
// error check
if (!f.getType().isArray()) {
runtimeException("Setting an array to a non-array field. field: " + f + " value: " + Arrays.toString(array) + " src: " + value);
}
// create specific array
Object toSet = Array.newInstance(f.getType().getComponentType(), array.length);
for (int i = 0; i < array.length; i++) {
Array.set(toSet, i, array[i]);
}
// set value
f.set(instance, toSet);
} else {
//case: not array
f.set(instance, objVal);
}
} else {
runtimeException("Cannot assign option field: " + f + " value: " + value + "; invalid type");
}
//--Permissions
if (!accessState) {
f.setAccessible(false);
}
} catch (IllegalArgumentException e) {
err(e);
runtimeException("Cannot assign option field: " + f.getDeclaringClass().getCanonicalName() + "." + f.getName() + " value: " + value + " cause: " + e.getMessage());
} catch (IllegalAccessException e) {
err(e);
runtimeException("Cannot access option field: " + f.getDeclaringClass().getCanonicalName() + "." + f.getName());
} catch (Exception e) {
err(e);
runtimeException("Cannot assign option field: " + f.getDeclaringClass().getCanonicalName() + "." + f.getName() + " value: " + value + " cause: " + e.getMessage());
}
}
@SuppressWarnings("rawtypes")
private static Class filePathToClass(String cpEntry, String path) {
if (path.length() <= cpEntry.length()) {
throw new IllegalArgumentException("Illegal path: cp=" + cpEntry
+ " path=" + path);
}
if (path.charAt(cpEntry.length()) != '/') {
throw new IllegalArgumentException("Illegal path: cp=" + cpEntry
+ " path=" + path);
}
path = path.substring(cpEntry.length() + 1);
path = path.replaceAll("/", ".").substring(0, path.length() - 6);
try {
return Class.forName(path,
false,
ClassLoader.getSystemClassLoader());
} catch (ClassNotFoundException e) {
throw fail("Could not load class at path: " + path);
} catch (NoClassDefFoundError ex) {
warn("Class at path " + path + " is unloadable");
return null;
}
}
private static boolean isIgnored(String path) {
for (String ignore : IGNORED_JARS) {
if (path.endsWith(ignore)) {
return true;
}
}
return false;
}
private static Class<?>[] getVisibleClasses() {
//--Variables
List<Class<?>> classes = new ArrayList<>();
// (get classpath)
String pathSep = System.getProperty("path.separator");
String[] cp = System.getProperties().getProperty("java.class.path",
null).split(pathSep);
// --Fill Options
// (get classes)
for (String entry : cp) {
log("Checking cp " + entry);
//(should skip?)
if (entry.equals(".") || entry.trim().length() == 0) {
continue;
}
//(no, don't skip)
File f = new File(entry);
if (f.isDirectory()) {
// --Case: Files
LazyFileIterator iter = new LazyFileIterator(f, ".*class$");
while (iter.hasNext()) {
//(get the associated class)
Class<?> clazz = filePathToClass(entry, iter.next().getPath());
if (clazz != null) {
//(add the class if it's valid)
classes.add(clazz);
}
}
} else //noinspection StatementWithEmptyBody
if (!isIgnored(entry)) {
// --Case: Jar
try {
JarFile jar = new JarFile(f);
Enumeration<JarEntry> e = jar.entries();
while (e.hasMoreElements()) {
//(for each jar file element)
JarEntry jarEntry = e.nextElement();
String clazz = jarEntry.getName();
if (clazz.matches(".*class$")) {
//(if it's a class)
clazz = clazz.substring(0, clazz.length() - 6)
.replaceAll("/", ".");
//(add it)
try {
classes.add(
Class.forName(clazz,
false,
ClassLoader.getSystemClassLoader()));
} catch (ClassNotFoundException ex) {
warn("Could not load class in jar: " + f + " at path: " + clazz);
} catch (NoClassDefFoundError ex) {
debug("Could not scan class: " + clazz + " (in jar: " + f + ")");
}
}
}
} catch (IOException e) {
warn("Could not open jar file: " + f + "(are you sure the file exists?)");
}
} else {
//case: ignored jar
}
}
return classes.toArray(new Class<?>[classes.size()]);
}
/**
* Get all the declared fields of this class and all super classes.
*/
private static Field[] scrapeFields(Class<?> clazz) throws Exception {
List<Field> fields = new ArrayList<>();
while (clazz != null && !clazz.equals(Object.class)) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return fields.toArray(new Field[fields.size()]);
}
private static String threadRootClass() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
int i = trace.length - 1;
while(i > 0 &&
(trace[i].getClassName().startsWith("com.intellij") ||
trace[i].getClassName().startsWith("java.") ||
trace[i].getClassName().startsWith("sun.")
) ) {
i -= 1;
}
StackTraceElement elem = trace[i];
return elem.getClassName();
}
private static String bufferString(String raw, int minLength) {
StringBuilder b = new StringBuilder(raw);
for (int i = raw.length(); i < minLength; ++i) {
b.append(" ");
}
return b.toString();
}
@SuppressWarnings("rawtypes")
private static Map<String, Field> fillOptionsImpl(
Object[] instances,
Class<?>[] classes,
Properties options,
boolean ensureAllOptions,
boolean isBootstrap) {
// Print usage, if applicable
if (!isBootstrap) {
if ("true".equalsIgnoreCase(options.getProperty("usage", "false")) ||
"true".equalsIgnoreCase(options.getProperty("help", "false"))
) {
Set<Class<?>> allClasses = new HashSet<>();
Collections.addAll(allClasses, classes);
if (instances != null) {
for (Object o : instances) {
allClasses.add(o.getClass());
}
}
System.err.println(usage(allClasses.stream().toArray(Class[]::new)));
System.exit(0);
}
}
//--Create Class->Object Mapping
Map<Class, Object> class2object = new HashMap<>();
if (instances != null) {
for (int i = 0; i < classes.length; ++i) {
assert instances[i].getClass() == classes[i];
class2object.put(classes[i], instances[i]);
Class<?> mySuper = instances[i].getClass().getSuperclass();
while (mySuper != null && !mySuper.equals(Object.class)) {
if (!class2object.containsKey(mySuper)) {
class2object.put(mySuper, instances[i]);
}
mySuper = mySuper.getSuperclass();
}
}
}
//--Get Fillable Options
Map<String, Field> canFill = new HashMap<>();
Map<String, Pair<Boolean, Boolean>> required = new HashMap<>(); /* <exists, is_set> */
Map<String, String> interner = new HashMap<>();
for (Class c : classes) {
Field[] fields;
try {
fields = scrapeFields(c);
} catch (Throwable e) {
debug("Could not check fields for class: " + c.getName() + " (caused by " + e.getClass() + ": " + e.getMessage() + ")");
continue;
}
boolean someOptionFilled = false;
boolean someOptionFound = false;
for (Field f : fields) {
Option o = f.getAnnotation(Option.class);
if (o != null) {
someOptionFound = true;
//(check if field is static)
if ((f.getModifiers() & Modifier.STATIC) == 0 && instances == null) {
continue;
}
someOptionFilled = true;
//(required marker)
Pair<Boolean, Boolean> mark = Pair.makePair(false, false);
if (o.required()) {
mark = Pair.makePair(true, false);
}
//(add main name)
String name = o.name().toLowerCase();
if (name.equals("")) {
name = f.getName().toLowerCase();
}
if (canFill.containsKey(name)) {
String name1 = canFill.get(name).getDeclaringClass().getCanonicalName() + "." + canFill.get(name).getName();
String name2 = f.getDeclaringClass().getCanonicalName() + "." + f.getName();
if (!name1.equals(name2)) {
runtimeException("Multiple declarations of option " + name + ": " + name1 + " and " + name2);
} else {
err("Class is in classpath multiple times: " + canFill.get(name).getDeclaringClass().getCanonicalName());
}
}
canFill.put(name, f);
required.put(name, mark);
interner.put(name, name);
//(add alternate names)
if (!o.alt().equals("")) {
for (String alt : o.alt().split(" *, *")) {
alt = alt.toLowerCase();
if (canFill.containsKey(alt) && !alt.equals(name))
throw new IllegalArgumentException("Multiple declarations of option " + alt + ": " + canFill.get(alt) + " and " + f);
canFill.put(alt, f);
if (mark.first) required.put(alt, mark);
interner.put(alt, name);
}
}
}
}
//(check to ensure that something got filled, if any @Option annotation was found)
if (someOptionFound && !someOptionFilled) {
warn("found @Option annotations in class " + c + ", but didn't set any of them (all options were instance variables and no instance given?)");
}
}
//--Fill Options
for (Map.Entry<Object, Object> entry : options.entrySet()) {
String rawKeyStr = entry.getKey().toString();
String key = rawKeyStr.toLowerCase();
// (get values)
String value = entry.getValue().toString();
assert value != null;
Field target = canFill.get(key);
// (mark required option as fulfilled)
Pair<Boolean, Boolean> mark = required.get(key);
if (mark != null && mark.first) {
required.put(key, Pair.makePair(true, true));
}
// (fill the field)
if (target != null) {
// (case: declared option)
fillField(class2object.get(target.getDeclaringClass()), target, value);
} else if (ensureAllOptions) {
// (case: undeclared option)
// split the key
int lastDotIndex = rawKeyStr.lastIndexOf('.');
if (lastDotIndex < 0) {
err("Unrecognized option: " + key);
continue;
}
if (!rawKeyStr.startsWith("log.")) { // ignore Redwood options
String className = rawKeyStr.substring(0, lastDotIndex);
String fieldName = rawKeyStr.substring(lastDotIndex + 1);
// get the class
Class clazz = null;
try {
clazz = ClassLoader.getSystemClassLoader().loadClass(className);
} catch (Exception e) {
err("Could not set option: " + entry.getKey() + "; either the option is mistyped, not defined, or the class " + className + " does not exist.");
}
// get the field
if (clazz != null) {
try {
target = clazz.getField(fieldName);
} catch (Exception e) {
err("Could not set option: " + entry.getKey() + "; no such field: " + fieldName + " in class: " + className);
}
if (target != null) {
log("option overrides " + target + " to '" + value + "'");
fillField(class2object.get(target.getDeclaringClass()), target, value);
} else {
err("Could not set option: " + entry.getKey() + "; no such field: " + fieldName + " in class: " + className);
}
}
}
}
}
//--Ensure Required
boolean good = true;
for (Map.Entry<String, Pair<Boolean, Boolean>> entry : required.entrySet()) {
String key = entry.getKey();
Pair<Boolean, Boolean> mark = entry.getValue();
if (mark.first && !mark.second) {
err("Missing required option: " + interner.get(key) + " <in class: " + canFill.get(key).getDeclaringClass() + ">");
required.put(key, Pair.makePair(true, true)); //don't duplicate error messages
good = false;
}
}
if ( ! good) {
throw new RuntimeException("Not able to parse properties!!!");
//System.exit(1);
}
return canFill;
}
private static Map<String, Field> fillOptionsImpl(
Object[] instances,
Class<?>[] classes,
Properties options) {
return fillOptionsImpl(instances, classes, options, strict, false);
}
/*
* ----------
* EXECUTION
* ----------
*/
/**
* Populate all static options in the given set of classes, as defined by the given
* properties.
*
* @param classes The classes to populate static {@link Option}-tagged fields in.
* @param options The properties to use to fill these fields.
*/
public static void fillOptions(Class<?>[] classes, Properties options) {
fillOptionsImpl(null, classes, options);
}
/**
* Populate with the given properties all static {@link Option}-tagged fields in
* the given classes.
* Then, populate remaining fields with the given command-line arguments.
*
* @param optionClasses The classes to populate static {@link Option}-tagged fields in.
* @param props The properties to use to fill these fields.
* @param args The command-line arguments to use to fill in additional properties.
*/
@SuppressWarnings("UnusedDeclaration")
public static void fillOptions(Class<?>[] optionClasses, Properties props, String... args) {
ArgumentParser.optionClasses = optionClasses;
fillOptions(props, args);
}
/**
* Populate with the given command-line arguments all static {@link Option}-tagged fields in
* the given classes.
*
* @param classes The classes to populate static {@link Option}-tagged fields in.
* @param args The command-line arguments to use to fill in additional properties.
*/
public static void fillOptions(Class<?>[] classes,
String... args) {
Properties options = StringUtils.argsToProperties(args); //get options
fillOptionsImpl(null, BOOTSTRAP_CLASSES, options, false, true); //bootstrap
fillOptionsImpl(null, classes, options);
}
/**
* Populate all static options in the given class, as defined by the given
* properties.
*
* @param clazz The class to populate static {@link Option}-tagged fields in.
* @param options The properties to use to fill these fields.
*/
public static void fillOptions(Class<?> clazz, Properties options) {
fillOptionsImpl(null, new Class[]{ clazz }, options);
}
/**
* Populate all static options in the given class, as defined by the given
* command-line arguments.
*
* @param clazz The class to populate static {@link Option}-tagged fields in.
* @param args The command-line arguments to use to fill these fields.
*/
public static void fillOptions(Class<?> clazz,
String... args) {
Class<?>[] classes = new Class<?>[1];
classes[0] = clazz;
fillOptions(classes, args);
}
/**
* Populate with the given properties all static options in all classes in the current classpath.
* Note that this may take a while if the classpath is large.
*
* @param props The properties to use to fill fields in the various classes.
*/
public static void fillOptions(Properties props) {
fillOptions(props, StringUtils.EMPTY_STRING_ARRAY);
}
/**
* Populate with the given command-line arguments all static options in all
* classes in the current classpath.
* Note that this may take a while if the classpath is large.
*
* @param args The command-line arguments to use to fill options.
*/
public static void fillOptions(String... args) {
fillOptions(StringUtils.argsToProperties(args), StringUtils.EMPTY_STRING_ARRAY);
}
/**
* Populate with the given properties all static options in all classes in the current classpath.
* Then, fill in additional properties from the given command-line arguments.
* Note that this may take a while if the classpath is large.
*
* @param props The properties to use to fill fields in the various classes.
* @param args The command-line arguments to use to fill in additional properties.
*/
public static void fillOptions(Properties props, String... args) {
// todo [cdm 2016]: Isn't this the wrong way round and properties overwrite commandline options? Should be the opposite!
//(convert to map)
Properties options = StringUtils.argsToProperties(args);
for (String key : props.stringPropertyNames()) {
options.setProperty(key, props.getProperty(key));
}
//(bootstrap)
Map<String, Field> bootstrapMap = fillOptionsImpl(null, BOOTSTRAP_CLASSES, options, false, true); //bootstrap
bootstrapMap.keySet().forEach(options::remove);
//(fill options)
Class<?>[] visibleClasses = optionClasses;
if (visibleClasses == null) visibleClasses = getVisibleClasses(); //get classes
fillOptionsImpl(null, visibleClasses, options);//fill
}
/**
* Fill all non-static {@link Option}-tagged fields in the given set of objects with the given
* properties.
*
* @param instances The object instances containing {@link Option}-tagged fields which we should fill.
* @param options The properties to use to fill these fields.
*/
public static void fillOptions(Object[] instances, Properties options) {
Class[] classes = new Class[instances.length];
for (int i = 0; i < classes.length; ++i) { classes[i] = instances[i].getClass(); }
fillOptionsImpl(instances, classes, options);
}
/**
* Fill all non-static {@link Option}-tagged fields in the given set of objects with the given
* command-line arguments.
*
* @param instances The object instances containing {@link Option}-tagged fields which we should fill.
* @param args The command-line arguments to use to fill these fields.
*/
public static void fillOptions(Object[] instances,
String[] args) {
Properties options = StringUtils.argsToProperties(args); //get options
fillOptionsImpl(null, BOOTSTRAP_CLASSES, options, false, true); //bootstrap
Class[] classes = new Class[instances.length];
for (int i = 0; i < classes.length; ++i) { classes[i] = instances[i].getClass(); }
fillOptionsImpl(instances, classes, options);
}
/**
* Fill all non-static {@link Option}-tagged fields in the given object with the given
* properties.
*
* @param instance The object instance containing {@link Option}-tagged fields which we should fill.
* @param options The properties to use to fill these fields.
*/
public static void fillOptions(Object instance, Properties options) {
fillOptions(new Object[]{ instance }, options);
}
/**
* Fill all non-static {@link Option}-tagged fields in the given object with the given
* command-line arguments.
*
* @param instance The object instance containing {@link Option}-tagged fields which we should fill.
* @param args The command-line arguments to use to fill these fields.
*/
public static void fillOptions(Object instance,
String[] args) {
fillOptions(new Object[]{ instance }, args);
}
/**
* Fill all the options for a given CoreNLP annotator.
* This assumes that the annotator takes properties with a prefix, so that, for example,
* if the annotator is {@code parse} then it takes a property {@code parse.maxlen} for instance.
*
* @param annotator The annotator to fill options for.
* @param annotatorName The name of the annotator, for parsing properties.
* @param props The properties to fill the options in the annotator with.
*/
public static void fillOptions(Annotator annotator, String annotatorName, Properties props) {
ArgumentParser.fillOptions(annotator, props);
Properties withoutPrefix = new Properties();
String prefixString = annotatorName + '.';
for (Map.Entry entry : props.entrySet()) {
String key = entry.getKey().toString();
withoutPrefix.setProperty(key.replace(prefixString, ""), entry.getValue().toString());
}
ArgumentParser.fillOptions(annotator, withoutPrefix);
}
/**
* Return a string describing the usage of the program this method is called from, given the
* options declared in the given set of classes.
* This will print both the static options, and the non-static options.
*
* @param optionsClasses The classes defining the options being used by this program.
* @return A String describing the usage of the class.
*/
public static String usage(Class[] optionsClasses) {
String mainClass = threadRootClass();
StringBuilder b = new StringBuilder();
b.append("Usage: ").append(mainClass).append(' ');
List<Pair<Option, Field>> options = new ArrayList<>();
for (Class clazz : optionsClasses) {
try {
options.addAll(Arrays.stream(scrapeFields(clazz))
.map(field -> {
Annotation[] annotations = field.getAnnotationsByType(Option.class);
if (annotations.length > 0) {
return Pair.makePair((Option) annotations[0], field);
} else {
return null;
}
})
.filter(x -> x != null)
.collect(Collectors.toList()));
} catch (Exception e) {
return b.append("<unknown>").toString();
}
}
int longestOptionName = options.stream().map(x -> x.first.name().length()).max(Comparator.comparingInt(x -> x)).orElse(10);
int longestOptionType = options.stream().map(x -> x.second.getType().getSimpleName().length()).max(Comparator.comparingInt(x -> x)).orElse(10) + 1;
options.stream().filter(x -> x.first.required()).forEach(optionPair -> {
Option option = optionPair.first;
Field field = optionPair.second;
b.append("\n\t-").append(bufferString(option.name(), longestOptionName))
.append(" <").append(bufferString(field.getType().getSimpleName() + ">", longestOptionType))
.append(" [required] ")
.append(option.gloss());
});
options.stream().filter(x -> !x.first.required()).forEach(optionPair -> {
Option option = optionPair.first;
Field field = optionPair.second;
b.append("\n\t-").append(bufferString(option.name(), longestOptionName))
.append(" <").append(bufferString(field.getType().getSimpleName() + ">", longestOptionType))
.append(" ")
.append(option.gloss());
});
return b.toString();
}
/**
* Return a string describing the usage of the program this method is called from, given the
* options declared in the given set of objects.
* This will print both the static options, and the non-static options.
*
* @param optionsClasses The objects defining the options being used by this program.
* @return A String describing the usage of the class.
*/
public static String usage(Object[] optionsClasses) {
return usage(Arrays.stream(optionsClasses).map(Object::getClass).toArray(Class[]::new));
}
/**
* Return a string describing the usage of the program this method is called from, given the
* options declared in the given class.
* This will print both the static options, and the non-static options.
*
* @param optionsClass The class defining the options being used by this program.
* @return A String describing the usage of the class.
*/
public static String usage(Class<?> optionsClass) {
return usage(new Class[]{ optionsClass });
}
/**
* Return a string describing the usage of the program this method is called from, given the
* options declared in the given object.
* This will print both the static options, and the non-static options.
*
* @param optionsClass The object defining the options being used by this program.
* @return A String describing the usage of the class.
*/
public static String usage(Object optionsClass) {
return usage(new Class[]{ optionsClass.getClass() });
}
}