/* * Copyright 2015 herd contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.finra.herd.core; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collection; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.propertyeditors.CustomBooleanEditor; import org.springframework.util.Assert; /** * This class makes it easy to write user-friendly command-line interfaces. It is basically a wrapper around Apache Commons CLI. */ public class ArgumentParser { protected String applicationName; protected Options options; // All parameters where required parameters are truly marked as "required". protected Options optionsIgnoreRequired; // All parameters where each is marked as optional - including required ones. protected CommandLine commandLine; /** * @param applicationName The application name. */ public ArgumentParser(String applicationName) { this.applicationName = applicationName; options = new Options(); optionsIgnoreRequired = new Options(); } /** * Returns the application name that this instance of ArgumentParser was created for. * * @return the application name. */ public String getApplicationName() { return applicationName; } /** * Adds an option instance. It may be specified as being mandatory. * * @param option the option that is to be added * @param required specifies whether the option being added is mandatory * * @return the option that was added */ public Option addArgument(Option option, boolean required) { optionsIgnoreRequired.addOption((Option) option.clone()); option.setRequired(required); options.addOption(option); return option; } /** * Creates and adds an Option using the specified parameters. The parameters contain a short-name and a long-name. It may be specified as requiring an * argument and/or being mandatory. * * @param opt Short single-character name of the option. * @param longOpt Long multi-character name of the option. * @param hasArg flag signally if an argument is required after this option * @param description Self-documenting description * @param required specifies whether the option being added is mandatory * * @return the resulting option that was added */ public Option addArgument(String opt, String longOpt, boolean hasArg, String description, boolean required) { Option option = new Option(opt, longOpt, hasArg, description); return addArgument(option, required); } /** * Retrieve a read-only list of options in this set * * @return read-only Collection of {@link Option} objects in this descriptor */ public Collection getConfiguredOptions() { return options.getOptions(); } /** * Parses the arguments according to the specified options and properties. * * @param args the command line arguments passed to the program * @param failOnMissingRequiredOptions specifies whether to fail on any missing mandatory options */ public void parseArguments(String[] args, boolean failOnMissingRequiredOptions) throws ParseException { CommandLineParser parser = new DefaultParser(); commandLine = parser.parse(failOnMissingRequiredOptions ? options : optionsIgnoreRequired, args); } /** * Parses the arguments according to the specified options. The parsing fails when any of the required arguments are missing. * * @param args the command line arguments passed to the program */ public void parseArguments(String[] args) throws ParseException { parseArguments(args, true); } /** * Returns a help message, including the program usage and information about the arguments registered with the ArgumentParser. * * @return the usage information about the arguments registered with the ArgumentParser. */ public String getUsageInformation() { HelpFormatter formatter = new HelpFormatter(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); formatter.printHelp(pw, formatter.getWidth(), applicationName, null, options, formatter.getLeftPadding(), formatter.getDescPadding(), null, false); pw.flush(); return sw.toString(); } /** * Query to see if an option has been set. * * @param option the option that we want to query for * * @return true if set, false if not */ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "This is a false positive. A null check is present.") public Boolean getBooleanValue(Option option) throws IllegalStateException { ensureCommandLineNotNull(); return commandLine.hasOption(option.getOpt()); } /** * Retrieves the argument value, if any, as a String object and converts it to a boolean value. * * @param option the option that we want to query for * @param defaultValue the default value to return if option is not set or missing an argument value * * @return the value of the argument converted to a boolean value or default value when the option is not set or missing an argument value * @throws ParseException if the value of the argument is an invalid boolean value */ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "This is a false positive. A null check is present.") public Boolean getStringValueAsBoolean(Option option, Boolean defaultValue) throws ParseException { Boolean result; ensureCommandLineNotNull(); String stringValue = getStringValue(option); if (StringUtils.isNotBlank(stringValue)) { // Use custom boolean editor without allowed empty strings to convert the value of the argument to a boolean value. CustomBooleanEditor customBooleanEditor = new CustomBooleanEditor(false); try { customBooleanEditor.setAsText(stringValue); } catch (IllegalArgumentException e) { ParseException parseException = new ParseException(e.getMessage()); parseException.initCause(e); throw parseException; } result = (Boolean) customBooleanEditor.getValue(); } else { result = defaultValue; } return result; } /** * Retrieves the argument as a String object, if any, of an option. * * @param option the option that we want argument value to be returned for * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. */ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "This is a false positive. A null check is present.") public String getStringValue(Option option) throws IllegalStateException { ensureCommandLineNotNull(); return commandLine.getOptionValue(option.getOpt()); } /** * Ensures that the command line is not null. If it is null, an exception will be thrown. * * @throws IllegalStateException if the command line is null. */ private void ensureCommandLineNotNull() throws IllegalStateException { Assert.notNull(commandLine, "The command line hasn't been initialized."); } /** * Retrieves the argument as a String object, if any, of an option. * * @param option the option that we want argument value to be returned for * @param defaultValue is the default value to be returned if the option is not specified * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. */ public String getStringValue(Option option, String defaultValue) { String answer = getStringValue(option); return (answer != null) ? answer : defaultValue; } /** * Retrieves the argument as an Integer object, if any, of an option. * * @param option the option that we want argument value to be returned for * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. * @throws NumberFormatException if there are problems parsing the option value into the Integer type */ public Integer getIntegerValue(Option option) throws NumberFormatException { String value = getStringValue(option); return (value != null) ? Integer.parseInt(value) : null; } /** * Retrieves the argument as an Integer object, if any, of an option. * * @param option the option that we want argument value to be returned for * @param defaultValue is the default value to be returned if the option is not specified * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. * @throws NumberFormatException if there are problems parsing the option value into the Integer type */ public Integer getIntegerValue(Option option, Integer defaultValue) throws NumberFormatException { Integer answer = getIntegerValue(option); return (answer != null) ? answer : defaultValue; } /** * Retrieves the argument as an Integer object, if any, of an option and validates it against minimum and maximum allowed values. * * @param option the option that we want argument value to be returned for * @param defaultValue is the default value to be returned if the option is not specified * @param minValue the minimum allowed Integer value for the option * @param maxValue the maximum allowed Integer value for the option * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. * @throws IllegalArgumentException if there are problems with the option value */ public Integer getIntegerValue(Option option, Integer defaultValue, Integer minValue, Integer maxValue) throws IllegalArgumentException { Integer answer = getIntegerValue(option, defaultValue); if (answer != null) { Assert.isTrue(answer.compareTo(minValue) >= 0, String.format("The %s option value %d is less than the minimum allowed value of %d.", option.getLongOpt(), answer, minValue)); Assert.isTrue(answer.compareTo(maxValue) <= 0, String.format("The %s option value %d is bigger than maximum allowed value of %d.", option.getLongOpt(), answer, maxValue)); } return answer; } /** * Retrieves the argument as a File object, if any, of an option. * * @param option the option that we want argument value to be returned for * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. */ public File getFileValue(Option option) { String value = getStringValue(option); return (value != null) ? new File(value) : null; } /** * Retrieves the argument as a File object, if any, of an option. * * @param option the option that we want argument value to be returned for * @param defaultValue is the default value to be returned if the option is not specified * * @return Value of the argument if option is set, and has an argument, otherwise defaultValue. */ public File getFileValue(Option option, File defaultValue) { File answer = getFileValue(option); return (answer != null) ? answer : defaultValue; } }