com.senzing.cmdline.CommandLineUtilities Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of senzing-commons Show documentation
Show all versions of senzing-commons Show documentation
Utility classes and functions common to multiple Senzing projects.
The newest version!
package com.senzing.cmdline;
import com.senzing.util.JsonUtilities;
import javax.json.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.regex.Pattern;
import static com.senzing.cmdline.CommandLineSource.*;
/**
* Utility functions for parsing command line arguments.
*
*/
@SuppressWarnings("unchecked")
public class CommandLineUtilities {
/**
* The value to use in the JSON representation of the parsed command-line
* parameters when a {@linkplain CommandLineOption#isSensitive() sensitive}
* option is encountered.
*/
public static final String REDACTED_SENSITIVE_VALUE = "********";
/***
* The Regular Expression pattern for JSON arrays.
*/
private static final Pattern JSON_ARRAY_PATTERN
= Pattern.compile(
"\\s*\\[\\s*((\\\".*\\\"\\s*,\\s*)+"
+ "(\\\".*\\\")|(\\\".*\\\")?)\\s*\\]\\s*");
/**
* The JAR file name containing this class.
*/
public static final String JAR_FILE_NAME;
/**
* The base URL of the JAR file containing this class.
*/
public static final String JAR_BASE_URL;
/**
* The URL path to the JAR file containing this class.
*/
public static final String PATH_TO_JAR;
static {
String jarBaseUrl = null;
String jarFileName = null;
String pathToJar = null;
try {
Class cls = CommandLineUtilities.class;
String url = cls.getResource(
cls.getSimpleName() + ".class").toString();
if (url.indexOf(".jar") >= 0) {
int index = url.lastIndexOf(
cls.getName().replace(".", "/") + ".class");
jarBaseUrl = url.substring(0, index);
index = jarBaseUrl.lastIndexOf("!");
if (index >= 0) {
url = url.substring(0, index);
index = url.lastIndexOf("/");
if (index >= 0) {
jarFileName = url.substring(index + 1);
}
url = url.substring(0, index);
index = url.indexOf("/");
pathToJar = url.substring(index);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new ExceptionInInitializerError(e);
} finally {
JAR_BASE_URL = jarBaseUrl;
JAR_FILE_NAME = jarFileName;
PATH_TO_JAR = pathToJar;
}
}
/**
* Private default constructor.
*/
private CommandLineUtilities() {
// do nothing
}
/**
* Returns a new {@link String} array that contains the same elements as
* the specified array except for the first N arguments where N is the
* specified count.
*
* @param args The array of command line arguments.
* @param count The number of arguments to shift.
* @return The shifted argument array.
* @throws IllegalArgumentException If the specified count is negative.
* @throws TooFewArgumentsException If there are not enough arguments for
* the shift.
*/
public static String[] shiftArguments(String[] args, int count)
{
if (count < 0) {
throw new IllegalArgumentException(
"The specified count cannot be negative: " + count);
}
if ((args.length - count) < 0) {
throw new TooFewArgumentsException(
"The specified shift count cannot be greater than the array length. "
+ "arrayLength=[ " + args.length + " ], shiftCount=[ " + count
+ " ]");
}
String[] args2 = new String[args.length - count];
for (int index = 0; index < args2.length; index++) {
args2[index] = args[index + count];
}
return args2;
}
/**
* Stores the option and its value(s) in the specified option map, first
* checking to ensure that the option is NOT already specified and
* checking if the option has any conflicts. If the specified option is
* deprecated then a warning message is printed.
*
* @param optionMap The {@link Map} to put the option in.
* @param value the {@link CommandLineValue} to add to the {@link Map}.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*
* @throws CommandLineException If the specified {@link CommandLineValue}
* cannot be put in the specified {@link Map}
* due to a validation problem.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
void putValue(Map optionMap,
CommandLineValue value)
throws CommandLineException
{
CommandLineOption option = value.getOption();
Set optionKeys = optionMap.keySet();
CommandLineValue prevValue = optionMap.get(option);
if (prevValue != null) {
Set flags = new HashSet<>();
if (value.getSource() == COMMAND_LINE) {
flags.add(value.getSpecifier());
}
if (prevValue.getSource() == COMMAND_LINE) {
flags.add(prevValue.getSpecifier());
}
throw new RepeatedOptionException(option, flags);
}
// check for conflicts if the value is NOT a defaulted value
if (!checkNoArgFalse(value) && (value.getSource() != DEFAULT)) {
Set conflicts = option.getConflicts();
for (CommandLineOption, ?> opt : optionKeys) {
// get the reverse conflicts in case not symmetrical
Set revConflicts = opt.getConflicts();
// check for conflicts
if ((conflicts != null && conflicts.contains(opt))
|| ((revConflicts != null && revConflicts.contains(option))))
{
// get the command-line value
CommandLineValue conflictValue = optionMap.get(opt);
// no conflict if the other is a defaulted value
if (conflictValue.getSource() == DEFAULT) continue;
// no conflict if the other is a no-arg option with a false value
if (checkNoArgFalse(conflictValue)) continue;
throw new ConflictingOptionsException(conflictValue, value);
}
}
}
// put it in the option map
optionMap.put(option, value);
}
/**
* Checks if the specified {@link CommandLineValue} is a zero or
* single-valued boolean flag and the value is false.
*
* @param cmdLineValue The {@link CommandLineValue} to check.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
boolean checkNoArgFalse(CommandLineValue cmdLineValue)
{
CommandLineOption option = cmdLineValue.getOption();
// special-case no-arg boolean flags
int minParamCount = option.getMinimumParameterCount();
int maxParamCount = option.getMaximumParameterCount();
List defaultParams = option.getDefaultParameters();
List actualParams = cmdLineValue.getParameters();
return (minParamCount == 0 && maxParamCount >= 0
&& defaultParams != null
&& defaultParams.size() == 1
&& "false".equalsIgnoreCase(defaultParams.get(0))
&& actualParams != null
&& actualParams.size() == 1
&& "false".equalsIgnoreCase(actualParams.get(0)));
}
/**
* Returns the enumerated {@link CommandLineOption} value associated with
* the specified command line flag and enumerated {@link CommandLineOption}
* class.
*
* @param enumClass The {@link Class} for the {@link CommandLineOption}
* implementation.
* @param commandLineFlag The command line flag.
*
* @return The enumerated {@link CommandLineOption} or null
if
* not found.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
T lookup(Class enumClass, String commandLineFlag)
{
// just iterate to find it rather than using a lookup map given that
// enums are usually not more than a handful of values
EnumSet enumSet = EnumSet.allOf(enumClass);
for (T enumVal : enumSet) {
if (enumVal.getCommandLineFlag().equals(commandLineFlag)) {
return enumVal;
}
if (enumVal.getSynonymFlags().contains(commandLineFlag)) {
return enumVal;
}
}
return null;
}
/**
* Checks if a primary option is required.
*
* @param enumClass The enum class for the {@link CommandLineOption}
* implementation.
*
* @return true
if a primary option is required, otherwise
* false
.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
boolean checkPrimaryRequired(Class enumClass)
{
// check if we need a primary option
boolean primaryRequired = false;
EnumSet enumSet = EnumSet.allOf(enumClass);
for (T enumVal : enumSet) {
if (enumVal.isPrimary()) {
return true;
}
}
// get the base type
Class baseType
= ((CommandLineOption) enumSet.iterator().next()).getBaseOptionType();
// check the base type
if (baseType != null && Enum.class.isAssignableFrom(baseType)) {
return checkPrimaryRequired(baseType);
}
// return false if we get here
return false;
}
/**
* Checks if a primary option is required.
*
* @param enumClass The enum class for the {@link CommandLineOption}
* implementation.
*
* @return true
if a primary option is required, otherwise
* false
.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
Set getPrimaryOptions(Class enumClass)
{
// create the result
Set result = new LinkedHashSet<>();
populatePrimaryOptions(result, enumClass);
return result;
}
/**
* Checks if a primary option is required.
*
* @param enumClass The enum class for the {@link CommandLineOption}
* implementation.
*
* @return true
if a primary option is required, otherwise
* false
.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
void populatePrimaryOptions(Set set, Class enumClass)
{
// check if we need a primary option
EnumSet enumSet = EnumSet.allOf(enumClass);
for (T enumVal : enumSet) {
if (enumVal.isPrimary()) {
set.add(enumVal);
}
}
// get the base type
Class baseType
= ((CommandLineOption) enumSet.iterator().next()).getBaseOptionType();
// check the base type
if (baseType != null && Enum.class.isAssignableFrom(baseType)) {
populatePrimaryOptions(set,baseType);
}
}
/**
* Gets the ordered {@link Set} describing the type chain for the specified
* command line option class.
*
* @param enumClass The starting class for the type chain.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
Set> getTypeChain(Class enumClass)
{
Set> result = new LinkedHashSet<>();
populateTypeChain(result, enumClass);
return result;
}
/**
* Populates the type chain for the specified command line option class in
* the specified {@link Set}.
*
* @param set The {@link} set to populate.
* @param enumClass The starting class for the type chain.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
void populateTypeChain(Set> set,
Class enumClass)
{
if (enumClass == null) return;
set.add(enumClass);
EnumSet enumSet = EnumSet.allOf(enumClass);
Class baseType = enumSet.iterator().next().getBaseOptionType();
if (baseType != null) {
populateTypeChain(set, baseType);
}
}
/**
* Populates the options chain for the specified command line option class in
* the specified {@link Set}.
*
* @param set The {@link} set to populate.
* @param enumClass The starting class for the options chain.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
void populateOptionsChain(Set set,
Class enumClass)
{
if (enumClass == null) return;
EnumSet enumSet = EnumSet.allOf(enumClass);
Class baseType = null;
for (T option : enumSet) {
set.add((CommandLineOption) option);
if (baseType == null) baseType = option.getBaseOptionType();
}
if (baseType != null) {
populateOptionsChain(set, baseType);
}
}
/**
* Validates the specified {@link Set} of specified {@link CommandLineOption}
* instances and ensures that they logically make sense together. This
* checks for the existing of at least one primary option (if primary options
* exist), ensures there are no conflicts and that all dependencies are
* satisfied.
*
* @param enumClass The {@link Class} object identifying the enumerated type
* that implements {@link CommandLineOption}.
* @param optionValues The {@link Map} of {@link CommandLineOption} keys to
* {@link CommandLineValue} values.
*
* @return A {@link List} of {@link DeprecatedOptionWarning} instances
* describing the deprecation warnings (if any), or null
* if there are no deprecation warnings.
*
* @throws IllegalArgumentException If the specified options are invalid
* together.
* @throws CommandLineException If the specified command-line options fail
* validation.
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
List validateOptions(
Class enumClass,
Map optionValues)
throws IllegalArgumentException, CommandLineException
{
List deprecatedList = new LinkedList<>();
// create the set of legal option classes
Set> typeChainSet
= getTypeChain(enumClass);
// check the types in the key set
optionValues.forEach((option, optionValue) -> {
if (option != optionValue.getOption()) {
throw new IllegalArgumentException(
"Mismatch on option values key/value pair. The option key does "
+ "not the associated CommandLineValue's option. optionKey=[ "
+ option + " ], optionValue=[ " + optionValue.getOption() + " ]");
}
if (!typeChainSet.contains(option.getClass())) {
throw new IllegalArgumentException(
"The specified option values map contains an option of an illegal "
+ "type given the specified CommandLineOption type. found=[ "
+ option + " ], foundType=[ " + option.getClass()
+ " ], expected=[ " + typeChainSet + " ]");
}
});
// check if we need a primary option
Set primaryOptions = getPrimaryOptions(enumClass);
boolean primaryRequired = primaryOptions.size() > 0;
if (primaryRequired) {
// if primary option is required then check for at least one
int primaryCount = 0;
for (CommandLineOption option : optionValues.keySet()) {
// check if primary
if (option.isPrimary()) {
primaryCount++;
}
}
if (primaryCount == 0) {
throw new NoPrimaryOptionException(primaryOptions);
}
}
// check for conflicts and dependencies
for (CommandLineValue cmdLineValue : optionValues.values()) {
// check if this is a default value
if (cmdLineValue.getSource() == DEFAULT) continue;
// get the option
CommandLineOption option = (CommandLineOption) cmdLineValue.getOption();
// check for deprecation
if (option.isDeprecated()) {
deprecatedList.add(new DeprecatedOptionWarning(cmdLineValue));
}
// check for conflicts
Set conflicts = option.getConflicts();
// get the dependencies
Set> dependencies = option.getDependencies();
if (conflicts != null) {
for (CommandLineOption conflict : conflicts) {
// skip if the same option -- cannot conflict with itself
if (option == conflict) continue;
// check if the conflict is present
CommandLineValue conflictValue = optionValues.get(conflict);
if (conflictValue != null) {
if (conflictValue.getSource() == DEFAULT) continue;
throw new ConflictingOptionsException(cmdLineValue, conflictValue);
}
}
}
boolean satisfied = (dependencies == null || dependencies.size() == 0);
if (!satisfied) {
for (Set dependencySet : dependencies) {
if (optionValues.keySet().containsAll(dependencySet)) {
satisfied = true;
break;
}
}
}
if (!satisfied) {
throw new MissingDependenciesException(cmdLineValue.getSource(),
cmdLineValue.getOption(),
cmdLineValue.getSpecifier(),
optionValues.keySet());
}
}
// return the list of deprecation warnings
return (deprecatedList.size() == 0) ? null : deprecatedList;
}
/**
* Creates a lookup map of {@link String} command-line flags to the
* {@link CommandLineOption} values that they map to. This includes any
* synonyms for the options if they exist.
*
* @param enumClass The enumerated class for the {@link CommandLineOption}.
*/
private static Map createFlagLookupMap(Class enumClass)
{
// get all the options
EnumSet> enumSet = EnumSet.allOf((Class) enumClass);
// create a lookup map for the flags
Map lookupMap = new LinkedHashMap<>();
for (Enum> optionEnum : enumSet) {
// cast to a CommandLineOption
CommandLineOption option = (CommandLineOption) optionEnum;
// get the flag
String flag = option.getCommandLineFlag();
// do a sanity check
if (lookupMap.containsKey(flag)) {
throw new IllegalStateException(
"Command-line flag (" + flag + ") cannot resolve to different "
+ "options (" + lookupMap.get(flag) + " and " + option
+ "). It must be unique.");
}
// add the primary flag to the lookup map
lookupMap.put(flag, option);
// add the synonym flags if any
Set synonymFlags = option.getSynonymFlags();
if (synonymFlags == null) synonymFlags = Collections.emptySet();
for (String synonym : synonymFlags) {
// check the synonym against the primary flag (sanity check)
if (synonym.equals(flag)) {
throw new IllegalStateException(
"Synonym command-line (" + flag + ") for option (" + option
+ ") is the same as the primary flag.");
}
// do a sanity check
if (lookupMap.containsKey(synonym)) {
throw new IllegalStateException(
"Command-line flag (" + flag + ") cannot resolve to different "
+ "options (" + lookupMap.get(flag) + " and " + option
+ "). It must be unique.");
}
// add to the lookup map
lookupMap.put(synonym, option);
}
}
// get the base type
Class baseType
= ((CommandLineOption) enumSet.iterator().next()).getBaseOptionType();
// check if not null
if (baseType != null) {
Map baseMap = createFlagLookupMap(baseType);
baseMap.keySet().forEach(flag -> {
if (lookupMap.containsKey(flag)) {
CommandLineOption option = lookupMap.get(flag);
CommandLineOption baseOption = baseMap.get(flag);
throw new IllegalStateException(
"Command-line flag (" + flag + ") for " + option + " in "
+ enumClass + " conflicts with " + baseOption + " in the "
+ baseType + " base option class.");
}
});
// add the base map entries
lookupMap.putAll(baseMap);
}
// return the lookup map
return lookupMap;
}
/**
* Parses the command line arguments and returns a {@link Map} of those
* arguments. This function will attempt to find values for command-line
* options that are not explicitly specified on the command line by using
* environment variables for those options have environment variable
* equivalents defined.
*
* @param enumClass The enumerated {@link CommandLineOption} class.
* @param args The arguments to parse.
* @param processor The {@link ParameterProcessor} to use for handling the
* parameters to the options.
* @param deprecationWarnings The {@link List} to be populated with any
* {@link DeprecatedOptionWarning} instances that
* are generated, or null
if the
* caller is not interested in deprecation
* warnings.
*
* @return The {@link Map} to populate with the result of the parsing.
*
* @throws CommandLineException If a command-line option parsing error occurs.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
Map parseCommandLine(
Class enumClass,
String[] args,
ParameterProcessor processor,
List deprecationWarnings)
throws CommandLineException
{
return parseCommandLine(enumClass,
args,
processor,
false,
null,
deprecationWarnings);
}
/**
* Parses the command line arguments and returns a {@link Map} of those
* arguments. Depending on the specified value for
* ignoreEnvironment
this function will optionally attempt to
* find values for command-line options that are not explicitly specified on
* the command line by using environment variables for those options have
* environment variable equivalents defined.
*
* @param enumClass The enumerated {@link CommandLineOption} class.
* @param args The arguments to parse.
* @param processor The {@link ParameterProcessor} to use for handling the
* parameters to the options.
* @param ignoreEnvironment Flag indicating if the environment variables
* should be ignored in the processing.
* @param deprecationWarnings The {@link List} to be populated with any
* {@link DeprecatedOptionWarning} instances that
* are generated, or null
if the
* caller is not interested in deprecation
* warnings.
*
* @return The {@link Map} to populate with the result of the parsing.
*
* @throws CommandLineException If a command-line option parsing error occurs.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
Map parseCommandLine(
Class enumClass,
String[] args,
ParameterProcessor processor,
boolean ignoreEnvironment,
List deprecationWarnings)
throws CommandLineException
{
return parseCommandLine(enumClass,
args,
processor,
false,
null,
deprecationWarnings);
}
/**
* Parses the command line arguments and returns a {@link Map} of those
* arguments. This function will optionally attempt to find values for
* command-line options that are not explicitly specified on the command line
* by using environment variables for those options have environment variable
* equivalents defined. Use of the environment is disabled if the
* ignoreEnvOption
parameter is non-null and that option is present
* in the explicitly specified command-line arguments and either has no
* parameter value or any value other than false
.
*
* @param enumClass The enumerated {@link CommandLineOption} class.
* @param args The arguments to parse.
* @param processor The {@link ParameterProcessor} to use for handling the
* parameters to the options.
* @param ignoreEnvOption The optional command-line option value that if
* present with no value or present with any value
* other than false
will cause the environment
* to be ignored in processing.
*
* @param deprecationWarnings The {@link List} to be populated with any
* {@link DeprecatedOptionWarning} instances that
* are generated, or null
if the
* caller is not interested in deprecation
* warnings.
*
* @return The {@link Map} to populate with the result of the parsing.
*
* @throws CommandLineException If a command-line option parsing error occurs.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
public static & CommandLineOption,
B extends Enum & CommandLineOption>
Map parseCommandLine(
Class enumClass,
String[] args,
ParameterProcessor processor,
CommandLineOption ignoreEnvOption,
List deprecationWarnings)
throws CommandLineException
{
return parseCommandLine(enumClass,
args,
processor,
false,
ignoreEnvOption,
deprecationWarnings);
}
/**
* Parses the command line arguments and returns a {@link Map} of those
* arguments.
*
* @param enumClass The enumerated {@link CommandLineOption} class.
* @param args The arguments to parse.
* @param processor The {@link ParameterProcessor} to use for handling the
* parameters to the options.
* @param ignoreEnvironment Flag indicating if the environment variables
* should be ignored in the processing.
* @param ignoreEnvOption The option to trigger ignoring the environment.
*
* @param deprecationWarnings The {@link List} to be populated with any
* {@link DeprecatedOptionWarning} instances that
* are generated, or null
if the
* caller is not interested in deprecation
* warnings.
*
* @return The {@link Map} of {@link CommandLineOption} keys to {@link
* CommandLineValue} values that are the result of the parsing.
*
* @throws CommandLineException If the specified command line arguments fail
* to parse.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
Map parseCommandLine(
Class enumClass,
String[] args,
ParameterProcessor processor,
boolean ignoreEnvironment,
CommandLineOption ignoreEnvOption,
List deprecationWarnings)
throws CommandLineException
{
Map result = new LinkedHashMap<>();
// create a lookup map for the flags to their options
Map lookupMap = createFlagLookupMap(enumClass);
// iterate over the args and build a map
Map usedFlags = new LinkedHashMap<>();
for (int index = 0; index < args.length; index++) {
// get the next flag
String flag = args[index];
// determine the option from the flag
CommandLineOption option = lookupMap.get(flag);
// check if the option is recognized
if (option == null) {
throw new UnrecognizedOptionException(
flag, "Unrecognized command line option: " + flag);
}
// check if the option has already been specified
String usedFlag = usedFlags.get(option);
if (usedFlag != null) {
Set flags = Set.of(flag, usedFlag);
throw new RepeatedOptionException(option, flags);
}
// get the option parameters
int minParamCount = option.getMinimumParameterCount();
int maxParamCount = option.getMaximumParameterCount();
if (maxParamCount >= 0 && maxParamCount < minParamCount) {
throw new IllegalStateException(
"The non-negative maximum parameter count is less than the minimum "
+ "parameter count. min=[ " + minParamCount + " ], max=[ "
+ maxParamCount + " ], option=[ " + option + " ]");
}
List params = new ArrayList<>(maxParamCount<0 ? 5:maxParamCount);
if (minParamCount > 0) {
// check if there are enough parameters
int max = index + minParamCount;
for (index++; index <= max; index++) {
if (index >= args.length) {
throw new BadOptionParameterCountException(
COMMAND_LINE, option, flag, params);
}
// add the parameter to the list
params.add(args[index]);
}
// back up the index by one so when we advance it later it is correct
index--;
}
// get the default parameters
List defaultParams = option.getDefaultParameters();
// check if we have a zero-argument parameter
if (minParamCount == 0 && maxParamCount == 0
&& defaultParams != null
&& defaultParams.size() == 1
&& "false".equalsIgnoreCase(defaultParams.get(0)))
{
// allow a zero-argument parameter to be followed by "true" or "false"
if (args.length > (index + 1)) {
// we have arguments after this one -- check the next one
String param = args[index+1].trim().toLowerCase();
// check if it is a parameter or a command-line flag
if (!param.startsWith("-")) {
// looks like a parameter since a flag starts with "-"
switch (param) {
case "true":
case "false":
params.add(args[++index].trim().toLowerCase());
break;
default:
throw new BadOptionParametersException(
COMMAND_LINE, option, flag, List.of(param),
"The " + flag + " command line option can be specified "
+ "with no parameters, but if a parameter is provided it "
+ "must be \"true\" or \"false\": " + args[index + 1]);
}
}
}
} else {
// get the parameters from the command line
int bound = (maxParamCount < 0)
? args.length
: index + (maxParamCount - minParamCount) + 1;
if (bound > args.length) bound = args.length;
for (int nextIndex = index + 1;
nextIndex < bound && !lookupMap.containsKey(args[nextIndex]);
nextIndex++)
{
params.add(args[nextIndex]);
index++;
}
// check if there are more (unexpected) parameters
for (int nextIndex = index + 1;
(nextIndex < args.length && !args[nextIndex].startsWith("-")
&& !lookupMap.containsKey(args[index]));
nextIndex++)
{
params.add(args[nextIndex]);
index++;
}
// check if too many parameters
if (maxParamCount >= 0 && params.size() > maxParamCount) {
throw new BadOptionParameterCountException(
COMMAND_LINE, option, flag, params);
}
}
defaultParams = option.getDefaultParameters();
// check if we have a zero-parameter option with a "false" default
if (minParamCount == 0 && maxParamCount == 0 && params.size() == 0
&& defaultParams != null
&& defaultParams.size() == 1
&& "false".equalsIgnoreCase(defaultParams.get(0)))
{
params.add("true");
}
// process the parameters
Object processedValue = processValue(
COMMAND_LINE, option, flag, processor, params);
// create the command-line value
CommandLineValue cmdLineVal = new CommandLineValue(COMMAND_LINE,
option,
flag,
processedValue,
params);
// add to the options map
putValue(result, cmdLineVal);
}
// optionally process the environment
ignoreEnvironment
= (ignoreEnvironment
|| (result.containsKey(ignoreEnvOption)
&& (!Boolean.FALSE.equals(result.get(ignoreEnvOption)))));
if (!ignoreEnvironment) {
try {
processEnvironment(enumClass, processor, result);
} catch (NullPointerException e) {
e.printStackTrace();
throw e;
}
}
try {
// handle setting the default values
processDefaults(enumClass, processor, result);
} catch (NullPointerException e) {
e.printStackTrace();
throw e;
}
// validate the options
try {
List warnings
= validateOptions(enumClass, result);
if (deprecationWarnings != null && warnings != null) {
deprecationWarnings.addAll(warnings);
}
return result;
} catch (CommandLineException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
/**
* Handles processing the specified parameters for the specified option.
*
* @param option The relevant option.
* @param processor The {@link ParameterProcessor}, or null
if none.
* @param params The {@link List} of {@link String} parameters.
* @return The processed value.
* @throws BadOptionParametersException If the parameter values cannot be
* processed without an error.
*/
private static Object processValue(
CommandLineSource source,
CommandLineOption option,
String specifier,
ParameterProcessor processor,
List params)
throws BadOptionParametersException
{
// process the parameters
if (processor != null) {
// handle the case where a processor is defined for the parameters
Object value = null;
try {
value = processor.process(option, params);
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
pw.println("Bad parameters for " + option.getCommandLineFlag()
+ " option:");
for (String p : params) {
pw.println(" o " + p);
}
pw.println();
pw.println(e.getMessage());
pw.flush();
throw new BadOptionParametersException(
source, option, specifier, params, sw.toString());
}
return value;
} else if (params.size() == 0) {
// handle the case of no parameters and no parameter processor
return null;
} else if (params.size() == 1) {
// handle the case of one parameter and no parameter processor
return params.get(0);
} else {
// handle the case of multiple parameters and no parameter processor
return params.toArray();
}
}
/**
* Check for command-line option values in the environment.
* @param enumClass The {@link Class} for the enumerated type that
* implements {@link CommandLineOption}.
* @param processor The {@link ParameterProcessor} to use.
* @param optionValues The {@link Map} of {@link CommandLineOption} keys
* to {@link CommandLineValue} values.
* @throws CommandLineException If a command-line option processing error
* occurs.
*
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
void processEnvironment(
Class enumClass,
ParameterProcessor processor,
Map optionValues)
throws CommandLineException
{
Set options = new LinkedHashSet<>();
populateOptionsChain(options, enumClass);
processEnvironment(options,
processor,
optionValues,
false);
// create a set if fallback options to check
Set fallbackOptions = new LinkedHashSet<>();
// check fallbacks for primary options if not specified otherwise
Set primaryOptions = getPrimaryOptions(enumClass);
for (CommandLineOption primaryOption : primaryOptions) {
// check if not contained
if (!optionValues.containsKey(primaryOption)) {
List fallbacks = primaryOption.getEnvironmentFallbacks();
if (fallbacks != null && fallbacks.size() > 0) {
fallbackOptions.add(primaryOption);
}
}
}
Set extends CommandLineOption> optionKeys = optionValues.keySet();
for (CommandLineOption option : optionKeys) {
// get the dependency sets
Set> dependencySets = option.getDependencies();
// check if no dependencies
if (dependencySets == null) continue;
if (dependencySets.size() == 0) continue;
// set the flag indicating the dependencies are not satisfied
boolean satisfied = false;
// iterate over the dependency sets and see if at least one is satisfied
for (Set dependencySet: dependencySets) {
if (optionValues.keySet().containsAll(dependencySet)) {
satisfied = true;
break;
}
}
// check if the option is not satisfied
if (!satisfied) {
// find the first dependency set with missing items with fallbacks
for (Set dependencySet: dependencySets) {
// set the fallback count to zero and create the missing set
int fallbackCount = 0;
Set missingSet = new LinkedHashSet<>();
// iterate over the options
for (CommandLineOption dependency: dependencySet) {
// check if dependency is missing
if (optionValues.containsKey(dependency)) continue;
// add to the missing set
missingSet.add(dependency);
// check if the missing item has fallbacks
List fallbacks = dependency.getEnvironmentFallbacks();
if (fallbacks != null && fallbacks.size() > 0) fallbackCount++;
}
// check if the fallback count and missing count are equal and if
// so then use this dependency set
if (missingSet.size() > 0 && missingSet.size() == fallbackCount) {
fallbackOptions.addAll(missingSet);
break;
}
}
}
}
// check the fallback options
if (fallbackOptions.size() > 0) {
processEnvironment(fallbackOptions, processor, optionValues, true);
}
}
/**
* Checks for command-line option values in the environment.
*
* @throws CommandLineException If the specified environment command line
* arguments are illegal.
*/
private static void processEnvironment(
Set extends CommandLineOption> enumSet,
ParameterProcessor processor,
Map optionValues,
boolean fallBacks)
throws CommandLineException
{
// get the environment
Map env = System.getenv();
// iterate over the options
for (CommandLineOption option: enumSet) {
// prefer explicit command-line arguments, so skip if already present
if (optionValues.containsKey(option)) continue;
List envVars = null;
if (fallBacks) {
// get the fallbacks
envVars = option.getEnvironmentFallbacks();
if (envVars == null) envVars = Collections.emptyList();
} else {
// get the environment variable and its synonyms
String envVar = option.getEnvironmentVariable();
Set synonymSet = option.getEnvironmentSynonyms();
envVars = new ArrayList<>(synonymSet==null ? 1 : synonymSet.size() + 1);
if (envVar != null) envVars.add(envVar);
if (synonymSet != null) envVars.addAll(synonymSet);
}
// iterate over the items in the list
String envVal = null;
String foundVar = null;
for (String envVar: envVars) {
// check if null
if (envVar == null) continue;
// check if contained in the environment
envVal = env.get(envVar);
if (envVal != null) {
foundVar = envVar;
break;
}
}
// check if no value
if (envVal == null) continue;
// process the env value
int minParamCount = option.getMinimumParameterCount();
int maxParamCount = option.getMaximumParameterCount();
if (maxParamCount > 0 && maxParamCount < minParamCount) {
throw new IllegalStateException(
"The non-negative maximum parameter count is less than the minimum "
+ "parameter count. min=[ " + minParamCount + " ], max=[ "
+ maxParamCount + " ], option=[ " + option + " ]");
}
List params = null;
// check the number of parameters
if (minParamCount == 0 && maxParamCount == 0) {
// no parameters, these are boolean on/off options
switch (envVal.trim().toLowerCase()) {
case "":
case "true":
params = List.of("true");
break;
case "false":
params = List.of("false");
break;
default:
throw new IllegalArgumentException(
"The specified value for the " + foundVar
+ " environment variable can only be \"true\""
+ " or \"false\": " + foundVar);
}
} else if (minParamCount <= 1 && maxParamCount == 1) {
// handle the single parameter case
params = List.of(envVal);
} else if ((maxParamCount > 1 || maxParamCount < 0)
&& (JSON_ARRAY_PATTERN.matcher(envVal).matches()))
{
// handle the case of multiple parameters and a JSON array
JsonArray jsonArray = JsonUtilities.parseJsonArray(envVal.trim());
params = new ArrayList<>(jsonArray.size());
for (JsonString jsonString: jsonArray.getValuesAs(JsonString.class)) {
params.add(jsonString.getString());
}
} else if (maxParamCount > 1 || maxParamCount < 0) {
// handle the case of multiple parameters, splitting on commas & spaces
String[] tokens = envVal.split("(\\s*,\\s*|\\s+)");
params = Arrays.asList(tokens);
}
// get the parameter count
int paramCount = params.size();
// check the parameter count
if (paramCount != 1 || minParamCount != 0 || maxParamCount != 0) {
// handle the options with parameters
if (paramCount < minParamCount) {
throw new BadOptionParameterCountException(
ENVIRONMENT, option, foundVar, params);
}
if ((maxParamCount >= 0) && (paramCount > maxParamCount)) {
throw new BadOptionParameterCountException(
ENVIRONMENT, option, foundVar, params);
}
}
// process the parameters
Object processedValue = processValue(
ENVIRONMENT, option, foundVar, processor, params);
// create the command line value
CommandLineValue cmdLineVal = new CommandLineValue(ENVIRONMENT,
option,
foundVar,
processedValue,
params);
// put the value
putValue(optionValues, cmdLineVal);
}
}
/**
* Check for command-line option values in the environment.
*
* @param enumClass The option enum class that implements {@link
* CommandLineOption}.
* @param processor The {@link ParameterProcessor} for processing the
* parameters.
* @param optionValues The {@link Map} of {@link CommandLineOption} keys to
* {@link CommandLineValue} values.
*
* @throws CommandLineException If a command-line option processing error
* occurs.
* @param The enumerated type that implements {@link CommandLineOption}.
* @param The base enumerated type that the command-line options extend,
* OR the same as type T
if the command-line
* option type has no base and returns null
from
* {@link CommandLineOption#getBaseOptionType()}.
*/
private static & CommandLineOption,
B extends Enum & CommandLineOption>
void processDefaults(Class enumClass,
ParameterProcessor processor,
Map optionValues)
throws CommandLineException
{
Set options = new LinkedHashSet<>();
populateOptionsChain(options, enumClass);
// iterate over the options
for (CommandLineOption option: options) {
// prefer explicit command-line arguments, so skip if already present
if (optionValues.containsKey(option)) continue;
// get the default parameters
List params = option.getDefaultParameters();
// check if there are none
if (params == null || params.size() == 0) continue;
// process the parameters
Object processedValue = processValue(
DEFAULT, option, null, processor, params);
// create the command line value
CommandLineValue cmdLineVal = new CommandLineValue(DEFAULT,
option,
processedValue,
params);
// put the value
putValue(optionValues, cmdLineVal);
}
}
/**
* Processes the {@link Map} of {@link CommandLineOption} keys to {@link
* CommandLineValue} values and produces the {@link Map} of {@link
* CommandLineOption} keys to {@linkplain CommandLineValue#getProcessedValue()
* processed values}.
*
* @param optionValues The {@link Map} of option keys to {@link
* CommandLineValue} values.
* @param processedValues The {@link Map} to populate with the processed
* values if specified, if null
a new {@link
* Map} is created and returned.
* @return The {@link Map} of {@link CommandLineOption} keys to {@link Object}
* values describing the parameters for the option.
*/
public static Map processCommandLine(
Map optionValues,
Map processedValues)
{
return processCommandLine(optionValues,
processedValues,
null,
null);
}
/**
* Processes the {@link Map} of {@link CommandLineOption} keys to {@link
* CommandLineValue} values and produces the {@link Map} of {@link
* CommandLineOption} keys to {@linkplain CommandLineValue#getProcessedValue()
* processed values}.
*
* @param optionValues The {@link Map} of option keys to {@link
* CommandLineValue} values.
* @param processedValues The {@link Map} to populate with the processed
* values if specified, if null
a new {@link
* Map} is created and returned.
* @param jsonBuilder If not null
then this {@link JsonObjectBuilder}
* is populated with startup option information.
* @return The {@link Map} of {@link CommandLineOption} keys to {@link Object}
* values describing the parameters for the option.
*/
public static Map processCommandLine(
Map optionValues,
Map processedValues,
JsonObjectBuilder jsonBuilder)
{
return processCommandLine(optionValues,
processedValues,
jsonBuilder,
null);
}
/**
* Processes the {@link Map} of {@link CommandLineOption} keys to {@link
* CommandLineValue} values and produces the {@link Map} of {@link
* CommandLineOption} keys to {@linkplain CommandLineValue#getProcessedValue()
* processed values}.
*
* @param optionValues The {@link Map} of option keys to {@link
* CommandLineValue} values.
* @param processedValues The {@link Map} to populate with the processed
* values if specified, if null
a new {@link
* Map} is created and returned.
* @param stringBuilder If not null
then this {@link StringBuilder}
* is populated with JSON text describing the startup
* options.
* @return The {@link Map} of {@link CommandLineOption} keys to {@link Object}
* values describing the parameters for the option.
*/
public static Map processCommandLine(
Map optionValues,
Map processedValues,
StringBuilder stringBuilder)
{
return processCommandLine(optionValues,
processedValues,
null,
stringBuilder);
}
/**
* Processes the {@link Map} of {@link CommandLineOption} keys to {@link
* CommandLineValue} values and produces the {@link Map} of {@link
* CommandLineOption} keys to {@linkplain CommandLineValue#getProcessedValue()
* processed values}.
*
* @param optionValues The {@link Map} of option keys to {@link
* CommandLineValue} values.
* @param processedValues The {@link Map} to populate with the processed
* values if specified, if null
a new {@link
* Map} is created and returned.
* @param jsonBuilder If not null
then this {@link JsonObjectBuilder}
* is populated with startup option information.
* @param stringBuilder If not null
then this {@link StringBuilder}
* is populated with JSON text describing the startup
* options.
* @return The {@link Map} of {@link CommandLineOption} keys to {@link Object}
* values describing the parameters for the option.
*/
public static Map processCommandLine(
Map optionValues,
Map processedValues,
JsonObjectBuilder jsonBuilder,
StringBuilder stringBuilder)
{
// create the result map if not specified
Map result
= (processedValues == null) ? new LinkedHashMap<>() : processedValues;
// check if we are generating the JSON
boolean doJson = (jsonBuilder != null || stringBuilder != null);
// check if we need to create the JSON object builder
JsonObjectBuilder job = (doJson && (jsonBuilder == null || stringBuilder != null))
? Json.createObjectBuilder() : jsonBuilder;
// iterate over the option values and handle them
optionValues.forEach( (key, cmdLineVal) -> {
// confirm the option is consistent between the key and value
if (key != cmdLineVal.getOption()) {
throw new IllegalArgumentException(
"CommandLineOption key does not match the option from the paired "
+ "CommandLineValue value. key=[ " + key + " ], value=[ "
+ cmdLineVal + " ]");
}
// create the sub-builder if doing JSON
JsonObjectBuilder job2 = (doJson) ? Json.createObjectBuilder() : null;
// get the parameters
List params = cmdLineVal.getParameters();
// add the parameters if doing JSON
if (doJson) {
// check if there is only a single parameter
if (key.isSensitive()) {
// handle sensitive values
job2.add("value", REDACTED_SENSITIVE_VALUE);
} else if (params.size() == 1) {
// handle a single parameter
job2.add("value", params.get(0));
} else {
// handle multiple parameters
JsonArrayBuilder jab = Json.createArrayBuilder();
for (String param : params) {
jab.add(param);
}
job2.add("values", jab);
}
// add the source
job2.add("source", cmdLineVal.getSource().toString());
// check if we have a specifier to add
if (cmdLineVal.getSpecifier() != null) {
// add the specifier
job2.add("via", cmdLineVal.getSpecifier());
}
// add to the JsonObjectBuilder
JsonObject jsonObject = job2.build();
job.add(key.toString(), jsonObject);
if (jsonBuilder != null && job != jsonBuilder) {
jsonBuilder.add(key.toString(), jsonObject);
}
}
// add to the map
result.put(key, cmdLineVal.getProcessedValue());
});
// append the JSON text if requested
if (stringBuilder != null) {
stringBuilder.append(JsonUtilities.toJsonText(job));
}
// return the map
return result;
}
/**
* Checks if the specified {@link Class} was the class whose static
* main(String[])
function was called to begin execution of the
* current process.
*
* @param cls The {@link Class} to test for.
*
* @return true
if the specified class' static
* main(String[])
function was called to begin execution of
* the current process.
*/
public static boolean checkClassIsMain(Class cls) {
// check if called from the ConfigurationManager.main() directly
Throwable t = new Throwable();
StackTraceElement[] trace = t.getStackTrace();
StackTraceElement lastStackFrame = trace[trace.length-1];
String className = lastStackFrame.getClassName();
String methodName = lastStackFrame.getMethodName();
return ("main".equals(methodName) && cls.getName().equals(className));
}
/**
* Returns a multi-line bulleted list of the specified options with the
* specified indentation.
*
* @param indent The number of spaces to indent.
* @param options The zero or more options to write.
*
* @return The multi-line bulleted list of options.
*/
public static String formatUsageOptionsList(int indent,
CommandLineOption... options)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
int maxLength = 0;
StringBuilder sb = new StringBuilder();
for (int index = 0; index < indent; index++) {
sb.append(" ");
}
String indenter = sb.toString();
for (CommandLineOption option : options) {
if (option.getCommandLineFlag().length() > maxLength) {
maxLength = option.getCommandLineFlag().length();
}
}
String spacer = " ";
String bullet = "o ";
maxLength += (bullet.length() + spacer.length());
// check how many options per line we can fit
int columnCount = (80 - indent - spacer.length()) / maxLength;
// check if we can balance things out if we have a single dangling option
if (options.length == columnCount + 1) {
columnCount--;
}
// if less than 1, then set a minimum of 1 column
if (columnCount < 1) columnCount = 1;
int columnIndex = 0;
for (CommandLineOption option: options) {
String flag = option.getCommandLineFlag();
int spaceCount = maxLength - flag.length() - bullet.length();
if (columnIndex == 0) pw.print(indenter);
else pw.print(spacer);
pw.print(bullet);
pw.print(flag);
for (int index = 0; index < spaceCount; index++) {
pw.print(" ");
}
if (columnIndex == columnCount - 1) {
pw.println();
columnIndex = 0;
} else {
columnIndex++;
}
}
if (columnIndex != 0) {
pw.println();
}
pw.flush();
return sw.toString();
}
/**
* Gets the base URL for the JAR file where the specified {@link Class} is
* found. This method returns null
if the JAR base URL cannot be
* determined.
*
* @param cls The {@link Class} for which the JAR base URL is requested.
*
* @return The base URL for the JAR that has the specified class, or
* null
if it cannot be determined.
*/
public static String getJarBaseUrl(Class cls) {
String simpleName = cls.getSimpleName();
String url = cls.getResource(simpleName + ".class").toString();
int index = url.lastIndexOf(
cls.getName().replace(".", "/") + ".class");
if (index < 0) return null;
return url.substring(0, index);
}
/**
* Gets the name of the JAR file that contains the specified {@link Class}.
* This method returns null
if the JAR file name cannot be
* determined.
*
* @param cls The {@link Class} for which the jar file name is requested.
*
* @return The name of the JAR file that has the specified class, or
* null
if it cannot be determined.
*/
public static String getJarName(Class cls) {
String url = getJarBaseUrl(cls);
if (url == null) return null;
if (url.indexOf(".jar") >= 0) {
int index = url.lastIndexOf("!");
if (index >= 0) {
url = url.substring(0, index);
index = url.lastIndexOf("/");
if (index >= 0) {
return url.substring(index + 1);
}
}
}
return null;
}
/**
* Gets the path of the JAR file that contains the specified {@link Class}.
* This method returns null
if the JAR file path cannot be
* determined.
*
* @param cls The {@link Class} for which the jar file path is requested.
*
* @return The path to the JAR file that has the specified class, or
* null
if it cannot be determined.
*/
public static String getJarPath(Class cls) {
String url = getJarBaseUrl(cls);
if (url == null) return null;
if (url.indexOf(".jar") >= 0) {
int index = url.lastIndexOf("!");
if (index >= 0) {
url = url.substring(0, index);
index = url.lastIndexOf("/");
if (index >= 0) {
url = url.substring(0, index);
index = url.indexOf("/");
if (index >= 0) {
return url.substring(index);
}
}
}
}
return null;
}
public static void main(String[] args) {
try {
Class cls = CommandLineUtilities.class;
System.out.println("BASE URL: " + getJarBaseUrl(cls));
System.out.println("JAR NAME: " + getJarName(cls));
System.out.println("JAR PATH: " + getJarPath(cls));
} catch (Exception e) {
e.printStackTrace();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy