org.bitbucket.bradleysmithllc.java_cl_parser.CommonsCLILauncher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-cl-parser Show documentation
Show all versions of java-cl-parser Show documentation
This parser strives to achieve an elegant, ioc-type interface to make launching command-line projects
effortless.
package org.bitbucket.bradleysmithllc.java_cl_parser;
import org.apache.commons.cli.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.bitbucket.bradleysmithllc.java_cl_parser.regexp.EscapeFunctionExpression;
import org.bitbucket.bradleysmithllc.java_cl_parser.util.RecursiveMap;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;
import java.util.regex.Pattern;
public class CommonsCLILauncher
{
public static final String HELP_INVALID_USAGE_MESSAGE = "Help is not valid in combination with any other option";
public static final String HELP_USAGE_MESSAGE = "Help requested";
public static final String HELP_REQUIRED_MESSAGE = "Help required";
private static final String [] NULL_ARGS = new String[]{};
enum data_type
{
bool,
Bool,
long_integer,
Long,
integer,
Integer,
string,
String,
Object
}
enum cardinal_type
{
instance,
array,
flag
}
static final class Data
{
data_type type = data_type.bool;
cardinal_type cardinality = null;
Method setter;
CLIOption clioption;
}
private static final Map reservedOptionNames = new HashMap();
static
{
reservedOptionNames.put("?", "?");
reservedOptionNames.put("help", "help");
reservedOptionNames.put("verbose", "verbose");
reservedOptionNames.put("v", "v");
}
private static final CLIOption help = new CLIOption(){
public String name()
{
return "?";
}
public String longName()
{
return "help";
}
public String description()
{
return "Prints this usage description.";
}
public boolean required()
{
return false;
}
public value_type valueType()
{
return value_type.not_allowed;
}
public int valueCardinality()
{
return 0;
}
public char valueSeparator()
{
return CLIOption.ARRAY_SEPARATOR_DEFAULT;
}
public String defaultValue()
{
return CLIOption.NULL_STRING_VALUE;
}
public String[] enumeratedValues()
{
return new String[0];
}
public Class extends Annotation> annotationType()
{
return CLIEntry.class;
}
};
private static final CLIOption verbose = new CLIOption(){
public String name()
{
return "v";
}
public String longName()
{
return "verbose";
}
public String description()
{
return "Displays the command line options.";
}
public boolean required()
{
return false;
}
public value_type valueType()
{
return value_type.not_allowed;
}
public int valueCardinality()
{
return 0;
}
public char valueSeparator()
{
return CLIOption.ARRAY_SEPARATOR_DEFAULT;
}
public String defaultValue()
{
return CLIOption.NULL_STRING_VALUE;
}
public String[] enumeratedValues()
{
return new String[0];
}
public Class extends Annotation> annotationType()
{
return CLIEntry.class;
}
};
private static final CLIOption [] impliedOptions = new CLIOption[] {
help,
verbose
};
public static Object mainClean(String[] argv)
{
try
{
return main(argv);
}
catch (UsageException e)
{
if (e.getMessage() != HELP_USAGE_MESSAGE)
{
e.printStackTrace(System.out);
System.out.println(e.getFormattedUsageStatement());
}
else
{
// this is not an error
System.out.println(e.getFormattedUsageStatement());
return ShutdownHandler.instance().shutdown(0);
}
}
catch (InvalidCLIEntryException e)
{
e.printStackTrace(System.err);
}
catch (MissingCLIEntryException e)
{
e.printStackTrace(System.err);
}
return ShutdownHandler.instance().shutdown(-1);
}
public static Object main(String[] argv) throws InvalidCLIEntryException, UsageException, MissingCLIEntryException
{
// the protocol is system property, then call stack
String val = System.getProperty("CLIMain");
if (val != null)
{
Object launchable = resolveLaunchable(val);
return mainWithInstance(launchable, argv);
}
// determine the appropriate caller
StackTraceElement[] st = Thread.currentThread().getStackTrace();
// ignoring the last element, which will be the current class, move up the chain
// and grab the first CLIEntry you find
for (int i = 1; i < st.length; i++)
{
String name = st[i].getClassName();
try
{
Object launchable = resolveLaunchable(name);
return mainWithInstance(launchable, argv);
}
catch(MissingCLIEntryException exc)
{
// go on to try the next one
}
}
// fail loudly
throw new MissingCLIEntryException("No acceptable CLIEntry found on the stack.");
}
private static Object resolveLaunchable(String name)
throws InvalidCLIEntryException, MissingCLIEntryException, UsageException
{
try
{
Class> cl = Thread.currentThread().getContextClassLoader().loadClass(name);
// look for the marker annotation
CLIEntry annot = cl.getAnnotation(CLIEntry.class);
if (annot != null)
{
// try this one
// next requirement is exactly one CLIMain annotation
int mains = 0;
for (Method method : cl.getMethods())
{
CLIMain climain = method.getAnnotation(CLIMain.class);
if (climain != null)
{
mains++;
}
}
if (mains == 0)
{
throw new InvalidCLIEntryException("Class [" + cl + "] has a CLIEntry annotation, but no methods annotated with CLIMain", InvalidCLIEntryException.invalid_cause.missing_cli_main);
}
else if (mains > 1)
{
throw new InvalidCLIEntryException("Class [" + cl + "] has a CLIEntry annotation, but more than one method annotated with CLIMain", InvalidCLIEntryException.invalid_cause.redundant_cli_main);
}
// bail - our work is done
return cl.newInstance();
}
throw new MissingCLIEntryException();
}
catch (ClassNotFoundException e)
{
// this class is not available to us - flag it as missing
throw new MissingCLIEntryException(e);
}
catch (InstantiationException e)
{
throw new InvalidCLIEntryException(e, InvalidCLIEntryException.invalid_cause.class_access_error);
}
catch (IllegalAccessException e)
{
throw new InvalidCLIEntryException(e, InvalidCLIEntryException.invalid_cause.class_access_error);
}
}
public static Object mainWithInstanceClean(Object obj, String[] argv)
{
try
{
return mainWithInstance(obj, argv);
}
catch (UsageException e)
{
if (e.getMessage() != HELP_USAGE_MESSAGE)
{
e.printStackTrace(System.out);
System.out.println(e.getFormattedUsageStatement());
}
else
{
// this is not an error
System.out.println(e.getFormattedUsageStatement());
return ShutdownHandler.instance().shutdown(0);
}
}
catch (InvalidCLIEntryException e)
{
e.printStackTrace(System.out);
}
return ShutdownHandler.instance().shutdown(-1);
}
public static Object mainWithInstance(Object obj, String[] argv)
throws UsageException, InvalidCLIEntryException
{
if (argv == null)
{
argv = NULL_ARGS;
}
Options options = new Options();
Map instanceMap = new HashMap();
Map valueMap = new HashMap();
Map displayValueMap = new HashMap();
List typeList = new ArrayList();
Data d = new Data();
d.clioption = help;
d.cardinality = cardinal_type.flag;
typeList.add(d);
d = new Data();
d.clioption = verbose;
d.cardinality = cardinal_type.flag;
typeList.add(d);
// look through the annotations for methods of this class, and create options for each
Method[] methList = obj.getClass().getMethods();
for (int i = 0; i < methList.length; i++)
{
CLIOption annot = methList[i].getAnnotation(CLIOption.class);
if (annot != null)
{
// validate that this parameter is not one of the reserved options
if (reservedOptionNames.containsKey(annot.name()) || reservedOptionNames.containsKey(annot.longName()))
{
throw new InvalidCLIEntryException("Option name [" + annot.name() + "] or long name [" + annot.longName() + "] is reserved", InvalidCLIEntryException.invalid_cause.bad_options);
}
// validate some basic bad configurations
assertValid(annot);
// validate that this is a method which accepts a single parameter
Class>[] parameterTypes = methList[i].getParameterTypes();
Data data = new Data();
data.setter = methList[i];
data.clioption = annot;
if (instanceMap.containsKey(annot.name()))
{
throw new InvalidCLIEntryException("Option [" + annot.name() + "] specified more than once", InvalidCLIEntryException.invalid_cause.bad_options);
}
else if (instanceMap.containsKey(annot.longName()))
{
throw new InvalidCLIEntryException("Option [" + annot.longName() + "] specified more than once", InvalidCLIEntryException.invalid_cause.bad_options);
}
instanceMap.put(annot.name(), "");
if (!annot.longName().equals(CLIOption.NULL_STRING_VALUE))
{
instanceMap.put(annot.longName(), "");
}
typeList.add(data);
if (parameterTypes == null || parameterTypes.length != 1)
{
throw new InvalidCLIEntryException("Setter for option ["
+ annot.name()
+ "] does not accept exactly one parameter: "
+ methList[i], InvalidCLIEntryException.invalid_cause.bad_options);
}
// if the cardinality is anything other than 1, the parameter must be an array type
Class> parameterType = parameterTypes[0];
Class> arrayComponentType = parameterType.isArray() ? parameterType.getComponentType() : parameterType;
if (parameterType.isArray())
{
data.cardinality = cardinal_type.array;
}
else
{
data.cardinality = cardinal_type.instance;
}
// validate that the type of the parameter is within bounds
if (arrayComponentType.equals(Object.class))
{
data.type = data_type.Object;
}
else if (arrayComponentType.equals(String.class))
{
data.type = data_type.String;
}
else if (arrayComponentType.equals(boolean.class))
{
data.type = data_type.bool;
if (data.cardinality != cardinal_type.array)
{
data.cardinality = cardinal_type.flag;
}
}
else if (arrayComponentType.equals(Boolean.class))
{
data.type = data_type.Bool;
if (data.cardinality != cardinal_type.array)
{
data.cardinality = cardinal_type.flag;
}
}
else if (arrayComponentType.equals(int.class))
{
data.type = data_type.integer;
}
else if (arrayComponentType.equals(Integer.class))
{
data.type = data_type.Integer;
}
else if (arrayComponentType.equals(long.class))
{
data.type = data_type.long_integer;
}
else if (arrayComponentType.equals(Long.class))
{
data.type = data_type.Long;
}
if (data.type == null)
{
throw new InvalidCLIEntryException("Setter for option ["
+ annot.name()
+ "] does not accept a valid parameter type: "
+ methList[i], InvalidCLIEntryException.invalid_cause.bad_options);
}
if (annot.valueCardinality() == 1)
{
if (data.cardinality == cardinal_type.array)
{
throw new InvalidCLIEntryException("Setter for option ["
+ annot.name()
+ "] must not use an array type: "
+ methList[i], InvalidCLIEntryException.invalid_cause.bad_options);
}
}
else
{
if (data.cardinality != cardinal_type.array)
{
throw new InvalidCLIEntryException("Setter for option ["
+ annot.name()
+ "] requires an array type: "
+ methList[i], InvalidCLIEntryException.invalid_cause.bad_options);
}
}
}
}
// validate any option sets
CLIEntry clentry = obj.getClass().getAnnotation(CLIEntry.class);
CLIOptionSet [] validSets = clentry.validarameterSets();
CLIOptionSet [] invalidSets = clentry.invalidarameterSets();
// validate that the options referenced exist
validateOptionSet(validSets, instanceMap);
validateOptionSet(invalidSets, instanceMap);
// validate that no option names are duplicated
validateSets(validSets);
validateSets(invalidSets);
// validate that the option sets are exclusive - cross both ways. Always check if l is a subset of r
validateExclusiveSets(validSets, invalidSets);
validateExclusiveSets(invalidSets, validSets);
// before handling, order the typeList appropriately
typeList = orderOptions(clentry, typeList);
for (Data data : typeList)
{
//here is an option - check it out
Option option = new Option(
data.clioption.name(),
data.clioption.longName().equals(CLIOption.NULL_STRING_VALUE) ? null : data.clioption.longName(),
data.clioption.valueType() != CLIOption.value_type.not_allowed,
data.clioption.description()
);
option.setRequired(data.clioption.required());
// in the case of arguments with values, get the value cardinalities worked out
if (data.clioption.valueType() != CLIOption.value_type.not_allowed)
{
option.setArgs(1);
if (data.clioption.valueType() == CLIOption.value_type.required)
{
option.setOptionalArg(false);
}
else if (data.clioption.valueType() == CLIOption.value_type.optional)
{
option.setOptionalArg(true);
}
}
else
{
option.setArgs(0);
option.setOptionalArg(false);
}
//option.setType();
options.addOption(option);
}
// initially, check for the help option. This must be done before apache gets to it.
for (String option : argv)
{
// this is the signal to throw a usage error
if (option.equals("-?") || option.equals("--help"))
{
if (argv.length != 1)
{
throw new UsageException(obj, typeList, HELP_INVALID_USAGE_MESSAGE);
}
throw new UsageException(obj, typeList, HELP_USAGE_MESSAGE);
}
}
//options are created - pass the result to the command line
CommandLineParser clp = null;
switch (getParserType(obj))
{
case gnu:
clp = new GnuParser();
break;
case posix:
clp = new PosixParser();
break;
case basic:
clp = new BasicParser();
break;
}
try
{
CommandLine cl = clp.parse(options, argv);
boolean vbose = false;
if (cl.hasOption(help.name()) || cl.hasOption(help.longName()))
{
throw new UsageException(obj, typeList, HELP_INVALID_USAGE_MESSAGE);
}
if (cl.hasOption(verbose.name()) || cl.hasOption(verbose.longName()))
{
vbose = true;
}
// this map stores the actual options passed by the user for comparing with
// the valid and invalid option sets
Map optMap = new HashMap();
// run through all options and determine values
for (Data data : typeList)
{
boolean opted = cl.hasOption(data.clioption.name());
// skip when there is no setter
if (data.setter == null)
{
continue;
}
Object value;
if (!opted)
{
if (data.clioption.defaultValue().equals("") && data.clioption.required())
{
throw new UsageException(obj, typeList, "Value not specified ["
+ data.clioption.name()
+ "] and no suitable default exists");
}
}
else
{
optMap.put(data.clioption.name(), data);
}
try
{
value =
resolveStringValue(data,
cl,
data.clioption.defaultValue().equals(CLIOption.NULL_STRING_VALUE) ? null : data.clioption.defaultValue());
valueMap.put(data.clioption.name(), value);
valueMap.put(data.clioption.longName(), value);
}
catch(UsageException exc)
{
// rethrow to populate missing values
throw new UsageException(obj, typeList, exc);
}
}
// before continuing, check all valid and invalid combinations
checkValidSets(validSets, optMap, obj, typeList);
checkInValidSets(invalidSets, optMap, obj, typeList);
RecursiveMap rmap = new RecursiveMap(valueMap);
// run through all options and pass the values on with injection
for (Data data : typeList)
{
Object value = rmap.get(data.clioption.name());
Object val = resolveOptionValue(data, cl, (String) value, obj, typeList);
if (data.setter != null && val != null)
{
try
{
displayValueMap.put(StringUtils.rightPad(StringUtils.abbreviate(data.clioption.longName(), 30), 30), display(val));
data.setter.invoke(obj, val);
}
catch (IllegalAccessException e)
{
throw new InvalidCLIEntryException("Could not access setter for option ["
+ data.clioption.name()
+ "]", e, InvalidCLIEntryException.invalid_cause.class_access_error);
}
catch (InvocationTargetException e)
{
throw new UsageException(obj, typeList, "Error setting option value [" + data.clioption.name() + "]", e);
}
}
}
if (vbose)
{
verboseOutput(obj, displayValueMap);
}
invokeMain(obj, argv);
return obj;
}
catch (ParseException e)
{
throw new UsageException(obj, typeList, e);
}
catch (UsageException exc)
{
throw exc;
}
catch (Exception exc)
{
throw new RuntimeException(exc);
}
}
/**
* Sort the Data array by the assignmentOrder of the entry. Any unreferenced options default
* to current order (declaration order) and any names which aren't a option long or short name will
* cause an error.
* @param clentry
* @param typeList
* @return
*/
private static List orderOptions(CLIEntry clentry, List typeList) throws InvalidCLIEntryException {
if (clentry.assignmentOrder().length == 0)
{
return typeList;
}
List orderedTypeList = new ArrayList();
// store a reference to each option in the map by long and short name
Map optionNameMap = new HashMap();
for (Data data : typeList)
{
optionNameMap.put(data.clioption.name(), data);
optionNameMap.put(data.clioption.longName(), data);
}
for (String option : clentry.assignmentOrder())
{
Data op = optionNameMap.get(option);
if (op == null)
{
throw new InvalidCLIEntryException("Option '" + option + "' specified in assignmentOrder is not a declared option", InvalidCLIEntryException.invalid_cause.bad_option_assignment_order);
}
if (orderedTypeList.contains(op))
{
throw new InvalidCLIEntryException("Option '" + option + "' specified in assignmentOrder more than once", InvalidCLIEntryException.invalid_cause.bad_option_assignment_order);
}
orderedTypeList.add(op);
}
// add in all missing options in the order provided
for (Data data : typeList)
{
if (!orderedTypeList.contains(data))
{
orderedTypeList.add(data);
}
}
return orderedTypeList;
}
private static Object display(Object val) {
if (val != null && val.getClass().isArray())
{
StringBuilder stb = new StringBuilder();
Object [] obarr = getArray(val);
for (Object obj : obarr)
{
stb.append(obj);
stb.append(',');
}
if (stb.length() > 1)
{
stb.deleteCharAt(stb.length() - 1);
}
return stb.toString();
}
return String.valueOf(val);
}
private static final Class>[] ARRAY_PRIMITIVE_TYPES = {
int[].class, float[].class, double[].class, boolean[].class,
byte[].class, short[].class, long[].class, char[].class };
private static Object[] getArray(Object val){
Class> valKlass = val.getClass();
Object[] outputArray = null;
for(Class> arrKlass : ARRAY_PRIMITIVE_TYPES){
if(valKlass.isAssignableFrom(arrKlass)){
int arrlength = Array.getLength(val);
outputArray = new Object[arrlength];
for(int i = 0; i < arrlength; ++i){
outputArray[i] = Array.get(val, i);
}
break;
}
}
if(outputArray == null) // not primitive type array
outputArray = (Object[])val;
return outputArray;
}
private static void checkInValidSets(CLIOptionSet[] invalidSets, Map optMap, Object obj, List typeList)
throws UsageException
{
CLIOptionSet invalidSet = checkSet(invalidSets, optMap);
if (invalidSet != null)
{
throw new UsageException(obj, typeList, "Invalid combination of parameters. " + invalidSet.description());
}
}
private static void checkValidSets(CLIOptionSet[] validSets, Map optMap, Object obj, List typeList)
throws UsageException
{
if (validSets.length != 0 && checkSet(validSets, optMap) == null)
{
throw new UsageException(obj, typeList, "Invalid combination of parameters. Does not match any valid option sets.");
}
}
private static CLIOptionSet checkSet(CLIOptionSet[] validSets, Map optMap)
{
if (validSets.length != 0)
{
for (CLIOptionSet set : validSets)
{
if (matches(set, optMap))
{
return set;
}
}
}
return null;
}
private static boolean matches(CLIOptionSet set, Map optMap)
{
String [] names = set.optionShortNames();
CLIOptionSet.set_type type = set.setType();
switch (type)
{
case subset_of_args:
// if the set is larger than the options, it can't be a subset
if (names.length > optMap.size())
{
return false;
}
break;
case exact_match:
// if both sides aren't the same length, then they can't match
if (names.length != optMap.size())
{
return false;
}
break;
}
// now that lengths have been validated, check that set is at least a subset of optMap. This wil satisfy both conditions
for (String name : names)
{
if (!optMap.containsKey(name))
{
return false;
}
}
return true;
}
private static void validateSets(CLIOptionSet[] invalidSets) throws InvalidCLIEntryException
{
for (CLIOptionSet set : invalidSets)
{
String [] snames = set.optionShortNames();
for (String lsname : snames)
{
// for each option name, count the number of occurrences. Each must be exactly 1
int occCount = 0;
for (String rsname : snames)
{
if (lsname.equals(rsname))
{
occCount++;
}
}
if (occCount != 1)
{
throw new InvalidCLIEntryException("Option duplciated in set '" + lsname + "'", InvalidCLIEntryException.invalid_cause.bad_option_set);
}
}
}
}
private static void validateExclusiveSets(CLIOptionSet[] validSets, CLIOptionSet[] invalidSets)
throws InvalidCLIEntryException
{
for (CLIOptionSet lset : validSets)
{
String [] lnames = lset.optionShortNames();
for (CLIOptionSet rset : invalidSets)
{
String [] rnames = rset.optionShortNames();
// compare the two sets
compareExclusive(lnames, lset.id(), lset.setType(), rnames, rset.id(), rset.setType());
}
}
}
private static void compareExclusive(String [] lnames, String lid, CLIOptionSet.set_type lset_type, String [] rnames, String rid, CLIOptionSet.set_type rset_type) throws InvalidCLIEntryException
{
int overlapCount = 0;
for (String lname : lnames)
{
boolean found = false;
for (String rname : rnames)
{
if (lname.equals(rname))
{
// break - no need to continue
found = true;
break;
}
}
if (found)
{
overlapCount++;
}
}
if (
(lnames.length == rnames.length)
&&
(overlapCount == lnames.length))
{
//sets are lnames is a subset or rnames. Fail loudly
throw new InvalidCLIEntryException("Option sets overlap. [" + lid + ":" + rid + "]", InvalidCLIEntryException.invalid_cause.bad_option_set);
}
//TODO - handle subsets
}
private static void validateOptionSet(CLIOptionSet[] validSets, Map instanceMap)
throws InvalidCLIEntryException
{
for (CLIOptionSet set : validSets)
{
String[] shnames = set.optionShortNames();
for (String shname : shnames)
{
if (!instanceMap.containsKey(shname))
{
throw new InvalidCLIEntryException("Option '" + shname + "' from set '" + set.description() + "' not found in the command object", InvalidCLIEntryException.invalid_cause.bad_option_set);
}
}
}
}
private static void assertValid(CLIOption annot) throws InvalidCLIEntryException
{
if (annot.required() && !annot.defaultValue().equals(CLIOption.NULL_STRING_VALUE))
{
throw new InvalidCLIEntryException(
"Required options cannot have default values: " + annot.name(),
InvalidCLIEntryException.invalid_cause.bad_options
);
}
// flags can only have a default value of 'true', 'false'
if (annot.valueType() == CLIOption.value_type.not_allowed)
{
if (!annot.defaultValue().equals(CLIOption.NULL_STRING_VALUE) && !annot.defaultValue().equals("true") && !annot.defaultValue().equals("false"))
{
throw new InvalidCLIEntryException(
"Flags cannot have default values: " + annot.name(),
InvalidCLIEntryException.invalid_cause.bad_options
);
}
}
}
private static void verboseOutput(Object ent, Map valueMap)
{
CLIEntry entr = ent.getClass().getAnnotation(CLIEntry.class);
Map context = new HashMap();
context.put("options", valueMap.entrySet());
context.put("entry", entr);
context.put("datetime", new Timestamp(System.currentTimeMillis()));
String nick = entr.nickName();
if (nick.equals(CLIOption.NULL_STRING_VALUE))
{
nick = entr.getClass().getSimpleName();
}
context.put("nickName", nick);
try
{
String template = IOUtils.toString(ent.getClass().getResource("/runtime_options.vm"));
VelocityContext vcontext = new VelocityContext(context);
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.init();
StringWriter w = new StringWriter();
velocityEngine.evaluate(vcontext, w, "log", template);
System.out.println(w.toString());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private static String print(Object value)
{
return String.valueOf(value);
}
private static void invokeMain(Object obj, String[] argv) throws InvocationTargetException, IllegalAccessException
{
Method [] methods = obj.getClass().getMethods();
for (Method method : methods)
{
CLIMain climain = method.getAnnotation(CLIMain.class);
if (climain != null)
{
// this is the one.
if (method.getParameterTypes().length == 0)
{
method.invoke(obj);
}
else
{
// this one needs the string parameters
method.invoke(obj, new Object [] {argv});
}
}
}
}
private static CLIEntry.parser_type getParserType(Object obj)
{
CLIEntry clentry = obj.getClass().getAnnotation(CLIEntry.class);
return clentry.parserType();
}
private static Object resolveStringValue(Data data, CommandLine cl, String def) throws UsageException
{
boolean hasOption = cl.hasOption(data.clioption.name());
String textValue = hasOption ? cl.getOptionValue(data.clioption.name()) : def;
if (data.cardinality == cardinal_type.array)
{
}
else if (data.cardinality == cardinal_type.instance)
{
if (!hasOption)
{
textValue = def;
}
}
else if (data.cardinality == cardinal_type.flag)
{
if (!hasOption)
{
textValue = def;
}
}
return textValue;
}
private static Object resolveOptionValue(Data data, CommandLine cl, String textValue, Object cliEntry, List options) throws UsageException
{
Object value = null;
// we already validated earlier that there is exactly one parameter
if (data.cardinality != cardinal_type.flag)
{
if (textValue == null)
{
return null;
}
if (data.cardinality == cardinal_type.array)
{
String [] optionValue = getArrayValue(textValue, data.clioption.valueSeparator());
// validate against enumerations if present
validateEnum(data, value, cliEntry, options);
if (data.clioption.valueCardinality() != CLIOption.UNLIMITED_VALUES)
{
if (optionValue.length != data.clioption.valueCardinality())
{
throw new UsageException(cliEntry, options, "Wrong number of values supplied for option [" + data.clioption
.name() + "]: " + Arrays.asList(optionValue));
}
}
Object[] valueArr = optionValue;
boolean[] bValueArr = null;
int[] iValueArr = null;
long[] lValueArr = null;
if (data.type == data_type.bool)
{
bValueArr = new boolean[optionValue.length];
}
else if (data.type == data_type.Bool)
{
valueArr = new Boolean[optionValue.length];
}
else if (data.type == data_type.integer)
{
iValueArr = new int[optionValue.length];
}
else if (data.type == data_type.Integer)
{
valueArr = new Integer[optionValue.length];
}
else if (data.type == data_type.long_integer)
{
lValueArr = new long[optionValue.length];
}
else if (data.type == data_type.Long)
{
valueArr = new Long[optionValue.length];
}
else if (data.type == data_type.String)
{
valueArr = new String[optionValue.length];
}
for (int optIndex = 0; optIndex < optionValue.length; optIndex++)
{
if (data.type == data_type.Bool)
{
valueArr[optIndex] = Boolean.valueOf(optionValue[optIndex]);
}
else if (data.type == data_type.bool)
{
bValueArr[optIndex] = Boolean.valueOf(optionValue[optIndex]);
}
else if (data.type == data_type.integer)
{
iValueArr[optIndex] = Integer.parseInt(optionValue[optIndex]);
}
else if (data.type == data_type.Integer)
{
valueArr[optIndex] = Integer.parseInt(optionValue[optIndex]);
}
else if (data.type == data_type.long_integer)
{
lValueArr[optIndex] = Long.parseLong(optionValue[optIndex]);
}
else if (data.type == data_type.Long)
{
valueArr[optIndex] = Long.parseLong(optionValue[optIndex]);
}
else if (data.type == data_type.String || data.type == data_type.Object)
{
valueArr[optIndex] = optionValue[optIndex];
}
}
if (bValueArr != null)
{
value = bValueArr;
}
else if (iValueArr != null)
{
value = iValueArr;
}
else
{
value = valueArr;
}
}
else
{
value = textValue;
validateEnum(data, value, cliEntry, options);
if (data.type == data_type.Bool || data.type == data_type.bool)
{
value = Boolean.valueOf(textValue);
}
else if (data.type == data_type.integer || data.type == data_type.Integer)
{
value = Integer.parseInt(textValue);
}
else if (data.type == data_type.long_integer || data.type == data_type.Long)
{
value = Long.parseLong(textValue);
}
}
}
else
{
// determine the correct action here.
// 1 - if there is no value supplied, chose a default based on whether the option was supplied.
// boolean types use false, integral types get 0
if (textValue == null)
{
boolean hasOption = cl.hasOption(data.clioption.name());
switch (data.type)
{
case bool:
case Bool:
value = hasOption;
break;
case long_integer:
case Long:
case integer:
case Integer:
value = hasOption ? 1 : 0;
break;
case string:
case String:
case Object:
value = hasOption ? "true" : "false";
break;
}
}
else
{
value = false;
// try several methods for interpretation
if (textValue.equals("1"))
{
value = true;
}
else if (textValue.equals("0"))
{
value = false;
}
else if (textValue.equals("true"))
{
value = true;
}
else if (textValue.equals("false"))
{
value = false;
}
}
}
return value;
}
private static void validateEnum(Data data, Object value, Object cliEntry, List options) throws UsageException
{
String[] atr = data.clioption.enumeratedValues();
if (atr.length != 0)
{
boolean match = false;
for (String enu : atr)
{
if (enu.equals(value))
{
match = true;
break;
}
}
if (!match)
{
throw new UsageException(cliEntry, options, "Option [" + data.clioption.name() + "] value [" + value + "] does not match enumeration");
}
}
}
public static String [] getArrayValue(String textValue, char c)
{
// split on every value, ignoring escaped separators denoted by ESCAPE_CHARACTER_DEFAULT
// pass over the input two times, once looking for \() constructs, and the second
// time resolving escape sequences
StringBuilder builder = new StringBuilder();
EscapeFunctionExpression escapeFunctionExpression = new EscapeFunctionExpression(textValue);
int lastEnd = 0;
while (escapeFunctionExpression.hasNext())
{
String escText = escapeFunctionExpression.getEscapeFunction();
// check the preChar - an even number means pass it through.
// in the case of an odd number, remove the last one and escape the sequence
String pre = escapeFunctionExpression.getPreChar();
if (pre.length() % 2 == 0)
{
// pass all through unmodified
builder.append(textValue.substring(lastEnd, escapeFunctionExpression.start()));
builder.append(escapeFunctionExpression.group(0));
}
else
{
if (pre.length() > 1)
{
// remove one char, for our escape
builder.append(pre.substring(1));
}
builder.append(textValue.substring(lastEnd, escapeFunctionExpression.start()));
builder.append(escapeText(escText, c));
}
lastEnd = escapeFunctionExpression.end();
}
if (lastEnd < textValue.length())
{
builder.append(textValue.substring(lastEnd));
}
// scan and process escape sequences
String bu = builder.toString();
List strings = new ArrayList();
builder.setLength(0);
boolean lastCharWasEscape = false;
for (char ch : bu.toCharArray())
{
if (ch == CLIOption.ESCAPE_CHARACTER_DEFAULT)
{
if (lastCharWasEscape)
{
// append a single escape char
builder.append(CLIOption.ESCAPE_CHARACTER_DEFAULT);
lastCharWasEscape = false;
}
else
{
lastCharWasEscape = true;
}
}
else if (ch == c)
{
if (lastCharWasEscape)
{
// pass on the character
builder.append(ch);
}
else
{
strings.add(builder.toString().replace("@-", "-"));
builder.setLength(0);
}
lastCharWasEscape = false;
}
else
{
// pass the char on
if (lastCharWasEscape)
{
throw new IllegalArgumentException("Illegal escaped character: " + ch);
}
builder.append(ch);
lastCharWasEscape = false;
}
}
if (lastCharWasEscape)
{
throw new IllegalArgumentException("unterminated escaped character");
}
if (builder.length() > 0)
{
// do a quick replace of '@-' with '-'
strings.add(builder.toString().replace("@-", "-"));
}
return strings.toArray(new String[strings.size()]);
}
// Escape occurrences of the separator char OR the escape char
private static String escapeText(String substring, char c) {
// replace every occurrence of the separator char (c) with an escape sequence
return substring.replace("\\", "\\\\").replaceAll(Pattern.quote(String.valueOf(c)), "\\\\$0");
}
}