diff options
Diffstat (limited to 'src')
56 files changed, 4458 insertions, 2879 deletions
diff --git a/src/main/java/com/beust/jcommander/DynamicParameter.java b/src/main/java/com/beust/jcommander/DynamicParameter.java index 2159c1f..bdb5010 100644 --- a/src/main/java/com/beust/jcommander/DynamicParameter.java +++ b/src/main/java/com/beust/jcommander/DynamicParameter.java @@ -37,14 +37,14 @@ public @interface DynamicParameter { boolean hidden() default false; /** - * The validation class to use. + * The validation classes to use. */ - Class<? extends IParameterValidator> validateWith() default NoValidator.class; + Class<? extends IParameterValidator>[] validateWith() default NoValidator.class; /** * The character(s) used to assign the values. */ String assignment() default "="; - Class<? extends IValueValidator> validateValueWith() default NoValueValidator.class; + Class<? extends IValueValidator>[] validateValueWith() default NoValueValidator.class; } diff --git a/src/main/java/com/beust/jcommander/IStringConverterFactory.java b/src/main/java/com/beust/jcommander/IStringConverterFactory.java index 0e53ca0..3e26020 100644 --- a/src/main/java/com/beust/jcommander/IStringConverterFactory.java +++ b/src/main/java/com/beust/jcommander/IStringConverterFactory.java @@ -24,6 +24,7 @@ package com.beust.jcommander; * your argument classes. * * @author cbeust + * @see IStringConverterInstanceFactory */ public interface IStringConverterFactory { <T> Class<? extends IStringConverter<T>> getConverter(Class<T> forType); diff --git a/src/main/java/com/beust/jcommander/IStringConverterInstanceFactory.java b/src/main/java/com/beust/jcommander/IStringConverterInstanceFactory.java new file mode 100644 index 0000000..1a87b5b --- /dev/null +++ b/src/main/java/com/beust/jcommander/IStringConverterInstanceFactory.java @@ -0,0 +1,20 @@ +package com.beust.jcommander; + +/** + * A factory to create {@link IStringConverter} instances. + * + * This interface lets you specify your converters in one place instead of having them repeated all over your argument classes. + * + * @author simon04 + * @see IStringConverterFactory + */ +public interface IStringConverterInstanceFactory { + /** + * Obtain a converter instance for parsing {@code parameter} as type {@code forType} + * @param parameter the parameter to parse + * @param forType the type class + * @param optionName the name of the option used on the command line + * @return a converter instance + */ + IStringConverter<?> getConverterInstance(Parameter parameter, Class<?> forType, String optionName); +} diff --git a/src/main/java/com/beust/jcommander/JCommander.java b/src/main/java/com/beust/jcommander/JCommander.java index 2e049a1..59073c6 100644 --- a/src/main/java/com/beust/jcommander/JCommander.java +++ b/src/main/java/com/beust/jcommander/JCommander.java @@ -7,7 +7,7 @@ * 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 + * 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, @@ -18,37 +18,19 @@ package com.beust.jcommander; +import com.beust.jcommander.FuzzyMap.IKey; +import com.beust.jcommander.converters.*; +import com.beust.jcommander.internal.*; + import java.io.BufferedReader; -import java.io.FileReader; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.lang.reflect.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; import java.util.ResourceBundle; - -import com.beust.jcommander.FuzzyMap.IKey; -import com.beust.jcommander.converters.IParameterSplitter; -import com.beust.jcommander.converters.NoConverter; -import com.beust.jcommander.converters.StringConverter; -import com.beust.jcommander.internal.Console; -import com.beust.jcommander.internal.DefaultConsole; -import com.beust.jcommander.internal.DefaultConverterFactory; -import com.beust.jcommander.internal.JDK6Console; -import com.beust.jcommander.internal.Lists; -import com.beust.jcommander.internal.Maps; -import com.beust.jcommander.internal.Nullable; +import java.util.concurrent.CopyOnWriteArrayList; /** * The main class for JCommander. It's responsible for parsing the object that contains @@ -63,1537 +45,1647 @@ import com.beust.jcommander.internal.Nullable; * @author Cedric Beust <cedric@beust.com> */ public class JCommander { - public static final String DEBUG_PROPERTY = "jcommander.debug"; - - /** - * A map to look up parameter description per option name. - */ - private Map<IKey, ParameterDescription> m_descriptions; - - /** - * The objects that contain fields annotated with @Parameter. - */ - private List<Object> m_objects = Lists.newArrayList(); - - private boolean m_firstTimeMainParameter = true; - - /** - * This field/method will contain whatever command line parameter is not an option. - * It is expected to be a List<String>. - */ - private Parameterized m_mainParameter = null; - - /** - * The object on which we found the main parameter field. - */ - private Object m_mainParameterObject; - - /** - * The annotation found on the main parameter field. - */ - private Parameter m_mainParameterAnnotation; - - private ParameterDescription m_mainParameterDescription; - - /** - * A set of all the parameterizeds that are required. During the reflection phase, - * this field receives all the fields that are annotated with required=true - * and during the parsing phase, all the fields that are assigned a value - * are removed from it. At the end of the parsing phase, if it's not empty, - * then some required fields did not receive a value and an exception is - * thrown. - */ - private Map<Parameterized, ParameterDescription> m_requiredFields = Maps.newHashMap(); - - /** - * A map of all the parameterized fields/methods. - */ - private Map<Parameterized, ParameterDescription> m_fields = Maps.newHashMap(); - - private ResourceBundle m_bundle; - - /** - * A default provider returns default values for the parameters. - */ - private IDefaultProvider m_defaultProvider; - - /** - * List of commands and their instance. - */ - private Map<ProgramName, JCommander> m_commands = Maps.newLinkedHashMap(); - - /** - * Alias database for reverse lookup - */ - private Map<IKey, ProgramName> aliasMap = Maps.newLinkedHashMap(); - - /** - * The name of the command after the parsing has run. - */ - private String m_parsedCommand; - - /** - * The name of command or alias as it was passed to the - * command line - */ - private String m_parsedAlias; - - private ProgramName m_programName; - - private Comparator<? super ParameterDescription> m_parameterDescriptionComparator - = new Comparator<ParameterDescription>() { - @Override - public int compare(ParameterDescription p0, ParameterDescription p1) { - return p0.getLongestName().compareTo(p1.getLongestName()); - } - }; - - private int m_columnSize = 79; - - private boolean m_helpWasSpecified; - - private List<String> m_unknownArgs = Lists.newArrayList(); - private boolean m_acceptUnknownOptions = false; - private boolean m_allowParameterOverwriting = false; - - private static Console m_console; - - /** - * The factories used to look up string converters. - */ - private static LinkedList<IStringConverterFactory> CONVERTER_FACTORIES = Lists.newLinkedList(); - - static { - CONVERTER_FACTORIES.addFirst(new DefaultConverterFactory()); - }; - - /** - * Creates a new un-configured JCommander object. - */ - public JCommander() { - } - - /** - * @param object The arg object expected to contain {@link Parameter} annotations. - */ - public JCommander(Object object) { - addObject(object); - createDescriptions(); - } - - /** - * @param object The arg object expected to contain {@link Parameter} annotations. - * @param bundle The bundle to use for the descriptions. Can be null. - */ - public JCommander(Object object, @Nullable ResourceBundle bundle) { - addObject(object); - setDescriptionsBundle(bundle); - } - - /** - * @param object The arg object expected to contain {@link Parameter} annotations. - * @param bundle The bundle to use for the descriptions. Can be null. - * @param args The arguments to parse (optional). - */ - public JCommander(Object object, ResourceBundle bundle, String... args) { - addObject(object); - setDescriptionsBundle(bundle); - parse(args); - } - - /** - * @param object The arg object expected to contain {@link Parameter} annotations. - * @param args The arguments to parse (optional). - */ - public JCommander(Object object, String... args) { - addObject(object); - parse(args); - } - - public static Console getConsole() { - if (m_console == null) { - try { - Method consoleMethod = System.class.getDeclaredMethod("console", new Class<?>[0]); - Object console = consoleMethod.invoke(null, new Object[0]); - m_console = new JDK6Console(console); - } catch (Throwable t) { - m_console = new DefaultConsole(); - } - } - return m_console; - } - - /** - * Adds the provided arg object to the set of objects that this commander - * will parse arguments into. - * - * @param object The arg object expected to contain {@link Parameter} - * annotations. If <code>object</code> is an array or is {@link Iterable}, - * the child objects will be added instead. - */ - // declared final since this is invoked from constructors - public final void addObject(Object object) { - if (object instanceof Iterable) { - // Iterable - for (Object o : (Iterable<?>) object) { - m_objects.add(o); - } - } else if (object.getClass().isArray()) { - // Array - for (Object o : (Object[]) object) { - m_objects.add(o); - } - } else { - // Single object - m_objects.add(object); - } - } - - /** - * Sets the {@link ResourceBundle} to use for looking up descriptions. - * Set this to <code>null</code> to use description text directly. - */ - // declared final since this is invoked from constructors - public final void setDescriptionsBundle(ResourceBundle bundle) { - m_bundle = bundle; - } - - /** - * Parse and validate the command line parameters. - */ - public void parse(String... args) { - parse(true /* validate */, args); - } - - /** - * Parse the command line parameters without validating them. - */ - public void parseWithoutValidation(String... args) { - parse(false /* no validation */, args); - } - - private void parse(boolean validate, String... args) { - StringBuilder sb = new StringBuilder("Parsing \""); - sb.append(join(args).append("\"\n with:").append(join(m_objects.toArray()))); - p(sb.toString()); - - if (m_descriptions == null) createDescriptions(); - initializeDefaultValues(); - parseValues(expandArgs(args), validate); - if (validate) validateOptions(); - } - - private StringBuilder join(Object[] args) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < args.length; i++) { - if (i > 0) result.append(" "); - result.append(args[i]); - } - return result; - } - - private void initializeDefaultValues() { - if (m_defaultProvider != null) { - for (ParameterDescription pd : m_descriptions.values()) { - initializeDefaultValue(pd); - } - - for (Map.Entry<ProgramName, JCommander> entry : m_commands.entrySet()) { - entry.getValue().initializeDefaultValues(); - } - } - } - - /** - * Make sure that all the required parameters have received a value. - */ - private void validateOptions() { - // No validation if we found a help parameter - if (m_helpWasSpecified) { - return; - } - - if (! m_requiredFields.isEmpty()) { - StringBuilder missingFields = new StringBuilder(); - for (ParameterDescription pd : m_requiredFields.values()) { - missingFields.append(pd.getNames()).append(" "); - } - throw new ParameterException("The following " - + pluralize(m_requiredFields.size(), "option is required: ", "options are required: ") - + missingFields); - } - - if (m_mainParameterDescription != null) { - if (m_mainParameterDescription.getParameter().required() && - !m_mainParameterDescription.isAssigned()) { - throw new ParameterException("Main parameters are required (\"" - + m_mainParameterDescription.getDescription() + "\")"); - } - } - } - - private static String pluralize(int quantity, String singular, String plural) { - return quantity == 1 ? singular : plural; - } - - /** - * Expand the command line parameters to take @ parameters into account. - * When @ is encountered, the content of the file that follows is inserted - * in the command line. - * - * @param originalArgv the original command line parameters - * @return the new and enriched command line parameters - */ - private String[] expandArgs(String[] originalArgv) { - List<String> vResult1 = Lists.newArrayList(); - - // - // Expand @ - // - for (String arg : originalArgv) { - - if (arg.startsWith("@")) { - String fileName = arg.substring(1); - vResult1.addAll(readFile(fileName)); - } - else { - List<String> expanded = expandDynamicArg(arg); - vResult1.addAll(expanded); - } - } - - // Expand separators - // - List<String> vResult2 = Lists.newArrayList(); - for (int i = 0; i < vResult1.size(); i++) { - String arg = vResult1.get(i); - String[] v1 = vResult1.toArray(new String[0]); - if (isOption(v1, arg)) { - String sep = getSeparatorFor(v1, arg); - if (! " ".equals(sep)) { - String[] sp = arg.split("[" + sep + "]", 2); - for (String ssp : sp) { - vResult2.add(ssp); - } + public static final String DEBUG_PROPERTY = "jcommander.debug"; + + /** + * A map to look up parameter description per option name. + */ + private Map<IKey, ParameterDescription> descriptions; + + /** + * The objects that contain fields annotated with @Parameter. + */ + private List<Object> objects = Lists.newArrayList(); + + private boolean firstTimeMainParameter = true; + + /** + * This field/method will contain whatever command line parameter is not an option. + * It is expected to be a List<String>. + */ + private Parameterized mainParameter = null; + + /** + * The object on which we found the main parameter field. + */ + private Object mainParameterObject; + + /** + * The annotation found on the main parameter field. + */ + private Parameter mainParameterAnnotation; + + private ParameterDescription mainParameterDescription; + + /** + * A set of all the parameterizeds that are required. During the reflection phase, + * this field receives all the fields that are annotated with required=true + * and during the parsing phase, all the fields that are assigned a value + * are removed from it. At the end of the parsing phase, if it's not empty, + * then some required fields did not receive a value and an exception is + * thrown. + */ + private Map<Parameterized, ParameterDescription> requiredFields = Maps.newHashMap(); + + /** + * A map of all the parameterized fields/methods. + */ + private Map<Parameterized, ParameterDescription> fields = Maps.newHashMap(); + + /** + * List of commands and their instance. + */ + private Map<ProgramName, JCommander> commands = Maps.newLinkedHashMap(); + + /** + * Alias database for reverse lookup + */ + private Map<IKey, ProgramName> aliasMap = Maps.newLinkedHashMap(); + + /** + * The name of the command after the parsing has run. + */ + private String parsedCommand; + + /** + * The name of command or alias as it was passed to the + * command line + */ + private String parsedAlias; + + private ProgramName programName; + + private boolean helpWasSpecified; + + private List<String> unknownArgs = Lists.newArrayList(); + + private static Console console; + + private final Options options; + + /** + * Options shared with sub commands + */ + private static class Options { + + private ResourceBundle bundle; + + /** + * A default provider returns default values for the parameters. + */ + private IDefaultProvider defaultProvider; + + private Comparator<? super ParameterDescription> parameterDescriptionComparator + = new Comparator<ParameterDescription>() { + @Override + public int compare(ParameterDescription p0, ParameterDescription p1) { + Parameter a0 = p0.getParameterAnnotation(); + Parameter a1 = p1.getParameterAnnotation(); + if (a0 != null && a0.order() != -1 && a1 != null && a1.order() != -1) { + return Integer.compare(a0.order(), a1.order()); + } else if (a0 != null && a0.order() != -1) { + return -1; + } else if (a1 != null && a1.order() != -1) { + return 1; + } else { + return p0.getLongestName().compareTo(p1.getLongestName()); + } + } + }; + private int columnSize = 79; + private boolean acceptUnknownOptions = false; + private boolean allowParameterOverwriting = false; + private boolean expandAtSign = true; + private int verbose = 0; + private boolean caseSensitiveOptions = true; + private boolean allowAbbreviatedOptions = false; + /** + * The factories used to look up string converters. + */ + private final List<IStringConverterInstanceFactory> converterInstanceFactories = new CopyOnWriteArrayList<>(); + private Charset atFileCharset = Charset.defaultCharset(); + } + + private JCommander(Options options) { + if (options == null) { + throw new NullPointerException("options"); + } + this.options = options; + addConverterFactory(new DefaultConverterFactory()); + } + + /** + * Creates a new un-configured JCommander object. + */ + public JCommander() { + this(new Options()); + } + + /** + * @param object The arg object expected to contain {@link Parameter} annotations. + */ + public JCommander(Object object) { + this(object, (ResourceBundle) null); + } + + /** + * @param object The arg object expected to contain {@link Parameter} annotations. + * @param bundle The bundle to use for the descriptions. Can be null. + */ + public JCommander(Object object, @Nullable ResourceBundle bundle) { + this(object, bundle, (String[]) null); + } + + /** + * @param object The arg object expected to contain {@link Parameter} annotations. + * @param bundle The bundle to use for the descriptions. Can be null. + * @param args The arguments to parse (optional). + */ + public JCommander(Object object, @Nullable ResourceBundle bundle, String... args) { + this(); + addObject(object); + if (bundle != null) { + setDescriptionsBundle(bundle); + } + createDescriptions(); + if (args != null) { + parse(args); + } + } + + /** + * @param object The arg object expected to contain {@link Parameter} annotations. + * @param args The arguments to parse (optional). + * + * @deprecated Construct a JCommander instance first and then call parse() on it. + */ + @Deprecated() + public JCommander(Object object, String... args) { + this(object); + parse(args); + } + + /** + * Disables expanding {@code @file}. + * + * JCommander supports the {@code @file} syntax, which allows you to put all your options + * into a file and pass this file as parameter @param expandAtSign whether to expand {@code @file}. + */ + public void setExpandAtSign(boolean expandAtSign) { + options.expandAtSign = expandAtSign; + } + + public static Console getConsole() { + if (console == null) { + try { + Method consoleMethod = System.class.getDeclaredMethod("console"); + Object console = consoleMethod.invoke(null); + JCommander.console = new JDK6Console(console); + } catch (Throwable t) { + console = new DefaultConsole(); + } + } + return console; + } + + /** + * Adds the provided arg object to the set of objects that this commander + * will parse arguments into. + * + * @param object The arg object expected to contain {@link Parameter} + * annotations. If <code>object</code> is an array or is {@link Iterable}, + * the child objects will be added instead. + */ + // declared final since this is invoked from constructors + public final void addObject(Object object) { + if (object instanceof Iterable) { + // Iterable + for (Object o : (Iterable<?>) object) { + objects.add(o); + } + } else if (object.getClass().isArray()) { + // Array + for (Object o : (Object[]) object) { + objects.add(o); + } } else { - vResult2.add(arg); + // Single object + objects.add(object); } - } else { - vResult2.add(arg); - } } - return vResult2.toArray(new String[vResult2.size()]); - } + /** + * Sets the {@link ResourceBundle} to use for looking up descriptions. + * Set this to <code>null</code> to use description text directly. + */ + // declared final since this is invoked from constructors + public final void setDescriptionsBundle(ResourceBundle bundle) { + options.bundle = bundle; + } - private List<String> expandDynamicArg(String arg) { - for (ParameterDescription pd : m_descriptions.values()) { - if (pd.isDynamicParameter()) { - for (String name : pd.getParameter().names()) { - if (arg.startsWith(name) && !arg.equals(name)) { - return Arrays.asList(name, arg.substring(name.length())); - } + /** + * Parse and validate the command line parameters. + */ + public void parse(String... args) { + try { + parse(true /* validate */, args); + } catch(ParameterException ex) { + ex.setJCommander(this); + throw ex; } - } } - return Arrays.asList(arg); - } + /** + * Parse the command line parameters without validating them. + */ + public void parseWithoutValidation(String... args) { + parse(false /* no validation */, args); + } - private boolean isOption(String[] args, String arg) { - String prefixes = getOptionPrefixes(args, arg); - return arg.length() > 0 && prefixes.indexOf(arg.charAt(0)) >= 0; - } + private void parse(boolean validate, String... args) { + StringBuilder sb = new StringBuilder("Parsing \""); + sb.append(join(args).append("\"\n with:").append(join(objects.toArray()))); + p(sb.toString()); - private ParameterDescription getPrefixDescriptionFor(String arg) { - for (Map.Entry<IKey, ParameterDescription> es : m_descriptions.entrySet()) { - if (arg.startsWith(es.getKey().getName())) return es.getValue(); + if (descriptions == null) createDescriptions(); + initializeDefaultValues(); + parseValues(expandArgs(args), validate); + if (validate) validateOptions(); } - return null; - } + private StringBuilder join(Object[] args) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (i > 0) result.append(" "); + result.append(args[i]); + } + return result; + } - /** - * If arg is an option, we can look it up directly, but if it's a value, - * we need to find the description for the option that precedes it. - */ - private ParameterDescription getDescriptionFor(String[] args, String arg) { - ParameterDescription result = getPrefixDescriptionFor(arg); - if (result != null) return result; + private void initializeDefaultValues() { + if (options.defaultProvider != null) { + for (ParameterDescription pd : descriptions.values()) { + initializeDefaultValue(pd); + } - for (String a : args) { - ParameterDescription pd = getPrefixDescriptionFor(arg); - if (pd != null) result = pd; - if (a.equals(arg)) return result; + for (Map.Entry<ProgramName, JCommander> entry : commands.entrySet()) { + entry.getValue().initializeDefaultValues(); + } + } } - throw new ParameterException("Unknown parameter: " + arg); - } + /** + * Make sure that all the required parameters have received a value. + */ + private void validateOptions() { + // No validation if we found a help parameter + if (helpWasSpecified) { + return; + } - private String getSeparatorFor(String[] args, String arg) { - ParameterDescription pd = getDescriptionFor(args, arg); + if (!requiredFields.isEmpty()) { + List<String> missingFields = new ArrayList<>(); + for (ParameterDescription pd : requiredFields.values()) { + missingFields.add("[" + String.join(" | ", pd.getParameter().names()) + "]"); + } + String message = String.join(", ", missingFields); + throw new ParameterException("The following " + + pluralize(requiredFields.size(), "option is required: ", "options are required: ") + + message); + } + + if (mainParameterDescription != null) { + if (mainParameterDescription.getParameter().required() && + !mainParameterDescription.isAssigned()) { + throw new ParameterException("Main parameters are required (\"" + + mainParameterDescription.getDescription() + "\")"); + } + } + } - // Could be null if only main parameters were passed - if (pd != null) { - Parameters p = pd.getObject().getClass().getAnnotation(Parameters.class); - if (p != null) return p.separators(); + private static String pluralize(int quantity, String singular, String plural) { + return quantity == 1 ? singular : plural; } - return " "; - } + /** + * Expand the command line parameters to take @ parameters into account. + * When @ is encountered, the content of the file that follows is inserted + * in the command line. + * + * @param originalArgv the original command line parameters + * @return the new and enriched command line parameters + */ + private String[] expandArgs(String[] originalArgv) { + List<String> vResult1 = Lists.newArrayList(); - private String getOptionPrefixes(String[] args, String arg) { - ParameterDescription pd = getDescriptionFor(args, arg); + // + // Expand @ + // + for (String arg : originalArgv) { - // Could be null if only main parameters were passed - if (pd != null) { - Parameters p = pd.getObject().getClass() - .getAnnotation(Parameters.class); - if (p != null) return p.optionPrefixes(); + if (arg.startsWith("@") && options.expandAtSign) { + String fileName = arg.substring(1); + vResult1.addAll(readFile(fileName)); + } else { + List<String> expanded = expandDynamicArg(arg); + vResult1.addAll(expanded); + } + } + + // Expand separators + // + List<String> vResult2 = Lists.newArrayList(); + for (String arg : vResult1) { + if (isOption(arg)) { + String sep = getSeparatorFor(arg); + if (!" ".equals(sep)) { + String[] sp = arg.split("[" + sep + "]", 2); + for (String ssp : sp) { + vResult2.add(ssp); + } + } else { + vResult2.add(arg); + } + } else { + vResult2.add(arg); + } + } + + return vResult2.toArray(new String[vResult2.size()]); } - String result = Parameters.DEFAULT_OPTION_PREFIXES; - // See if any of the objects contains a @Parameters(optionPrefixes) - StringBuilder sb = new StringBuilder(); - for (Object o : m_objects) { - Parameters p = o.getClass().getAnnotation(Parameters.class); - if (p != null && !Parameters.DEFAULT_OPTION_PREFIXES.equals(p.optionPrefixes())) { - sb.append(p.optionPrefixes()); - } + private List<String> expandDynamicArg(String arg) { + for (ParameterDescription pd : descriptions.values()) { + if (pd.isDynamicParameter()) { + for (String name : pd.getParameter().names()) { + if (arg.startsWith(name) && !arg.equals(name)) { + return Arrays.asList(name, arg.substring(name.length())); + } + } + } + } + + return Arrays.asList(arg); } - if (! Strings.isStringEmpty(sb.toString())) { - result = sb.toString(); + private boolean matchArg(String arg, IKey key) { + String kn = options.caseSensitiveOptions + ? key.getName() + : key.getName().toLowerCase(); + if (options.allowAbbreviatedOptions) { + if (kn.startsWith(arg)) return true; + } else { + ParameterDescription pd = descriptions.get(key); + if (pd != null) { + // It's an option. If the option has a separator (e.g. -author==foo) then + // we only do a beginsWith match + String separator = getSeparatorFor(arg); + if (! " ".equals(separator)) { + if (arg.startsWith(kn)) return true; + } else { + if (kn.equals(arg)) return true; + } + } else { + // It's a command do a strict equality check + if (kn.equals(arg)) return true; + } + } + return false; } - return result; - } + private boolean isOption(String passedArg) { + if (options.acceptUnknownOptions) return true; - /** - * Reads the file specified by filename and returns the file content as a string. - * End of lines are replaced by a space. - * - * @param fileName the command line filename - * @return the file content as a string. - */ - private static List<String> readFile(String fileName) { - List<String> result = Lists.newArrayList(); + String arg = options.caseSensitiveOptions ? passedArg : passedArg.toLowerCase(); - try { - BufferedReader bufRead = new BufferedReader(new FileReader(fileName)); + for (IKey key : descriptions.keySet()) { + if (matchArg(arg, key)) return true; + } + for (IKey key : commands.keySet()) { + if (matchArg(arg, key)) return true; + } - String line; + return false; + } - // Read through file one line at time. Print line # and line - while ((line = bufRead.readLine()) != null) { - // Allow empty lines and # comments in these at files - if (line.length() > 0 && ! line.trim().startsWith("#")) { - result.add(line); + private ParameterDescription getPrefixDescriptionFor(String arg) { + for (Map.Entry<IKey, ParameterDescription> es : descriptions.entrySet()) { + if (arg.startsWith(es.getKey().getName())) return es.getValue(); } - } - bufRead.close(); + return null; } - catch (IOException e) { - throw new ParameterException("Could not read file " + fileName + ": " + e); + + /** + * If arg is an option, we can look it up directly, but if it's a value, + * we need to find the description for the option that precedes it. + */ + private ParameterDescription getDescriptionFor(String arg) { + return getPrefixDescriptionFor(arg); } - return result; - } + private String getSeparatorFor(String arg) { + ParameterDescription pd = getDescriptionFor(arg); - /** - * Remove spaces at both ends and handle double quotes. - */ - private static String trim(String string) { - String result = string.trim(); - if (result.startsWith("\"") && result.endsWith("\"") && result.length() > 1) { - result = result.substring(1, result.length() - 1); + // Could be null if only main parameters were passed + if (pd != null) { + Parameters p = pd.getObject().getClass().getAnnotation(Parameters.class); + if (p != null) return p.separators(); + } + + return " "; } - return result; - } - /** - * Create the ParameterDescriptions for all the \@Parameter found. - */ - private void createDescriptions() { - m_descriptions = Maps.newHashMap(); + /** + * Reads the file specified by filename and returns the file content as a string. + * End of lines are replaced by a space. + * + * @param fileName the command line filename + * @return the file content as a string. + */ + private List<String> readFile(String fileName) { + List<String> result = Lists.newArrayList(); + + try (BufferedReader bufRead = Files.newBufferedReader(Paths.get(fileName), options.atFileCharset)) { + String line; + // Read through file one line at time. Print line # and line + while ((line = bufRead.readLine()) != null) { + // Allow empty lines and # comments in these at files + if (line.length() > 0 && !line.trim().startsWith("#")) { + result.add(line); + } + } + } catch (IOException e) { + throw new ParameterException("Could not read file " + fileName + ": " + e); + } - for (Object object : m_objects) { - addDescription(object); + return result; } - } - private void addDescription(Object object) { - Class<?> cls = object.getClass(); + /** + * Remove spaces at both ends and handle double quotes. + */ + private static String trim(String string) { + String result = string.trim(); + if (result.startsWith("\"") && result.endsWith("\"") && result.length() > 1) { + result = result.substring(1, result.length() - 1); + } + return result; + } - List<Parameterized> parameterizeds = Parameterized.parseArg(object); - for (Parameterized parameterized : parameterizeds) { - WrappedParameter wp = parameterized.getWrappedParameter(); - if (wp != null && wp.getParameter() != null) { - Parameter annotation = wp.getParameter(); - // - // @Parameter - // - Parameter p = annotation; - if (p.names().length == 0) { - p("Found main parameter:" + parameterized); - if (m_mainParameter != null) { - throw new ParameterException("Only one @Parameter with no names attribute is" - + " allowed, found:" + m_mainParameter + " and " + parameterized); - } - m_mainParameter = parameterized; - m_mainParameterObject = object; - m_mainParameterAnnotation = p; - m_mainParameterDescription = - new ParameterDescription(object, p, parameterized, m_bundle, this); + /** + * Create the ParameterDescriptions for all the \@Parameter found. + */ + private void createDescriptions() { + descriptions = Maps.newHashMap(); + + for (Object object : objects) { + addDescription(object); + } + } + + private void addDescription(Object object) { + Class<?> cls = object.getClass(); + + List<Parameterized> parameterizeds = Parameterized.parseArg(object); + for (Parameterized parameterized : parameterizeds) { + WrappedParameter wp = parameterized.getWrappedParameter(); + if (wp != null && wp.getParameter() != null) { + Parameter annotation = wp.getParameter(); + // + // @Parameter + // + Parameter p = annotation; + if (p.names().length == 0) { + p("Found main parameter:" + parameterized); + if (mainParameter != null) { + throw new ParameterException("Only one @Parameter with no names attribute is" + + " allowed, found:" + mainParameter + " and " + parameterized); + } + mainParameter = parameterized; + mainParameterObject = object; + mainParameterAnnotation = p; + mainParameterDescription = + new ParameterDescription(object, p, parameterized, options.bundle, this); + } else { + ParameterDescription pd = + new ParameterDescription(object, p, parameterized, options.bundle, this); + for (String name : p.names()) { + if (descriptions.containsKey(new StringKey(name))) { + throw new ParameterException("Found the option " + name + " multiple times"); + } + p("Adding description for " + name); + fields.put(parameterized, pd); + descriptions.put(new StringKey(name), pd); + + if (p.required()) requiredFields.put(parameterized, pd); + } + } + } else if (parameterized.getDelegateAnnotation() != null) { + // + // @ParametersDelegate + // + Object delegateObject = parameterized.get(object); + if (delegateObject == null) { + throw new ParameterException("Delegate field '" + parameterized.getName() + + "' cannot be null."); + } + addDescription(delegateObject); + } else if (wp != null && wp.getDynamicParameter() != null) { + // + // @DynamicParameter + // + DynamicParameter dp = wp.getDynamicParameter(); + for (String name : dp.names()) { + if (descriptions.containsKey(name)) { + throw new ParameterException("Found the option " + name + " multiple times"); + } + p("Adding description for " + name); + ParameterDescription pd = + new ParameterDescription(object, dp, parameterized, options.bundle, this); + fields.put(parameterized, pd); + descriptions.put(new StringKey(name), pd); + + if (dp.required()) requiredFields.put(parameterized, pd); + } + } + } + } + + private void initializeDefaultValue(ParameterDescription pd) { + for (String optionName : pd.getParameter().names()) { + String def = options.defaultProvider.getDefaultValueFor(optionName); + if (def != null) { + p("Initializing " + optionName + " with default value:" + def); + pd.addValue(def, true /* default */); + // remove the parameter from the list of fields to be required + requiredFields.remove(pd.getParameterized()); + return; + } + } + } + + /** + * Main method that parses the values and initializes the fields accordingly. + */ + private void parseValues(String[] args, boolean validate) { + // This boolean becomes true if we encounter a command, which indicates we need + // to stop parsing (the parsing of the command will be done in a sub JCommander + // object) + boolean commandParsed = false; + int i = 0; + boolean isDashDash = false; // once we encounter --, everything goes into the main parameter + while (i < args.length && !commandParsed) { + String arg = args[i]; + String a = trim(arg); + args[i] = a; + p("Parsing arg: " + a); + + JCommander jc = findCommandByAlias(arg); + int increment = 1; + if (!isDashDash && !"--".equals(a) && isOption(a) && jc == null) { + // + // Option + // + ParameterDescription pd = findParameterDescription(a); + + if (pd != null) { + if (pd.getParameter().password()) { + increment = processPassword(args, i, pd, validate); + } else { + if (pd.getParameter().variableArity()) { + // + // Variable arity? + // + increment = processVariableArity(args, i, pd, validate); + } else { + // + // Regular option + // + Class<?> fieldType = pd.getParameterized().getType(); + + // Boolean, set to true as soon as we see it, unless it specified + // an arity of 1, in which case we need to read the next value + if ((fieldType == boolean.class || fieldType == Boolean.class) + && pd.getParameter().arity() == -1) { + // Flip the value this boolean was initialized with + Boolean value = (Boolean) pd.getParameterized().get(pd.getObject()); + pd.addValue(value ? "false" : "true"); + requiredFields.remove(pd.getParameterized()); + } else { + increment = processFixedArity(args, i, pd, validate, fieldType); + } + // If it's a help option, remember for later + if (pd.isHelp()) { + helpWasSpecified = true; + } + } + } + } else { + if (options.acceptUnknownOptions) { + unknownArgs.add(arg); + i++; + while (i < args.length && !isOption(args[i])) { + unknownArgs.add(args[i++]); + } + increment = 0; + } else { + throw new ParameterException("Unknown option: " + arg); + } + } + } else { + // + // Main parameter + // + if ("--".equals(arg) && !isDashDash) { + isDashDash = true; + } + else if (commands.isEmpty()) { + // + // Regular (non-command) parsing + // + List mp = getMainParameter(arg); + String value = a; // If there's a non-quoted version, prefer that one + Object convertedValue = value; + + if (mainParameter.getGenericType() instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) mainParameter.getGenericType(); + Type cls = p.getActualTypeArguments()[0]; + if (cls instanceof Class) { + convertedValue = convertValue(mainParameter, (Class) cls, null, value); + } + } + + for(final Class<? extends IParameterValidator> validator : mainParameterAnnotation.validateWith() ) { + ParameterDescription.validateParameter(mainParameterDescription, + validator, + "Default", value); + } + + mainParameterDescription.setAssigned(true); + mp.add(convertedValue); + } else { + // + // Command parsing + // + if (jc == null && validate) { + throw new MissingCommandException("Expected a command, got " + arg, arg); + } else if (jc != null) { + parsedCommand = jc.programName.name; + parsedAlias = arg; //preserve the original form + + // Found a valid command, ask it to parse the remainder of the arguments. + // Setting the boolean commandParsed to true will force the current + // loop to end. + jc.parse(validate, subArray(args, i + 1)); + commandParsed = true; + } + } + } + i += increment; + } + + // Mark the parameter descriptions held in fields as assigned + for (ParameterDescription parameterDescription : descriptions.values()) { + if (parameterDescription.isAssigned()) { + fields.get(parameterDescription.getParameterized()).setAssigned(true); + } + } + + } + + private class DefaultVariableArity implements IVariableArity { + + @Override + public int processVariableArity(String optionName, String[] options) { + int i = 0; + while (i < options.length && !isOption(options[i])) { + i++; + } + return i; + } + } + + private final IVariableArity DEFAULT_VARIABLE_ARITY = new DefaultVariableArity(); + + private final int determineArity(String[] args, int index, ParameterDescription pd, IVariableArity va) { + List<String> currentArgs = Lists.newArrayList(); + for (int j = index + 1; j < args.length; j++) { + currentArgs.add(args[j]); + } + return va.processVariableArity(pd.getParameter().names()[0], + currentArgs.toArray(new String[0])); + } + + /** + * @return the number of options that were processed. + */ + private int processPassword(String[] args, int index, ParameterDescription pd, boolean validate) { + final int passwordArity = determineArity(args, index, pd, DEFAULT_VARIABLE_ARITY); + if (passwordArity == 0) { + // password option with password not specified, use the Console to retrieve the password + char[] password = readPassword(pd.getDescription(), pd.getParameter().echoInput()); + pd.addValue(new String(password)); + requiredFields.remove(pd.getParameterized()); + return 1; + } else if (passwordArity == 1) { + // password option with password specified + return processFixedArity(args, index, pd, validate, List.class, 1); } else { - ParameterDescription pd = - new ParameterDescription(object, p, parameterized, m_bundle, this); - for (String name : p.names()) { - if (m_descriptions.containsKey(new StringKey(name))) { - throw new ParameterException("Found the option " + name + " multiple times"); + throw new ParameterException("Password parameter must have at most 1 argument."); + } + } + + /** + * @return the number of options that were processed. + */ + private int processVariableArity(String[] args, int index, ParameterDescription pd, boolean validate) { + Object arg = pd.getObject(); + IVariableArity va; + if (!(arg instanceof IVariableArity)) { + va = DEFAULT_VARIABLE_ARITY; + } else { + va = (IVariableArity) arg; + } + + int arity = determineArity(args, index, pd, va); + int result = processFixedArity(args, index, pd, validate, List.class, arity); + return result; + } + + private int processFixedArity(String[] args, int index, ParameterDescription pd, boolean validate, + Class<?> fieldType) { + // Regular parameter, use the arity to tell use how many values + // we need to consume + int arity = pd.getParameter().arity(); + int n = (arity != -1 ? arity : 1); + + return processFixedArity(args, index, pd, validate, fieldType, n); + } + + private int processFixedArity(String[] args, int originalIndex, ParameterDescription pd, boolean validate, + Class<?> fieldType, int arity) { + int index = originalIndex; + String arg = args[index]; + // Special case for boolean parameters of arity 0 + if (arity == 0 && + (Boolean.class.isAssignableFrom(fieldType) + || boolean.class.isAssignableFrom(fieldType))) { + // Flip the value this boolean was initialized with + Boolean value = (Boolean) pd.getParameterized().get(pd.getObject()); + pd.addValue(value ? "false" : "true"); + requiredFields.remove(pd.getParameterized()); + } else if (arity == 0) { + throw new ParameterException("Expected a value after parameter " + arg); + + } else if (index < args.length - 1) { + int offset = "--".equals(args[index + 1]) ? 1 : 0; + + Object finalValue = null; + if (index + arity < args.length) { + for (int j = 1; j <= arity; j++) { + String value = trim(args[index + j + offset]); + finalValue = pd.addValue(arg, value, false, validate, j - 1); + requiredFields.remove(pd.getParameterized()); + } + + if (finalValue != null && validate) { + pd.validateValueParameter(arg, finalValue); + } + index += arity + offset; + } else { + throw new ParameterException("Expected " + arity + " values after " + arg); } - p("Adding description for " + name); - m_fields.put(parameterized, pd); - m_descriptions.put(new StringKey(name), pd); + } else { + throw new ParameterException("Expected a value after parameter " + arg); + } + + return arity + 1; + } + + /** + * Invoke Console.readPassword through reflection to avoid depending + * on Java 6. + */ + private char[] readPassword(String description, boolean echoInput) { + getConsole().print(description + ": "); + return getConsole().readPassword(echoInput); + } - if (p.required()) m_requiredFields.put(parameterized, pd); - } + private String[] subArray(String[] args, int index) { + int l = args.length - index; + String[] result = new String[l]; + System.arraycopy(args, index, result, 0, l); + + return result; + } + + /** + * @return the field that's meant to receive all the parameters that are not options. + * + * @param arg the arg that we're about to add (only passed here to output a meaningful + * error message). + */ + private List<?> getMainParameter(String arg) { + if (mainParameter == null) { + throw new ParameterException( + "Was passed main parameter '" + arg + "' but no main parameter was defined in your arg class"); } - } else if (parameterized.getDelegateAnnotation() != null) { + + List<?> result = (List<?>) mainParameter.get(mainParameterObject); + if (result == null) { + result = Lists.newArrayList(); + if (!List.class.isAssignableFrom(mainParameter.getType())) { + throw new ParameterException("Main parameter field " + mainParameter + + " needs to be of type List, not " + mainParameter.getType()); + } + mainParameter.set(mainParameterObject, result); + } + if (firstTimeMainParameter) { + result.clear(); + firstTimeMainParameter = false; + } + return result; + } + + public String getMainParameterDescription() { + if (descriptions == null) createDescriptions(); + return mainParameterAnnotation != null ? mainParameterAnnotation.description() + : null; + } + + /** + * Set the program name (used only in the usage). + */ + public void setProgramName(String name) { + setProgramName(name, new String[0]); + } + + /** + * Get the program name (used only in the usage). + */ + public String getProgramName(){ + return programName == null ? null : programName.getName(); + } + + /** + * Set the program name + * + * @param name program name + * @param aliases aliases to the program name + */ + public void setProgramName(String name, String... aliases) { + programName = new ProgramName(name, Arrays.asList(aliases)); + } + + /** + * Display the usage for this command. + */ + public void usage(String commandName) { + StringBuilder sb = new StringBuilder(); + usage(commandName, sb); + getConsole().println(sb.toString()); + } + + /** + * Store the help for the command in the passed string builder. + */ + public void usage(String commandName, StringBuilder out) { + usage(commandName, out, ""); + } + + /** + * Store the help for the command in the passed string builder, indenting + * every line with "indent". + */ + public void usage(String commandName, StringBuilder out, String indent) { + String description = getCommandDescription(commandName); + JCommander jc = findCommandByAlias(commandName); + if (description != null) { + out.append(indent).append(description); + out.append("\n"); + } + jc.usage(out, indent); + } + + /** + * @return the description of the command. + */ + public String getCommandDescription(String commandName) { + JCommander jc = findCommandByAlias(commandName); + if (jc == null) { + throw new ParameterException("Asking description for unknown command: " + commandName); + } + + Object arg = jc.getObjects().get(0); + Parameters p = arg.getClass().getAnnotation(Parameters.class); + ResourceBundle bundle = null; + String result = null; + if (p != null) { + result = p.commandDescription(); + String bundleName = p.resourceBundle(); + if (!"".equals(bundleName)) { + bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault()); + } else { + bundle = options.bundle; + } + + if (bundle != null) { + String descriptionKey = p.commandDescriptionKey(); + if (!"".equals(descriptionKey)) { + result = getI18nString(bundle, descriptionKey, p.commandDescription()); + } + } + } + + return result; + } + + /** + * @return The internationalized version of the string if available, otherwise + * return def. + */ + private String getI18nString(ResourceBundle bundle, String key, String def) { + String s = bundle != null ? bundle.getString(key) : null; + return s != null ? s : def; + } + + /** + * Display the help on System.out. + */ + public void usage() { + StringBuilder sb = new StringBuilder(); + usage(sb); + getConsole().println(sb.toString()); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private JCommander jCommander = new JCommander(); + private String[] args = null; + + public Builder() { + } + + /** + * Adds the provided arg object to the set of objects that this commander + * will parse arguments into. + * + * @param o The arg object expected to contain {@link Parameter} + * annotations. If <code>object</code> is an array or is {@link Iterable}, + * the child objects will be added instead. + */ + public Builder addObject(Object o) { + jCommander.addObject(o); + return this; + } + + /** + * Sets the {@link ResourceBundle} to use for looking up descriptions. + * Set this to <code>null</code> to use description text directly. + */ + public Builder resourceBundle(ResourceBundle bundle) { + jCommander.setDescriptionsBundle(bundle); + return this; + } + + public Builder args(String[] args) { + this.args = args; + return this; + } + + /** + * Disables expanding {@code @file}. + * + * JCommander supports the {@code @file} syntax, which allows you to put all your options + * into a file and pass this file as parameter @param expandAtSign whether to expand {@code @file}. + */ + public Builder expandAtSign(Boolean expand) { + jCommander.setExpandAtSign(expand); + return this; + } + + /** + * Set the program name (used only in the usage). + */ + public Builder programName(String name) { + jCommander.setProgramName(name); + return this; + } + + public Builder columnSize(int columnSize) { + jCommander.setColumnSize(columnSize); + return this; + } + + /** + * Define the default provider for this instance. + */ + public Builder defaultProvider(IDefaultProvider provider) { + jCommander.setDefaultProvider(provider); + return this; + } + + /** + * Adds a factory to lookup string converters. The added factory is used prior to previously added factories. + * @param factory the factory determining string converters + */ + public Builder addConverterFactory(IStringConverterFactory factory) { + jCommander.addConverterFactory(factory); + return this; + } + + public Builder verbose(int verbose) { + jCommander.setVerbose(verbose); + return this; + } + + public Builder allowAbbreviatedOptions(boolean b) { + jCommander.setAllowAbbreviatedOptions(b); + return this; + } + + public Builder acceptUnknownOptions(boolean b) { + jCommander.setAcceptUnknownOptions(b); + return this; + } + + public Builder allowParameterOverwriting(boolean b) { + jCommander.setAllowParameterOverwriting(b); + return this; + } + + public Builder atFileCharset(Charset charset) { + jCommander.setAtFileCharset(charset); + return this; + } + + public Builder addConverterInstanceFactory(IStringConverterInstanceFactory factory) { + jCommander.addConverterInstanceFactory(factory); + return this; + } + + public Builder addCommand(Object command) { + jCommander.addCommand(command); + return this; + } + + public Builder addCommand(String name, Object command, String... aliases) { + jCommander.addCommand(name, command, aliases); + return this; + } + + public JCommander build() { + if (args != null) { + jCommander.parse(args); + } + return jCommander; + } + } + + + /** + * Store the help in the passed string builder. + */ + public void usage(StringBuilder out) { + usage(out, ""); + } + + public void usage(StringBuilder out, String indent) { + if (descriptions == null) createDescriptions(); + boolean hasCommands = !commands.isEmpty(); + boolean hasOptions = !descriptions.isEmpty(); + + //indenting + int descriptionIndent = 6; + int indentCount = indent.length() + descriptionIndent; + // - // @ParametersDelegate + // First line of the usage // - Object delegateObject = parameterized.get(object); - if (delegateObject == null){ - throw new ParameterException("Delegate field '" + parameterized.getName() - + "' cannot be null."); + String programName = this.programName != null ? this.programName.getDisplayName() : "<main class>"; + StringBuilder mainLine = new StringBuilder(); + mainLine.append(indent).append("Usage: ").append(programName); + if (hasOptions) mainLine.append(" [options]"); + if (hasCommands) mainLine.append(indent).append(" [command] [command options]"); + if (mainParameterDescription != null) { + mainLine.append(" ").append(mainParameterDescription.getDescription()); } - addDescription(delegateObject); - } else if (wp != null && wp.getDynamicParameter() != null) { + wrapDescription(out, indentCount, mainLine.toString()); + out.append("\n"); + // - // @DynamicParameter + // Align the descriptions at the "longestName" column // - DynamicParameter dp = wp.getDynamicParameter(); - for (String name : dp.names()) { - if (m_descriptions.containsKey(name)) { - throw new ParameterException("Found the option " + name + " multiple times"); - } - p("Adding description for " + name); - ParameterDescription pd = - new ParameterDescription(object, dp, parameterized, m_bundle, this); - m_fields.put(parameterized, pd); - m_descriptions.put(new StringKey(name), pd); - - if (dp.required()) m_requiredFields.put(parameterized, pd); - } - } - } - -// while (!Object.class.equals(cls)) { -// for (Field f : cls.getDeclaredFields()) { -// p("Field:" + cls.getSimpleName() + "." + f.getName()); -// f.setAccessible(true); -// Annotation annotation = f.getAnnotation(Parameter.class); -// Annotation delegateAnnotation = f.getAnnotation(ParametersDelegate.class); -// Annotation dynamicParameter = f.getAnnotation(DynamicParameter.class); -// if (annotation != null) { -// // -// // @Parameter -// // -// Parameter p = (Parameter) annotation; -// if (p.names().length == 0) { -// p("Found main parameter:" + f); -// if (m_mainParameterField != null) { -// throw new ParameterException("Only one @Parameter with no names attribute is" -// + " allowed, found:" + m_mainParameterField + " and " + f); -// } -// m_mainParameterField = parameterized; -// m_mainParameterObject = object; -// m_mainParameterAnnotation = p; -// m_mainParameterDescription = new ParameterDescription(object, p, f, m_bundle, this); -// } else { -// for (String name : p.names()) { -// if (m_descriptions.containsKey(name)) { -// throw new ParameterException("Found the option " + name + " multiple times"); -// } -// p("Adding description for " + name); -// ParameterDescription pd = new ParameterDescription(object, p, f, m_bundle, this); -// m_fields.put(f, pd); -// m_descriptions.put(name, pd); -// -// if (p.required()) m_requiredFields.put(f, pd); -// } -// } -// } else if (delegateAnnotation != null) { -// // -// // @ParametersDelegate -// // -// try { -// Object delegateObject = f.get(object); -// if (delegateObject == null){ -// throw new ParameterException("Delegate field '" + f.getName() + "' cannot be null."); -// } -// addDescription(delegateObject); -// } catch (IllegalAccessException e) { -// } -// } else if (dynamicParameter != null) { -// // -// // @DynamicParameter -// // -// DynamicParameter dp = (DynamicParameter) dynamicParameter; -// for (String name : dp.names()) { -// if (m_descriptions.containsKey(name)) { -// throw new ParameterException("Found the option " + name + " multiple times"); -// } -// p("Adding description for " + name); -// ParameterDescription pd = new ParameterDescription(object, dp, f, m_bundle, this); -// m_fields.put(f, pd); -// m_descriptions.put(name, pd); -// -// if (dp.required()) m_requiredFields.put(f, pd); -// } -// } -// } -// // Traverse the super class until we find Object.class -// cls = cls.getSuperclass(); -// } - } - - private void initializeDefaultValue(ParameterDescription pd) { - for (String optionName : pd.getParameter().names()) { - String def = m_defaultProvider.getDefaultValueFor(optionName); - if (def != null) { - p("Initializing " + optionName + " with default value:" + def); - pd.addValue(def, true /* default */); - return; - } - } - } - - /** - * Main method that parses the values and initializes the fields accordingly. - */ - private void parseValues(String[] args, boolean validate) { - // This boolean becomes true if we encounter a command, which indicates we need - // to stop parsing (the parsing of the command will be done in a sub JCommander - // object) - boolean commandParsed = false; - int i = 0; - boolean isDashDash = false; // once we encounter --, everything goes into the main parameter - while (i < args.length && ! commandParsed) { - String arg = args[i]; - String a = trim(arg); - args[i] = a; - p("Parsing arg: " + a); - - JCommander jc = findCommandByAlias(arg); - int increment = 1; - if (! isDashDash && ! "--".equals(a) && isOption(args, a) && jc == null) { + int longestName = 0; + List<ParameterDescription> sorted = Lists.newArrayList(); + for (ParameterDescription pd : fields.values()) { + if (!pd.getParameter().hidden()) { + sorted.add(pd); + // + to have an extra space between the name and the description + int length = pd.getNames().length() + 2; + if (length > longestName) { + longestName = length; + } + } + } + // - // Option + // Sort the options // - ParameterDescription pd = findParameterDescription(a); + Collections.sort(sorted, getParameterDescriptionComparator()); - if (pd != null) { - if (pd.getParameter().password()) { - // - // Password option, use the Console to retrieve the password - // - char[] password = readPassword(pd.getDescription(), pd.getParameter().echoInput()); - pd.addValue(new String(password)); - m_requiredFields.remove(pd.getParameterized()); - } else { - if (pd.getParameter().variableArity()) { - // - // Variable arity? - // - increment = processVariableArity(args, i, pd); - } else { - // - // Regular option - // - Class<?> fieldType = pd.getParameterized().getType(); - - // Boolean, set to true as soon as we see it, unless it specified - // an arity of 1, in which case we need to read the next value - if ((fieldType == boolean.class || fieldType == Boolean.class) - && pd.getParameter().arity() == -1) { - pd.addValue("true"); - m_requiredFields.remove(pd.getParameterized()); - } else { - increment = processFixedArity(args, i, pd, fieldType); - } - // If it's a help option, remember for later - if (pd.isHelp()) { - m_helpWasSpecified = true; - } + // + // Display all the names and descriptions + // + if (sorted.size() > 0) out.append(indent).append(" Options:\n"); + for (ParameterDescription pd : sorted) { + WrappedParameter parameter = pd.getParameter(); + out.append(indent).append(" " + + (parameter.required() ? "* " : " ") + + pd.getNames() + + "\n"); + wrapDescription(out, indentCount, s(indentCount) + pd.getDescription()); + Object def = pd.getDefault(); + if (pd.isDynamicParameter()) { + out.append("\n" + s(indentCount)) + .append("Syntax: " + parameter.names()[0] + + "key" + parameter.getAssignment() + + "value"); } - } - } else { - if (m_acceptUnknownOptions) { - m_unknownArgs.add(arg); - i++; - while (i < args.length && ! isOption(args, args[i])) { - m_unknownArgs.add(args[i++]); + if (def != null && !pd.isHelp()) { + String displayedDef = Strings.isStringEmpty(def.toString()) + ? "<empty string>" + : def.toString(); + out.append("\n" + s(indentCount)) + .append("Default: " + (parameter.password() ? "********" : displayedDef)); + } + Class<?> type = pd.getParameterized().getType(); + if (type.isEnum()) { + out.append("\n" + s(indentCount)) + .append("Possible Values: " + EnumSet.allOf((Class<? extends Enum>) type)); } - increment = 0; - } else { - throw new ParameterException("Unknown option: " + arg); - } + out.append("\n"); } - } - else { + // - // Main parameter + // If commands were specified, show them as well // - if (! Strings.isStringEmpty(arg)) { - if ("--".equals(arg)) { - isDashDash = true; - a = trim(args[++i]); - } - if (m_commands.isEmpty()) { - // - // Regular (non-command) parsing - // - List mp = getMainParameter(arg); - String value = a; // If there's a non-quoted version, prefer that one - Object convertedValue = value; - - if (m_mainParameter.getGenericType() instanceof ParameterizedType) { - ParameterizedType p = (ParameterizedType) m_mainParameter.getGenericType(); - Type cls = p.getActualTypeArguments()[0]; - if (cls instanceof Class) { - convertedValue = convertValue(m_mainParameter, (Class) cls, value); - } + if (hasCommands) { + out.append(indent + " Commands:\n"); + // The magic value 3 is the number of spaces between the name of the option + // and its description + for (Map.Entry<ProgramName, JCommander> commands : this.commands.entrySet()) { + Object arg = commands.getValue().getObjects().get(0); + Parameters p = arg.getClass().getAnnotation(Parameters.class); + if (p == null || !p.hidden()) { + ProgramName progName = commands.getKey(); + String dispName = progName.getDisplayName(); + String description = getCommandDescription(progName.getName()); + wrapDescription(out, indentCount + descriptionIndent, + indent + " " + dispName + " " + description); + out.append("\n"); + + // Options for this command + JCommander jc = findCommandByAlias(progName.getName()); + jc.usage(out, indent + " "); + out.append("\n"); + } } + } + } - ParameterDescription.validateParameter(m_mainParameterDescription, - m_mainParameterAnnotation.validateWith(), - "Default", value); - - m_mainParameterDescription.setAssigned(true); - mp.add(convertedValue); - } - else { - // - // Command parsing - // - if (jc == null && validate) { - throw new MissingCommandException("Expected a command, got " + arg); - } else if (jc != null){ - m_parsedCommand = jc.m_programName.m_name; - m_parsedAlias = arg; //preserve the original form - - // Found a valid command, ask it to parse the remainder of the arguments. - // Setting the boolean commandParsed to true will force the current - // loop to end. - jc.parse(subArray(args, i + 1)); - commandParsed = true; + private Comparator<? super ParameterDescription> getParameterDescriptionComparator() { + return options.parameterDescriptionComparator; + } + + public void setParameterDescriptionComparator(Comparator<? super ParameterDescription> c) { + options.parameterDescriptionComparator = c; + } + + public void setColumnSize(int columnSize) { + options.columnSize = columnSize; + } + + public int getColumnSize() { + return options.columnSize; + } + + /** + * Wrap a potentially long line to {@link #getColumnSize()}. + * + * @param out the output + * @param indent the indentation in spaces for lines after the first line. + * @param description the text to wrap. No extra spaces are inserted before {@code + * description}. If the first line needs to be indented prepend the + * correct number of spaces to {@code description}. + */ + private void wrapDescription(StringBuilder out, int indent, String description) { + int max = getColumnSize(); + String[] words = description.split(" "); + int current = 0; + int i = 0; + while (i < words.length) { + String word = words[i]; + if (word.length() > max || current + 1 + word.length() <= max) { + out.append(word); + current += word.length(); + if (i != words.length - 1) { + out.append(" "); + current++; + } + } else { + out.append("\n").append(s(indent)).append(word).append(" "); + current = indent + 1 + word.length(); } - } + i++; } - } - i += increment; } - // Mark the parameter descriptions held in m_fields as assigned - for (ParameterDescription parameterDescription : m_descriptions.values()) { - if (parameterDescription.isAssigned()) { - m_fields.get(parameterDescription.getParameterized()).setAssigned(true); - } + /** + * @return a Collection of all the \@Parameter annotations found on the + * target class. This can be used to display the usage() in a different + * format (e.g. HTML). + */ + public List<ParameterDescription> getParameters() { + return new ArrayList<>(fields.values()); + } + + /** + * @return the main parameter description or null if none is defined. + */ + public ParameterDescription getMainParameter() { + return mainParameterDescription; } - } + private void p(String string) { + if (options.verbose > 0 || System.getProperty(JCommander.DEBUG_PROPERTY) != null) { + getConsole().println("[JCommander] " + string); + } + } - private class DefaultVariableArity implements IVariableArity { + /** + * Define the default provider for this instance. + */ + public void setDefaultProvider(IDefaultProvider defaultProvider) { + options.defaultProvider = defaultProvider; + } - @Override - public int processVariableArity(String optionName, String[] options) { - int i = 0; - while (i < options.length && !isOption(options, options[i])) { - i++; - } - return i; - } - } - private final IVariableArity DEFAULT_VARIABLE_ARITY = new DefaultVariableArity(); - - private int m_verbose = 0; - - private boolean m_caseSensitiveOptions = true; - private boolean m_allowAbbreviatedOptions = false; - - /** - * @return the number of options that were processed. - */ - private int processVariableArity(String[] args, int index, ParameterDescription pd) { - Object arg = pd.getObject(); - IVariableArity va; - if (! (arg instanceof IVariableArity)) { - va = DEFAULT_VARIABLE_ARITY; - } else { - va = (IVariableArity) arg; - } - - List<String> currentArgs = Lists.newArrayList(); - for (int j = index + 1; j < args.length; j++) { - currentArgs.add(args[j]); - } - int arity = va.processVariableArity(pd.getParameter().names()[0], - currentArgs.toArray(new String[0])); - - int result = processFixedArity(args, index, pd, List.class, arity); - return result; - } - - private int processFixedArity(String[] args, int index, ParameterDescription pd, - Class<?> fieldType) { - // Regular parameter, use the arity to tell use how many values - // we need to consume - int arity = pd.getParameter().arity(); - int n = (arity != -1 ? arity : 1); - - return processFixedArity(args, index, pd, fieldType, n); - } - - private int processFixedArity(String[] args, int originalIndex, ParameterDescription pd, - Class<?> fieldType, int arity) { - int index = originalIndex; - String arg = args[index]; - // Special case for boolean parameters of arity 0 - if (arity == 0 && - (Boolean.class.isAssignableFrom(fieldType) - || boolean.class.isAssignableFrom(fieldType))) { - pd.addValue("true"); - m_requiredFields.remove(pd.getParameterized()); - } else if (index < args.length - 1) { - int offset = "--".equals(args[index + 1]) ? 1 : 0; - - if (index + arity < args.length) { - for (int j = 1; j <= arity; j++) { - pd.addValue(trim(args[index + j + offset])); - m_requiredFields.remove(pd.getParameterized()); - } - index += arity + offset; - } else { - throw new ParameterException("Expected " + arity + " values after " + arg); - } - } else { - throw new ParameterException("Expected a value after parameter " + arg); - } - - return arity + 1; - } - - /** - * Invoke Console.readPassword through reflection to avoid depending - * on Java 6. - */ - private char[] readPassword(String description, boolean echoInput) { - getConsole().print(description + ": "); - return getConsole().readPassword(echoInput); - } - - private String[] subArray(String[] args, int index) { - int l = args.length - index; - String[] result = new String[l]; - System.arraycopy(args, index, result, 0, l); - - return result; - } - - /** - * @return the field that's meant to receive all the parameters that are not options. - * - * @param arg the arg that we're about to add (only passed here to output a meaningful - * error message). - */ - private List<?> getMainParameter(String arg) { - if (m_mainParameter == null) { - throw new ParameterException( - "Was passed main parameter '" + arg + "' but no main parameter was defined"); - } - - List<?> result = (List<?>) m_mainParameter.get(m_mainParameterObject); - if (result == null) { - result = Lists.newArrayList(); - if (! List.class.isAssignableFrom(m_mainParameter.getType())) { - throw new ParameterException("Main parameter field " + m_mainParameter - + " needs to be of type List, not " + m_mainParameter.getType()); - } - m_mainParameter.set(m_mainParameterObject, result); - } - if (m_firstTimeMainParameter) { - result.clear(); - m_firstTimeMainParameter = false; - } - return result; - } - - public String getMainParameterDescription() { - if (m_descriptions == null) createDescriptions(); - return m_mainParameterAnnotation != null ? m_mainParameterAnnotation.description() - : null; - } - -// private int longestName(Collection<?> objects) { -// int result = 0; -// for (Object o : objects) { -// int l = o.toString().length(); -// if (l > result) result = l; -// } -// -// return result; -// } - - /** - * Set the program name (used only in the usage). - */ - public void setProgramName(String name) { - setProgramName(name, new String[0]); - } - - /** - * Set the program name - * - * @param name program name - * @param aliases aliases to the program name - */ - public void setProgramName(String name, String... aliases) { - m_programName = new ProgramName(name, Arrays.asList(aliases)); - } - - /** - * Display the usage for this command. - */ - public void usage(String commandName) { - StringBuilder sb = new StringBuilder(); - usage(commandName, sb); - getConsole().println(sb.toString()); - } - - /** - * Store the help for the command in the passed string builder. - */ - public void usage(String commandName, StringBuilder out) { - usage(commandName, out, ""); - } - - /** - * Store the help for the command in the passed string builder, indenting - * every line with "indent". - */ - public void usage(String commandName, StringBuilder out, String indent) { - String description = getCommandDescription(commandName); - JCommander jc = findCommandByAlias(commandName); - if (description != null) { - out.append(indent).append(description); - out.append("\n"); - } - jc.usage(out, indent); - } - - /** - * @return the description of the command. - */ - public String getCommandDescription(String commandName) { - JCommander jc = findCommandByAlias(commandName); - if (jc == null) { - throw new ParameterException("Asking description for unknown command: " + commandName); - } - - Object arg = jc.getObjects().get(0); - Parameters p = arg.getClass().getAnnotation(Parameters.class); - ResourceBundle bundle = null; - String result = null; - if (p != null) { - result = p.commandDescription(); - String bundleName = p.resourceBundle(); - if (!"".equals(bundleName)) { - bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault()); - } else { - bundle = m_bundle; - } - - if (bundle != null) { - result = getI18nString(bundle, p.commandDescriptionKey(), p.commandDescription()); - } - } - - return result; - } - - /** - * @return The internationalized version of the string if available, otherwise - * return def. - */ - private String getI18nString(ResourceBundle bundle, String key, String def) { - String s = bundle != null ? bundle.getString(key) : null; - return s != null ? s : def; - } - - /** - * Display the help on System.out. - */ - public void usage() { - StringBuilder sb = new StringBuilder(); - usage(sb); - getConsole().println(sb.toString()); - } - - /** - * Store the help in the passed string builder. - */ - public void usage(StringBuilder out) { - usage(out, ""); - } - - public void usage(StringBuilder out, String indent) { - if (m_descriptions == null) createDescriptions(); - boolean hasCommands = !m_commands.isEmpty(); - - // - // First line of the usage - // - String programName = m_programName != null ? m_programName.getDisplayName() : "<main class>"; - out.append(indent).append("Usage: " + programName + " [options]"); - if (hasCommands) out.append(indent).append(" [command] [command options]"); - if (m_mainParameterDescription != null) { - out.append(" " + m_mainParameterDescription.getDescription()); - } - out.append("\n"); - - // - // Align the descriptions at the "longestName" column - // - int longestName = 0; - List<ParameterDescription> sorted = Lists.newArrayList(); - for (ParameterDescription pd : m_fields.values()) { - if (! pd.getParameter().hidden()) { - sorted.add(pd); - // + to have an extra space between the name and the description - int length = pd.getNames().length() + 2; - if (length > longestName) { - longestName = length; - } - } - } - - // - // Sort the options - // - Collections.sort(sorted, getParameterDescriptionComparator()); - - // - // Display all the names and descriptions - // - int descriptionIndent = 6; - if (sorted.size() > 0) out.append(indent).append(" Options:\n"); - for (ParameterDescription pd : sorted) { - WrappedParameter parameter = pd.getParameter(); - out.append(indent).append(" " - + (parameter.required() ? "* " : " ") - + pd.getNames() - + "\n" - + indent + s(descriptionIndent)); - int indentCount = indent.length() + descriptionIndent; - wrapDescription(out, indentCount, pd.getDescription()); - Object def = pd.getDefault(); - if (pd.isDynamicParameter()) { - out.append("\n" + s(indentCount + 1)) - .append("Syntax: " + parameter.names()[0] - + "key" + parameter.getAssignment() - + "value"); - } - if (def != null) { - String displayedDef = Strings.isStringEmpty(def.toString()) - ? "<empty string>" - : def.toString(); - out.append("\n" + s(indentCount + 1)) - .append("Default: " + (parameter.password()?"********" : displayedDef)); - } - Class<?> type = pd.getParameterized().getType(); - if(type.isEnum()){ - out.append("\n" + s(indentCount + 1)) - .append("Possible Values: " + EnumSet.allOf((Class<? extends Enum>) type)); - } - out.append("\n"); - } - - // - // If commands were specified, show them as well - // - if (hasCommands) { - out.append(" Commands:\n"); - // The magic value 3 is the number of spaces between the name of the option - // and its description - for (Map.Entry<ProgramName, JCommander> commands : m_commands.entrySet()) { - Object arg = commands.getValue().getObjects().get(0); - Parameters p = arg.getClass().getAnnotation(Parameters.class); - if (!p.hidden()) { - ProgramName progName = commands.getKey(); - String dispName = progName.getDisplayName(); - out.append(indent).append(" " + dispName); // + s(spaceCount) + getCommandDescription(progName.name) + "\n"); - - // Options for this command - usage(progName.getName(), out, " "); - out.append("\n"); - } - } - } - } - - private Comparator<? super ParameterDescription> getParameterDescriptionComparator() { - return m_parameterDescriptionComparator; - } - - public void setParameterDescriptionComparator(Comparator<? super ParameterDescription> c) { - m_parameterDescriptionComparator = c; - } - - public void setColumnSize(int columnSize) { - m_columnSize = columnSize; - } - - public int getColumnSize() { - return m_columnSize; - } - - private void wrapDescription(StringBuilder out, int indent, String description) { - int max = getColumnSize(); - String[] words = description.split(" "); - int current = indent; - int i = 0; - while (i < words.length) { - String word = words[i]; - if (word.length() > max || current + word.length() <= max) { - out.append(" ").append(word); - current += word.length() + 1; - } else { - out.append("\n").append(s(indent + 1)).append(word); - current = indent; - } - i++; - } - } - - /** - * @return a Collection of all the \@Parameter annotations found on the - * target class. This can be used to display the usage() in a different - * format (e.g. HTML). - */ - public List<ParameterDescription> getParameters() { - return new ArrayList<ParameterDescription>(m_fields.values()); - } - - /** - * @return the main parameter description or null if none is defined. - */ - public ParameterDescription getMainParameter() { - return m_mainParameterDescription; - } - - private void p(String string) { - if (m_verbose > 0 || System.getProperty(JCommander.DEBUG_PROPERTY) != null) { - getConsole().println("[JCommander] " + string); - } - } - - /** - * Define the default provider for this instance. - */ - public void setDefaultProvider(IDefaultProvider defaultProvider) { - m_defaultProvider = defaultProvider; - - for (Map.Entry<ProgramName, JCommander> entry : m_commands.entrySet()) { - entry.getValue().setDefaultProvider(defaultProvider); - } - } - - public void addConverterFactory(IStringConverterFactory converterFactory) { - CONVERTER_FACTORIES.addFirst(converterFactory); - } - - public <T> Class<? extends IStringConverter<T>> findConverter(Class<T> cls) { - for (IStringConverterFactory f : CONVERTER_FACTORIES) { - Class<? extends IStringConverter<T>> result = f.getConverter(cls); - if (result != null) return result; - } - - return null; - } - - public Object convertValue(ParameterDescription pd, String value) { - return convertValue(pd.getParameterized(), pd.getParameterized().getType(), value); - } - - /** - * @param type The type of the actual parameter - * @param value The value to convert - */ - public Object convertValue(Parameterized parameterized, Class type, - String value) { - Parameter annotation = parameterized.getParameter(); - - // Do nothing if it's a @DynamicParameter - if (annotation == null) return value; - - Class<? extends IStringConverter<?>> converterClass = annotation.converter(); - boolean listConverterWasSpecified = annotation.listConverter() != NoConverter.class; - - // - // Try to find a converter on the annotation - // - if (converterClass == null || converterClass == NoConverter.class) { - // If no converter specified and type is enum, used enum values to convert - if (type.isEnum()){ - converterClass = type; - } else { - converterClass = findConverter(type); - } - } - - if (converterClass == null) { - Type elementType = parameterized.findFieldGenericType(); - converterClass = elementType != null - ? findConverter((Class<? extends IStringConverter<?>>) elementType) - : StringConverter.class; - // Check for enum type parameter - if (converterClass == null && Enum.class.isAssignableFrom((Class) elementType)) { - converterClass = (Class<? extends IStringConverter<?>>) elementType; - } - } - - IStringConverter<?> converter; - Object result = null; - try { - String[] names = annotation.names(); - String optionName = names.length > 0 ? names[0] : "[Main class]"; - if (converterClass != null && converterClass.isEnum()) { + /** + * Adds a factory to lookup string converters. The added factory is used prior to previously added factories. + * @param converterFactory the factory determining string converters + */ + public void addConverterFactory(final IStringConverterFactory converterFactory) { + addConverterInstanceFactory(new IStringConverterInstanceFactory() { + @SuppressWarnings("unchecked") + @Override + public IStringConverter<?> getConverterInstance(Parameter parameter, Class<?> forType, String optionName) { + final Class<? extends IStringConverter<?>> converterClass = converterFactory.getConverter(forType); + try { + if(optionName == null) { + optionName = parameter.names().length > 0 ? parameter.names()[0] : "[Main class]"; + } + return converterClass != null ? instantiateConverter(optionName, converterClass) : null; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new ParameterException(e); + } + } + }); + } + + /** + * Adds a factory to lookup string converters. The added factory is used prior to previously added factories. + * @param converterInstanceFactory the factory generating string converter instances + */ + public void addConverterInstanceFactory(IStringConverterInstanceFactory converterInstanceFactory) { + options.converterInstanceFactories.add(0, converterInstanceFactory); + } + + private IStringConverter<?> findConverterInstance(Parameter parameter, Class<?> forType, String optionName) { + for (IStringConverterInstanceFactory f : options.converterInstanceFactories) { + IStringConverter<?> result = f.getConverterInstance(parameter, forType, optionName); + if (result != null) return result; + } + + return null; + } + + /** + * @param type The type of the actual parameter + * @param optionName + * @param value The value to convert + */ + public Object convertValue(final Parameterized parameterized, Class type, String optionName, String value) { + final Parameter annotation = parameterized.getParameter(); + + // Do nothing if it's a @DynamicParameter + if (annotation == null) return value; + + if(optionName == null) { + optionName = annotation.names().length > 0 ? annotation.names()[0] : "[Main class]"; + } + + IStringConverter<?> converter = null; + if (type.isAssignableFrom(List.class)) { + // If a list converter was specified, pass the value to it for direct conversion + converter = tryInstantiateConverter(optionName, annotation.listConverter()); + } + if (type.isAssignableFrom(List.class) && converter == null) { + // No list converter: use the single value converter and pass each parsed value to it individually + final IParameterSplitter splitter = tryInstantiateConverter(null, annotation.splitter()); + converter = new DefaultListConverter(splitter, new IStringConverter() { + @Override + public Object convert(String value) { + final Type genericType = parameterized.findFieldGenericType(); + return convertValue(parameterized, genericType instanceof Class ? (Class) genericType : String.class, null, value); + } + }); + } + + if (converter == null) { + converter = tryInstantiateConverter(optionName, annotation.converter()); + } + if (converter == null) { + converter = findConverterInstance(annotation, type, optionName); + } + if (converter == null && type.isEnum()) { + converter = new EnumConverter(optionName, type); + } + if (converter == null) { + converter = new StringConverter(); + } + return converter.convert(value); + } + + private static <T> T tryInstantiateConverter(String optionName, Class<T> converterClass) { + if (converterClass == NoConverter.class || converterClass == null) { + return null; + } try { - result = Enum.valueOf((Class<? extends Enum>) converterClass, value); - } catch (IllegalArgumentException e) { - try { - result = Enum.valueOf((Class<? extends Enum>) converterClass, value.toUpperCase()); - } catch (IllegalArgumentException ex) { - throw new ParameterException("Invalid value for " + optionName + " parameter. Allowed values:" + - EnumSet.allOf((Class<? extends Enum>) converterClass)); + return instantiateConverter(optionName, converterClass); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ignore) { + return null; + } + } + + private static <T> T instantiateConverter(String optionName, Class<? extends T> converterClass) + throws InstantiationException, IllegalAccessException, + InvocationTargetException { + Constructor<T> ctor = null; + Constructor<T> stringCtor = null; + for (Constructor<T> c : (Constructor<T>[]) converterClass.getDeclaredConstructors()) { + c.setAccessible(true); + Class<?>[] types = c.getParameterTypes(); + if (types.length == 1 && types[0].equals(String.class)) { + stringCtor = c; + } else if (types.length == 0) { + ctor = c; + } + } + + return stringCtor != null + ? stringCtor.newInstance(optionName) + : ctor != null + ? ctor.newInstance() + : null; + } + + /** + * Add a command object. + */ + public void addCommand(String name, Object object) { + addCommand(name, object, new String[0]); + } + + public void addCommand(Object object) { + Parameters p = object.getClass().getAnnotation(Parameters.class); + if (p != null && p.commandNames().length > 0) { + for (String commandName : p.commandNames()) { + addCommand(commandName, object); } - } catch (Exception e) { - throw new ParameterException("Invalid value for " + optionName + " parameter. Allowed values:" + - EnumSet.allOf((Class<? extends Enum>) converterClass)); - } - } else { - converter = instantiateConverter(optionName, converterClass); - if (type.isAssignableFrom(List.class) - && parameterized.getGenericType() instanceof ParameterizedType) { - - // The field is a List - if (listConverterWasSpecified) { - // If a list converter was specified, pass the value to it - // for direct conversion - IStringConverter<?> listConverter = - instantiateConverter(optionName, annotation.listConverter()); - result = listConverter.convert(value); - } else { - // No list converter: use the single value converter and pass each - // parsed value to it individually - result = convertToList(value, converter, annotation.splitter()); - } } else { - result = converter.convert(value); - } - } - } catch (InstantiationException e) { - throw new ParameterException(e); - } catch (IllegalAccessException e) { - throw new ParameterException(e); - } catch (InvocationTargetException e) { - throw new ParameterException(e); - } - - return result; - } - - /** - * Use the splitter to split the value into multiple values and then convert - * each of them individually. - */ - private Object convertToList(String value, IStringConverter<?> converter, - Class<? extends IParameterSplitter> splitterClass) - throws InstantiationException, IllegalAccessException { - IParameterSplitter splitter = splitterClass.newInstance(); - List<Object> result = Lists.newArrayList(); - for (String param : splitter.split(value)) { - result.add(converter.convert(param)); - } - return result; - } - - private IStringConverter<?> instantiateConverter(String optionName, - Class<? extends IStringConverter<?>> converterClass) - throws IllegalArgumentException, InstantiationException, IllegalAccessException, - InvocationTargetException { - Constructor<IStringConverter<?>> ctor = null; - Constructor<IStringConverter<?>> stringCtor = null; - Constructor<IStringConverter<?>>[] ctors - = (Constructor<IStringConverter<?>>[]) converterClass.getDeclaredConstructors(); - for (Constructor<IStringConverter<?>> c : ctors) { - Class<?>[] types = c.getParameterTypes(); - if (types.length == 1 && types[0].equals(String.class)) { - stringCtor = c; - } else if (types.length == 0) { - ctor = c; - } - } - - IStringConverter<?> result = stringCtor != null - ? stringCtor.newInstance(optionName) - : (ctor != null - ? ctor.newInstance() - : null); - - return result; - } - - /** - * Add a command object. - */ - public void addCommand(String name, Object object) { - addCommand(name, object, new String[0]); - } - - public void addCommand(Object object) { - Parameters p = object.getClass().getAnnotation(Parameters.class); - if (p != null && p.commandNames().length > 0) { - for (String commandName : p.commandNames()) { - addCommand(commandName, object); - } - } else { - throw new ParameterException("Trying to add command " + object.getClass().getName() - + " without specifying its names in @Parameters"); - } - } - - /** - * Add a command object and its aliases. - */ - public void addCommand(String name, Object object, String... aliases) { - JCommander jc = new JCommander(object); - jc.setProgramName(name, aliases); - jc.setDefaultProvider(m_defaultProvider); - jc.setAcceptUnknownOptions(m_acceptUnknownOptions); - ProgramName progName = jc.m_programName; - m_commands.put(progName, jc); + throw new ParameterException("Trying to add command " + object.getClass().getName() + + " without specifying its names in @Parameters"); + } + } + + /** + * Add a command object and its aliases. + */ + public void addCommand(String name, Object object, String... aliases) { + JCommander jc = new JCommander(options); + jc.addObject(object); + jc.createDescriptions(); + jc.setProgramName(name, aliases); + ProgramName progName = jc.programName; + commands.put(progName, jc); /* * Register aliases */ - //register command name as an alias of itself for reverse lookup - //Note: Name clash check is intentionally omitted to resemble the - // original behaviour of clashing commands. - // Aliases are, however, are strictly checked for name clashes. - aliasMap.put(new StringKey(name), progName); - for (String a : aliases) { - IKey alias = new StringKey(a); - //omit pointless aliases to avoid name clash exception - if (!alias.equals(name)) { - ProgramName mappedName = aliasMap.get(alias); - if (mappedName != null && !mappedName.equals(progName)) { - throw new ParameterException("Cannot set alias " + alias - + " for " + name - + " command because it has already been defined for " - + mappedName.m_name + " command"); - } - aliasMap.put(alias, progName); - } - } - } - - public Map<String, JCommander> getCommands() { - Map<String, JCommander> res = Maps.newLinkedHashMap(); - for (Map.Entry<ProgramName, JCommander> entry : m_commands.entrySet()) { - res.put(entry.getKey().m_name, entry.getValue()); - } - return res; - } - - public String getParsedCommand() { - return m_parsedCommand; - } - - /** - * The name of the command or the alias in the form it was - * passed to the command line. <code>null</code> if no - * command or alias was specified. - * - * @return Name of command or alias passed to command line. If none passed: <code>null</code>. - */ - public String getParsedAlias() { - return m_parsedAlias; - } - - /** - * @return n spaces - */ - private String s(int count) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < count; i++) { - result.append(" "); - } - - return result.toString(); - } - - /** - * @return the objects that JCommander will fill with the result of - * parsing the command line. - */ - public List<Object> getObjects() { - return m_objects; - } - - private ParameterDescription findParameterDescription(String arg) { - return FuzzyMap.findInMap(m_descriptions, new StringKey(arg), m_caseSensitiveOptions, - m_allowAbbreviatedOptions); - } - - private JCommander findCommand(ProgramName name) { - return FuzzyMap.findInMap(m_commands, name, - m_caseSensitiveOptions, m_allowAbbreviatedOptions); -// if (! m_caseSensitiveOptions) { -// return m_commands.get(name); -// } else { -// for (ProgramName c : m_commands.keySet()) { -// if (c.getName().equalsIgnoreCase(name.getName())) { -// return m_commands.get(c); -// } -// } -// } -// return null; - } - - private ProgramName findProgramName(String name) { - return FuzzyMap.findInMap(aliasMap, new StringKey(name), - m_caseSensitiveOptions, m_allowAbbreviatedOptions); - } - - /* - * Reverse lookup JCommand object by command's name or its alias - */ - private JCommander findCommandByAlias(String commandOrAlias) { - ProgramName progName = findProgramName(commandOrAlias); - if (progName == null) { - return null; - } - JCommander jc = findCommand(progName); - if (jc == null) { - throw new IllegalStateException( - "There appears to be inconsistency in the internal command database. " + - " This is likely a bug. Please report."); - } - return jc; - } - - /** - * Encapsulation of either a main application or an individual command. - */ - private static final class ProgramName implements IKey { - private final String m_name; - private final List<String> m_aliases; - - ProgramName(String name, List<String> aliases) { - m_name = name; - m_aliases = aliases; - } - - @Override - public String getName() { - return m_name; - } - - private String getDisplayName() { - StringBuilder sb = new StringBuilder(); - sb.append(m_name); - if (!m_aliases.isEmpty()) { - sb.append("("); - Iterator<String> aliasesIt = m_aliases.iterator(); - while (aliasesIt.hasNext()) { - sb.append(aliasesIt.next()); - if (aliasesIt.hasNext()) { - sb.append(","); - } - } - sb.append(")"); - } - return sb.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((m_name == null) ? 0 : m_name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ProgramName other = (ProgramName) obj; - if (m_name == null) { - if (other.m_name != null) - return false; - } else if (!m_name.equals(other.m_name)) - return false; - return true; + //register command name as an alias of itself for reverse lookup + //Note: Name clash check is intentionally omitted to resemble the + // original behaviour of clashing commands. + // Aliases are, however, are strictly checked for name clashes. + aliasMap.put(new StringKey(name), progName); + for (String a : aliases) { + IKey alias = new StringKey(a); + //omit pointless aliases to avoid name clash exception + if (!alias.equals(name)) { + ProgramName mappedName = aliasMap.get(alias); + if (mappedName != null && !mappedName.equals(progName)) { + throw new ParameterException("Cannot set alias " + alias + + " for " + name + + " command because it has already been defined for " + + mappedName.name + " command"); + } + aliasMap.put(alias, progName); + } + } + } + + public Map<String, JCommander> getCommands() { + Map<String, JCommander> res = Maps.newLinkedHashMap(); + for (Map.Entry<ProgramName, JCommander> entry : commands.entrySet()) { + res.put(entry.getKey().name, entry.getValue()); + } + return res; + } + + public String getParsedCommand() { + return parsedCommand; + } + + /** + * The name of the command or the alias in the form it was + * passed to the command line. <code>null</code> if no + * command or alias was specified. + * + * @return Name of command or alias passed to command line. If none passed: <code>null</code>. + */ + public String getParsedAlias() { + return parsedAlias; + } + + /** + * @return n spaces + */ + private String s(int count) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < count; i++) { + result.append(" "); + } + + return result.toString(); + } + + /** + * @return the objects that JCommander will fill with the result of + * parsing the command line. + */ + public List<Object> getObjects() { + return objects; + } + + private ParameterDescription findParameterDescription(String arg) { + return FuzzyMap.findInMap(descriptions, new StringKey(arg), + options.caseSensitiveOptions, options.allowAbbreviatedOptions); + } + + private JCommander findCommand(ProgramName name) { + return FuzzyMap.findInMap(commands, name, + options.caseSensitiveOptions, options.allowAbbreviatedOptions); + } + + private ProgramName findProgramName(String name) { + return FuzzyMap.findInMap(aliasMap, new StringKey(name), + options.caseSensitiveOptions, options.allowAbbreviatedOptions); } /* - * Important: ProgramName#toString() is used by longestName(Collection) function - * to format usage output. + * Reverse lookup JCommand object by command's name or its alias + */ + private JCommander findCommandByAlias(String commandOrAlias) { + ProgramName progName = findProgramName(commandOrAlias); + if (progName == null) { + return null; + } + JCommander jc = findCommand(progName); + if (jc == null) { + throw new IllegalStateException( + "There appears to be inconsistency in the internal command database. " + + " This is likely a bug. Please report."); + } + return jc; + } + + /** + * Encapsulation of either a main application or an individual command. */ - @Override - public String toString() { - return getDisplayName(); - - } - } - - public void setVerbose(int verbose) { - m_verbose = verbose; - } - - public void setCaseSensitiveOptions(boolean b) { - m_caseSensitiveOptions = b; - } - - public void setAllowAbbreviatedOptions(boolean b) { - m_allowAbbreviatedOptions = b; - } - - public void setAcceptUnknownOptions(boolean b) { - m_acceptUnknownOptions = b; - } - - public List<String> getUnknownOptions() { - return m_unknownArgs; - } - public void setAllowParameterOverwriting(boolean b) { - m_allowParameterOverwriting = b; - } - - public boolean isParameterOverwritingAllowed() { - return m_allowParameterOverwriting; - } -// public void setCaseSensitiveCommands(boolean b) { -// m_caseSensitiveCommands = b; -// } -} + private static final class ProgramName implements IKey { + private final String name; + private final List<String> aliases; + + ProgramName(String name, List<String> aliases) { + this.name = name; + this.aliases = aliases; + } + + @Override + public String getName() { + return name; + } + + private String getDisplayName() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + if (!aliases.isEmpty()) { + sb.append("("); + Iterator<String> aliasesIt = aliases.iterator(); + while (aliasesIt.hasNext()) { + sb.append(aliasesIt.next()); + if (aliasesIt.hasNext()) { + sb.append(","); + } + } + sb.append(")"); + } + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ProgramName other = (ProgramName) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + /* + * Important: ProgramName#toString() is used by longestName(Collection) function + * to format usage output. + */ + @Override + public String toString() { + return getDisplayName(); + + } + } + + public void setVerbose(int verbose) { + options.verbose = verbose; + } + + public void setCaseSensitiveOptions(boolean b) { + options.caseSensitiveOptions = b; + } + + public void setAllowAbbreviatedOptions(boolean b) { + options.allowAbbreviatedOptions = b; + } + + public void setAcceptUnknownOptions(boolean b) { + options.acceptUnknownOptions = b; + } + + public List<String> getUnknownOptions() { + return unknownArgs; + } + + public void setAllowParameterOverwriting(boolean b) { + options.allowParameterOverwriting = b; + } + + public boolean isParameterOverwritingAllowed() { + return options.allowParameterOverwriting; + } + + /** + * Sets the charset used to expand {@code @files}. + * @param charset the charset + */ + public void setAtFileCharset(Charset charset) { + options.atFileCharset = charset; + } + +} diff --git a/src/main/java/com/beust/jcommander/MissingCommandException.java b/src/main/java/com/beust/jcommander/MissingCommandException.java index 1d572ab..7e8980b 100644 --- a/src/main/java/com/beust/jcommander/MissingCommandException.java +++ b/src/main/java/com/beust/jcommander/MissingCommandException.java @@ -26,11 +26,22 @@ package com.beust.jcommander; @SuppressWarnings("serial") public class MissingCommandException extends ParameterException { - public MissingCommandException(String string) { - super(string); + /** + * the command passed by the user. + */ + private final String unknownCommand; + + public MissingCommandException(String message) { + this(message, null); + } + + public MissingCommandException(String message, String command) { + super(message); + this.unknownCommand = command; } - public MissingCommandException(Throwable t) { - super(t); + public String getUnknownCommand() { + return unknownCommand; } + } diff --git a/src/main/java/com/beust/jcommander/Parameter.java b/src/main/java/com/beust/jcommander/Parameter.java index d8cf87d..2fc00c5 100644 --- a/src/main/java/com/beust/jcommander/Parameter.java +++ b/src/main/java/com/beust/jcommander/Parameter.java @@ -90,12 +90,12 @@ public @interface Parameter { /** * Validate the parameter found on the command line. */ - Class<? extends IParameterValidator> validateWith() default NoValidator.class; + Class<? extends IParameterValidator>[] validateWith() default NoValidator.class; /** * Validate the value for this parameter. */ - Class<? extends IValueValidator> validateValueWith() default NoValueValidator.class; + Class<? extends IValueValidator>[] validateValueWith() default NoValueValidator.class; /** * @return true if this parameter has a variable arity. See @{IVariableArity} @@ -122,9 +122,14 @@ public @interface Parameter { /** * If true, this parameter can be overwritten through a file or another appearance of the parameter - * @return + * @return nc */ boolean forceNonOverwritable() default false; + /** + * If specified, this number will be used to order the description of this parameter when usage() is invoked. + * @return + */ + int order() default -1; } diff --git a/src/main/java/com/beust/jcommander/ParameterDescription.java b/src/main/java/com/beust/jcommander/ParameterDescription.java index 2ef2d5f..bed5ba1 100644 --- a/src/main/java/com/beust/jcommander/ParameterDescription.java +++ b/src/main/java/com/beust/jcommander/ParameterDescription.java @@ -21,36 +21,28 @@ package com.beust.jcommander; import com.beust.jcommander.validators.NoValidator; import com.beust.jcommander.validators.NoValueValidator; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.*; import java.util.ResourceBundle; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; public class ParameterDescription { - private Object m_object; + private Object object; - private WrappedParameter m_wrappedParameter; - private Parameter m_parameterAnnotation; - private DynamicParameter m_dynamicParameterAnnotation; + private WrappedParameter wrappedParameter; + private Parameter parameterAnnotation; + private DynamicParameter dynamicParameterAnnotation; /** The field/method */ - private Parameterized m_parameterized; + private Parameterized parameterized; /** Keep track of whether a value was added to flag an error */ - private boolean m_assigned = false; - private ResourceBundle m_bundle; - private String m_description; - private JCommander m_jCommander; - private Object m_default; + private boolean assigned = false; + private ResourceBundle bundle; + private String description; + private JCommander jCommander; + private Object defaultObject; /** Longest of the names(), used to present usage() alphabetically */ - private String m_longestName = ""; + private String longestName = ""; public ParameterDescription(Object object, DynamicParameter annotation, Parameterized parameterized, @@ -61,15 +53,15 @@ public class ParameterDescription { + "Map but is " + parameterized.getType().getName()); } - m_dynamicParameterAnnotation = annotation; - m_wrappedParameter = new WrappedParameter(m_dynamicParameterAnnotation); + dynamicParameterAnnotation = annotation; + wrappedParameter = new WrappedParameter(dynamicParameterAnnotation); init(object, parameterized, bundle, jc); } public ParameterDescription(Object object, Parameter annotation, Parameterized parameterized, ResourceBundle bundle, JCommander jc) { - m_parameterAnnotation = annotation; - m_wrappedParameter = new WrappedParameter(m_parameterAnnotation); + parameterAnnotation = annotation; + wrappedParameter = new WrappedParameter(parameterAnnotation); init(object, parameterized, bundle, jc); } @@ -100,90 +92,86 @@ public class ParameterDescription { } private void initDescription(String description, String descriptionKey, String[] names) { - m_description = description; + this.description = description; if (! "".equals(descriptionKey)) { - if (m_bundle != null) { - m_description = m_bundle.getString(descriptionKey); - } else { -// JCommander.getConsole().println("Warning: field " + object.getClass() + "." + field.getName() -// + " has a descriptionKey but no bundle was defined with @ResourceBundle, using " + -// "default description:'" + m_description + "'"); + if (bundle != null) { + this.description = bundle.getString(descriptionKey); } } for (String name : names) { - if (name.length() > m_longestName.length()) m_longestName = name; + if (name.length() > longestName.length()) longestName = name; } } @SuppressWarnings("unchecked") private void init(Object object, Parameterized parameterized, ResourceBundle bundle, JCommander jCommander) { - m_object = object; - m_parameterized = parameterized; - m_bundle = bundle; - if (m_bundle == null) { - m_bundle = findResourceBundle(object); + this.object = object; + this.parameterized = parameterized; + this.bundle = bundle; + if (this.bundle == null) { + this.bundle = findResourceBundle(object); } - m_jCommander = jCommander; + this.jCommander = jCommander; - if (m_parameterAnnotation != null) { + if (parameterAnnotation != null) { String description; if (Enum.class.isAssignableFrom(parameterized.getType()) - && m_parameterAnnotation.description().isEmpty()) { + && parameterAnnotation.description().isEmpty()) { description = "Options: " + EnumSet.allOf((Class<? extends Enum>) parameterized.getType()); }else { - description = m_parameterAnnotation.description(); + description = parameterAnnotation.description(); } - initDescription(description, m_parameterAnnotation.descriptionKey(), - m_parameterAnnotation.names()); - } else if (m_dynamicParameterAnnotation != null) { - initDescription(m_dynamicParameterAnnotation.description(), - m_dynamicParameterAnnotation.descriptionKey(), - m_dynamicParameterAnnotation.names()); + initDescription(description, parameterAnnotation.descriptionKey(), + parameterAnnotation.names()); + } else if (dynamicParameterAnnotation != null) { + initDescription(dynamicParameterAnnotation.description(), + dynamicParameterAnnotation.descriptionKey(), + dynamicParameterAnnotation.names()); } else { throw new AssertionError("Shound never happen"); } try { - m_default = parameterized.get(object); + defaultObject = parameterized.get(object); } catch (Exception e) { } // // Validate default values, if any and if applicable // - if (m_default != null) { - if (m_parameterAnnotation != null) { - validateDefaultValues(m_parameterAnnotation.names()); + if (defaultObject != null) { + if (parameterAnnotation != null) { + validateDefaultValues(parameterAnnotation.names()); } } } private void validateDefaultValues(String[] names) { String name = names.length > 0 ? names[0] : ""; - validateValueParameter(name, m_default); + validateValueParameter(name, defaultObject); } public String getLongestName() { - return m_longestName; + return longestName; } public Object getDefault() { - return m_default; + return defaultObject; } public String getDescription() { - return m_description; + return description; } public Object getObject() { - return m_object; + return object; } public String getNames() { StringBuilder sb = new StringBuilder(); - String[] names = m_wrappedParameter.names(); + String[] names = wrappedParameter.names(); for (int i = 0; i < names.length; i++) { if (i > 0) sb.append(", "); sb.append(names[i]); @@ -192,17 +180,17 @@ public class ParameterDescription { } public WrappedParameter getParameter() { - return m_wrappedParameter; + return wrappedParameter; } public Parameterized getParameterized() { - return m_parameterized; + return parameterized; } private boolean isMultiOption() { - Class<?> fieldType = m_parameterized.getType(); + Class<?> fieldType = parameterized.getType(); return fieldType.equals(List.class) || fieldType.equals(Set.class) - || m_parameterized.isDynamicParameter(); + || parameterized.isDynamicParameter(); } public void addValue(String value) { @@ -213,12 +201,12 @@ public class ParameterDescription { * @return true if this parameter received a value during the parsing phase. */ public boolean isAssigned() { - return m_assigned; + return assigned; } public void setAssigned(boolean b) { - m_assigned = b; + assigned = b; } /** @@ -227,52 +215,132 @@ public class ParameterDescription { * converter, and if we can't find any, throw an exception. */ public void addValue(String value, boolean isDefault) { + addValue(null, value, isDefault, true, -1); + } + + Object addValue(String name, String value, boolean isDefault, boolean validate, int currentIndex) { p("Adding " + (isDefault ? "default " : "") + "value:" + value - + " to parameter:" + m_parameterized.getName()); - String name = m_wrappedParameter.names()[0]; - if (m_assigned && ! isMultiOption() && !m_jCommander.isParameterOverwritingAllowed() || isNonOverwritableForced()) { + + " to parameter:" + parameterized.getName()); + if(name == null) { + name = wrappedParameter.names()[0]; + } + if (currentIndex == 00 && assigned && ! isMultiOption() && !jCommander.isParameterOverwritingAllowed() + || isNonOverwritableForced()) { throw new ParameterException("Can only specify option " + name + " once."); } - validateParameter(name, value); + if (validate) { + validateParameter(name, value); + } - Class<?> type = m_parameterized.getType(); + Class<?> type = parameterized.getType(); - Object convertedValue = m_jCommander.convertValue(this, value); - validateValueParameter(name, convertedValue); + Object convertedValue = jCommander.convertValue(getParameterized(), getParameterized().getType(), name, value); + if (validate) { + validateValueParameter(name, convertedValue); + } boolean isCollection = Collection.class.isAssignableFrom(type); + Object finalValue; if (isCollection) { @SuppressWarnings("unchecked") - Collection<Object> l = (Collection<Object>) m_parameterized.get(m_object); + Collection<Object> l = (Collection<Object>) parameterized.get(object); if (l == null || fieldIsSetForTheFirstTime(isDefault)) { - l = newCollection(type); - m_parameterized.set(m_object, l); + l = newCollection(type); + parameterized.set(object, l); } if (convertedValue instanceof Collection) { - l.addAll((Collection) convertedValue); - } else { // if (isMainParameter || m_parameterAnnotation.arity() > 1) { - l.add(convertedValue); -// } else { -// l. + l.addAll((Collection) convertedValue); + } else { + l.add(convertedValue); } + finalValue = l; } else { - m_wrappedParameter.addValue(m_parameterized, m_object, convertedValue); + // If the field type is not a collection, see if it's a type that contains @SubParameters annotations + List<SubParameterIndex> subParameters = findSubParameters(type); + if (! subParameters.isEmpty()) { + // @SubParameters found + finalValue = handleSubParameters(value, currentIndex, type, subParameters); + } else { + // No, regular parameter + wrappedParameter.addValue(parameterized, object, convertedValue); + finalValue = convertedValue; + } } - if (! isDefault) m_assigned = true; + if (! isDefault) assigned = true; + + return finalValue; + } + + private Object handleSubParameters(String value, int currentIndex, Class<?> type, + List<SubParameterIndex> subParameters) { + Object finalValue;// Yes, assign each following argument to the corresponding field of that object + SubParameterIndex sai = null; + for (SubParameterIndex si: subParameters) { + if (si.order == currentIndex) { + sai = si; + break; + } + } + if (sai != null) { + Object objectValue = parameterized.get(object); + try { + if (objectValue == null) { + objectValue = type.newInstance(); + parameterized.set(object, objectValue); + } + wrappedParameter.addValue(parameterized, objectValue, value, sai.field); + finalValue = objectValue; + } catch (InstantiationException | IllegalAccessException e) { + throw new ParameterException("Couldn't instantiate " + type, e); + } + } else { + throw new ParameterException("Couldn't find where to assign parameter " + value + " in " + type); + } + return finalValue; + } + + public Parameter getParameterAnnotation() { + return parameterAnnotation; + } + + class SubParameterIndex { + int order = -1; + Field field; + + public SubParameterIndex(int order, Field field) { + this.order = order; + this.field = field; + } + } + + private List<SubParameterIndex> findSubParameters(Class<?> type) { + List<SubParameterIndex> result = new ArrayList<>(); + for (Field field: type.getDeclaredFields()) { + Annotation subParameter = field.getAnnotation(SubParameter.class); + if (subParameter != null) { + SubParameter sa = (SubParameter) subParameter; + result.add(new SubParameterIndex(sa.order(), field)); + } + } + return result; } private void validateParameter(String name, String value) { - Class<? extends IParameterValidator> validator = m_wrappedParameter.validateWith(); - if (validator != null) { - validateParameter(this, validator, name, value); + final Class<? extends IParameterValidator> validators[] = wrappedParameter.validateWith(); + if (validators != null && validators.length > 0) { + for(final Class<? extends IParameterValidator> validator: validators) { + validateParameter(this, validator, name, value); + } } } - private void validateValueParameter(String name, Object value) { - Class<? extends IValueValidator> validator = m_wrappedParameter.validateValueWith(); - if (validator != null) { - validateValueParameter(validator, name, value); + void validateValueParameter(String name, Object value) { + final Class<? extends IValueValidator> validators[] = wrappedParameter.validateValueWith(); + if (validators != null && validators.length > 0) { + for(final Class<? extends IValueValidator> validator: validators) { + validateValueParameter(validator, name, value); + } } } @@ -283,9 +351,7 @@ public class ParameterDescription { p("Validating value parameter:" + name + " value:" + value + " validator:" + validator); } validator.newInstance().validate(name, value); - } catch (InstantiationException e) { - throw new ParameterException("Can't instantiate validator:" + e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new ParameterException("Can't instantiate validator:" + e); } } @@ -294,6 +360,7 @@ public class ParameterDescription { Class<? extends IParameterValidator> validator, String name, String value) { try { + if (validator != NoValidator.class) { p("Validating parameter:" + name + " value:" + value + " validator:" + validator); } @@ -302,9 +369,7 @@ public class ParameterDescription { IParameterValidator2 instance = (IParameterValidator2) validator.newInstance(); instance.validate(name, value, pd); } - } catch (InstantiationException e) { - throw new ParameterException("Can't instantiate validator:" + e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new ParameterException("Can't instantiate validator:" + e); } catch(ParameterException ex) { throw ex; @@ -336,7 +401,7 @@ public class ParameterDescription { * being added to the field. */ private boolean fieldIsSetForTheFirstTime(boolean isDefault) { - return (!isDefault && !m_assigned); + return (!isDefault && !assigned); } private static void p(String string) { @@ -347,18 +412,18 @@ public class ParameterDescription { @Override public String toString() { - return "[ParameterDescription " + m_parameterized.getName() + "]"; + return "[ParameterDescription " + parameterized.getName() + "]"; } public boolean isDynamicParameter() { - return m_dynamicParameterAnnotation != null; + return dynamicParameterAnnotation != null; } public boolean isHelp() { - return m_wrappedParameter.isHelp(); + return wrappedParameter.isHelp(); } public boolean isNonOverwritableForced() { - return m_wrappedParameter.isNonOverwritableForced(); + return wrappedParameter.isNonOverwritableForced(); } } diff --git a/src/main/java/com/beust/jcommander/ParameterException.java b/src/main/java/com/beust/jcommander/ParameterException.java index 2bba7d1..41570ff 100644 --- a/src/main/java/com/beust/jcommander/ParameterException.java +++ b/src/main/java/com/beust/jcommander/ParameterException.java @@ -26,7 +26,6 @@ package com.beust.jcommander; */ @SuppressWarnings("serial") public class ParameterException extends RuntimeException { - public ParameterException(Throwable t) { super(t); } @@ -37,6 +36,21 @@ public class ParameterException extends RuntimeException { public ParameterException(String string, Throwable t) { super(string, t); - } + } + + private JCommander jc; + + public void setJCommander(JCommander jc) { + this.jc = jc; + } + + public JCommander getJCommander() { + return jc; + } + public void usage() { + if (jc != null) { + jc.usage(); + } + } } diff --git a/src/main/java/com/beust/jcommander/Parameterized.java b/src/main/java/com/beust/jcommander/Parameterized.java index ff8753b..3264008 100644 --- a/src/main/java/com/beust/jcommander/Parameterized.java +++ b/src/main/java/com/beust/jcommander/Parameterized.java @@ -1,14 +1,18 @@ package com.beust.jcommander; import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Sets; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * Encapsulate a field or a method annotated with @Parameter or @DynamicParameter @@ -16,129 +20,175 @@ import java.util.List; public class Parameterized { // Either a method or a field - private Field m_field; - private Method m_method; - private Method m_getter; + private Field field; + private Method method; + private Method getter; // Either of these two - private WrappedParameter m_wrappedParameter; - private ParametersDelegate m_parametersDelegate; + private WrappedParameter wrappedParameter; + private ParametersDelegate parametersDelegate; public Parameterized(WrappedParameter wp, ParametersDelegate pd, Field field, Method method) { - m_wrappedParameter = wp; - m_method = method; - m_field = field; - if (m_field != null) { - m_field.setAccessible(true); + wrappedParameter = wp; + this.method = method; + this.field = field; + if (this.field != null) { + setFieldAccessible(this.field); } - m_parametersDelegate = pd; + parametersDelegate = pd; + } + + /** + * Recursive handler for describing the set of classes while + * using the setOfClasses parameter as a collector + * + * @param inputClass the class to analyze + * @param setOfClasses the set collector to collect the results + */ + private static void describeClassTree(Class<?> inputClass, Set<Class<?>> setOfClasses) { + // can't map null class + if(inputClass == null) { + return; + } + + // don't further analyze a class that has been analyzed already + if(Object.class.equals(inputClass) || setOfClasses.contains(inputClass)) { + return; + } + + // add to analysis set + setOfClasses.add(inputClass); + + // perform super class analysis + describeClassTree(inputClass.getSuperclass(), setOfClasses); + + // perform analysis on interfaces + for(Class<?> hasInterface : inputClass.getInterfaces()) { + describeClassTree(hasInterface, setOfClasses); + } + } + + /** + * Given an object return the set of classes that it extends + * or implements. + * + * @param arg object to describe + * @return set of classes that are implemented or extended by that object + */ + private static Set<Class<?>> describeClassTree(Class<?> inputClass) { + if(inputClass == null) { + return Collections.emptySet(); + } + + // create result collector + Set<Class<?>> classes = Sets.newLinkedHashSet(); + + // describe tree + describeClassTree(inputClass, classes); + + return classes; } public static List<Parameterized> parseArg(Object arg) { List<Parameterized> result = Lists.newArrayList(); - Class<? extends Object> cls = arg.getClass(); - while (!Object.class.equals(cls)) { + Class<?> rootClass = arg.getClass(); + + // get the list of types that are extended or implemented by the root class + // and all of its parent types + Set<Class<?>> types = describeClassTree(rootClass); + + // analyze each type + for(Class<?> cls : types) { + + // check fields for (Field f : cls.getDeclaredFields()) { Annotation annotation = f.getAnnotation(Parameter.class); Annotation delegateAnnotation = f.getAnnotation(ParametersDelegate.class); Annotation dynamicParameter = f.getAnnotation(DynamicParameter.class); if (annotation != null) { result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null, - f, null)); + f, null)); } else if (dynamicParameter != null) { result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null, - f, null)); + f, null)); } else if (delegateAnnotation != null) { result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation, - f, null)); + f, null)); } } - cls = cls.getSuperclass(); - } - // Reassigning - cls = arg.getClass(); - while (!Object.class.equals(cls)) { + // check methods for (Method m : cls.getDeclaredMethods()) { + m.setAccessible(true); Annotation annotation = m.getAnnotation(Parameter.class); Annotation delegateAnnotation = m.getAnnotation(ParametersDelegate.class); Annotation dynamicParameter = m.getAnnotation(DynamicParameter.class); if (annotation != null) { result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null, - null, m)); + null, m)); } else if (dynamicParameter != null) { - result.add(new Parameterized(new WrappedParameter((DynamicParameter) annotation), null, - null, m)); + result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null, + null, m)); } else if (delegateAnnotation != null) { result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation, - null, m)); + null, m)); } } - cls = cls.getSuperclass(); } return result; } public WrappedParameter getWrappedParameter() { - return m_wrappedParameter; + return wrappedParameter; } public Class<?> getType() { - if (m_method != null) { - return m_method.getParameterTypes()[0]; + if (method != null) { + return method.getParameterTypes()[0]; } else { - return m_field.getType(); + return field.getType(); } } public String getName() { - if (m_method != null) { - return m_method.getName(); + if (method != null) { + return method.getName(); } else { - return m_field.getName(); + return field.getName(); } } public Object get(Object object) { try { - if (m_method != null) { - if (m_getter == null) { - m_getter = m_method.getDeclaringClass() - .getMethod("g" + m_method.getName().substring(1), - new Class[0]); + if (method != null) { + if (getter == null) { + getter = method.getDeclaringClass() + .getMethod("g" + method.getName().substring(1)); } - return m_getter.invoke(object); + return getter.invoke(object); } else { - return m_field.get(object); + return field.get(object); } - } catch (SecurityException e) { + } catch (SecurityException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { throw new ParameterException(e); } catch (NoSuchMethodException e) { // Try to find a field - String name = m_method.getName(); + String name = method.getName(); String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4); Object result = null; try { - Field field = m_method.getDeclaringClass().getDeclaredField(fieldName); + Field field = method.getDeclaringClass().getDeclaredField(fieldName); if (field != null) { - field.setAccessible(true); + setFieldAccessible(field); result = field.get(object); } - } catch(NoSuchFieldException ex) { - // ignore - } catch(IllegalAccessException ex) { + } catch(NoSuchFieldException | IllegalAccessException ex) { // ignore } return result; - } catch (IllegalArgumentException e) { - throw new ParameterException(e); - } catch (IllegalAccessException e) { - throw new ParameterException(e); - } catch (InvocationTargetException e) { - throw new ParameterException(e); } } @@ -146,8 +196,8 @@ public class Parameterized { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((m_field == null) ? 0 : m_field.hashCode()); - result = prime * result + ((m_method == null) ? 0 : m_method.hashCode()); + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + ((method == null) ? 0 : method.hashCode()); return result; } @@ -160,73 +210,84 @@ public class Parameterized { if (getClass() != obj.getClass()) return false; Parameterized other = (Parameterized) obj; - if (m_field == null) { - if (other.m_field != null) + if (field == null) { + if (other.field != null) return false; - } else if (!m_field.equals(other.m_field)) + } else if (!field.equals(other.field)) return false; - if (m_method == null) { - if (other.m_method != null) + if (method == null) { + if (other.method != null) return false; - } else if (!m_method.equals(other.m_method)) + } else if (!method.equals(other.method)) return false; return true; } public boolean isDynamicParameter(Field field) { - if (m_method != null) { - return m_method.getAnnotation(DynamicParameter.class) != null; + if (method != null) { + return method.getAnnotation(DynamicParameter.class) != null; } else { - return m_field.getAnnotation(DynamicParameter.class) != null; + return this.field.getAnnotation(DynamicParameter.class) != null; + } + } + + private static void setFieldAccessible(Field f) { + if (Modifier.isFinal(f.getModifiers())) { + throw new ParameterException( + "Cannot use final field " + f.getDeclaringClass().getName() + "#" + f.getName() + " as a parameter;" + + " compile-time constant inlining may hide new values written to it."); } + f.setAccessible(true); + } + + private static String errorMessage(Method m, Exception ex) { + return "Could not invoke " + m + "\n Reason: " + ex.getMessage(); } public void set(Object object, Object value) { try { - if (m_method != null) { - m_method.invoke(object, value); + if (method != null) { + method.invoke(object, value); } else { - m_field.set(object, value); + field.set(object, value); } - } catch (IllegalArgumentException ex) { - throw new ParameterException(ex); - } catch (IllegalAccessException ex) { - throw new ParameterException(ex); + } catch (IllegalAccessException | IllegalArgumentException ex) { + throw new ParameterException(errorMessage(method, ex)); } catch (InvocationTargetException ex) { // If a ParameterException was thrown, don't wrap it into another one if (ex.getTargetException() instanceof ParameterException) { throw (ParameterException) ex.getTargetException(); } else { - throw new ParameterException(ex); + throw new ParameterException(errorMessage(method, ex)); } } } public ParametersDelegate getDelegateAnnotation() { - return m_parametersDelegate; + return parametersDelegate; } public Type getGenericType() { - if (m_method != null) { - return m_method.getGenericParameterTypes()[0]; + if (method != null) { + return method.getGenericParameterTypes()[0]; } else { - return m_field.getGenericType(); + return field.getGenericType(); } } public Parameter getParameter() { - return m_wrappedParameter.getParameter(); + return wrappedParameter.getParameter(); } /** * @return the generic type of the collection for this field, or null if not applicable. */ public Type findFieldGenericType() { - if (m_method != null) { + if (method != null) { return null; } else { - if (m_field.getGenericType() instanceof ParameterizedType) { - ParameterizedType p = (ParameterizedType) m_field.getGenericType(); + if (field.getGenericType() instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) field.getGenericType(); Type cls = p.getActualTypeArguments()[0]; if (cls instanceof Class) { return cls; @@ -238,7 +299,7 @@ public class Parameterized { } public boolean isDynamicParameter() { - return m_wrappedParameter.getDynamicParameter() != null; + return wrappedParameter.getDynamicParameter() != null; } } diff --git a/src/main/java/com/beust/jcommander/Parameters.java b/src/main/java/com/beust/jcommander/Parameters.java index f2e8c76..edab036 100644 --- a/src/main/java/com/beust/jcommander/Parameters.java +++ b/src/main/java/com/beust/jcommander/Parameters.java @@ -34,8 +34,6 @@ import static java.lang.annotation.ElementType.TYPE; @Inherited public @interface Parameters { - public static final String DEFAULT_OPTION_PREFIXES = "-"; - /** * The name of the resource bundle to use for this class. */ @@ -47,11 +45,6 @@ public @interface Parameters { String separators() default " "; /** - * What characters an option starts with. - */ - String optionPrefixes() default DEFAULT_OPTION_PREFIXES; - - /** * If the annotated class was added to {@link JCommander} as a command with * {@link JCommander#addCommand}, then this string will be displayed in the * description when @{link JCommander#usage} is invoked. diff --git a/src/main/java/com/beust/jcommander/StringKey.java b/src/main/java/com/beust/jcommander/StringKey.java index 09d1149..11a7d18 100644 --- a/src/main/java/com/beust/jcommander/StringKey.java +++ b/src/main/java/com/beust/jcommander/StringKey.java @@ -4,27 +4,27 @@ import com.beust.jcommander.FuzzyMap.IKey; public class StringKey implements IKey { - private String m_name; + private String name; public StringKey(String name) { - m_name = name; + this.name = name; } @Override public String getName() { - return m_name; + return name; } @Override public String toString() { - return m_name; + return name; } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((m_name == null) ? 0 : m_name.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @@ -37,10 +37,10 @@ public class StringKey implements IKey { if (getClass() != obj.getClass()) return false; StringKey other = (StringKey) obj; - if (m_name == null) { - if (other.m_name != null) + if (name == null) { + if (other.name != null) return false; - } else if (!m_name.equals(other.m_name)) + } else if (!name.equals(other.name)) return false; return true; } diff --git a/src/main/java/com/beust/jcommander/SubParameter.java b/src/main/java/com/beust/jcommander/SubParameter.java new file mode 100644 index 0000000..22adfc4 --- /dev/null +++ b/src/main/java/com/beust/jcommander/SubParameter.java @@ -0,0 +1,17 @@ +package com.beust.jcommander; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + +/** + * @author Cedric Beust <cedric@refresh.io> + * @since 02 12, 2017 + */ +@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) +public @interface SubParameter { + int order() default -1; +} diff --git a/src/main/java/com/beust/jcommander/WrappedParameter.java b/src/main/java/com/beust/jcommander/WrappedParameter.java index f4e7d56..e49245a 100644 --- a/src/main/java/com/beust/jcommander/WrappedParameter.java +++ b/src/main/java/com/beust/jcommander/WrappedParameter.java @@ -1,5 +1,6 @@ package com.beust.jcommander; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -7,68 +8,81 @@ import java.lang.reflect.Method; * Encapsulates the operations common to @Parameter and @DynamicParameter */ public class WrappedParameter { - private Parameter m_parameter; - private DynamicParameter m_dynamicParameter; + private Parameter parameter; + private DynamicParameter dynamicParameter; public WrappedParameter(Parameter p) { - m_parameter = p; + parameter = p; } public WrappedParameter(DynamicParameter p) { - m_dynamicParameter = p; + dynamicParameter = p; } public Parameter getParameter() { - return m_parameter; + return parameter; } public DynamicParameter getDynamicParameter() { - return m_dynamicParameter; + return dynamicParameter; } public int arity() { - return m_parameter != null ? m_parameter.arity() : 1; + return parameter != null ? parameter.arity() : 1; } public boolean hidden() { - return m_parameter != null ? m_parameter.hidden() : m_dynamicParameter.hidden(); + return parameter != null ? parameter.hidden() : dynamicParameter.hidden(); } public boolean required() { - return m_parameter != null ? m_parameter.required() : m_dynamicParameter.required(); + return parameter != null ? parameter.required() : dynamicParameter.required(); } public boolean password() { - return m_parameter != null ? m_parameter.password() : false; + return parameter != null ? parameter.password() : false; } public String[] names() { - return m_parameter != null ? m_parameter.names() : m_dynamicParameter.names(); + return parameter != null ? parameter.names() : dynamicParameter.names(); } public boolean variableArity() { - return m_parameter != null ? m_parameter.variableArity() : false; + return parameter != null ? parameter.variableArity() : false; } - public Class<? extends IParameterValidator> validateWith() { - return m_parameter != null ? m_parameter.validateWith() : m_dynamicParameter.validateWith(); + public Class<? extends IParameterValidator>[] validateWith() { + return parameter != null ? parameter.validateWith() : dynamicParameter.validateWith(); } - public Class<? extends IValueValidator> validateValueWith() { - return m_parameter != null - ? m_parameter.validateValueWith() - : m_dynamicParameter.validateValueWith(); + public Class<? extends IValueValidator>[] validateValueWith() { + return parameter != null + ? parameter.validateValueWith() + : dynamicParameter.validateValueWith(); } public boolean echoInput() { - return m_parameter != null ? m_parameter.echoInput() : false; + return parameter != null ? parameter.echoInput() : false; } public void addValue(Parameterized parameterized, Object object, Object value) { - if (m_parameter != null) { - parameterized.set(object, value); + try { + addValue(parameterized, object, value, null); + } catch (IllegalAccessException e) { + throw new ParameterException("Couldn't set " + object + " to " + value, e); + } + } + + public void addValue(Parameterized parameterized, Object object, Object value, Field field) + throws IllegalAccessException { + if (parameter != null) { + if (field != null) { + field.set(object, value); + } else { + parameterized.set(object, value); + } } else { - String a = m_dynamicParameter.assignment(); + String a = dynamicParameter.assignment(); String sv = value.toString(); int aInd = sv.indexOf(a); @@ -86,13 +100,7 @@ public class WrappedParameter { Method m; m = findPut(parameterized.getType()); m.invoke(parameterized.get(object), key, value); - } catch (SecurityException e) { - e.printStackTrace(); - } catch(IllegalAccessException e) { - e.printStackTrace(); - } catch(InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { + } catch (SecurityException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } } @@ -102,14 +110,14 @@ public class WrappedParameter { } public String getAssignment() { - return m_dynamicParameter != null ? m_dynamicParameter.assignment() : ""; + return dynamicParameter != null ? dynamicParameter.assignment() : ""; } public boolean isHelp() { - return m_parameter != null && m_parameter.help(); + return parameter != null && parameter.help(); } public boolean isNonOverwritableForced() { - return m_parameter != null && m_parameter.forceNonOverwritable(); + return parameter != null && parameter.forceNonOverwritable(); } } diff --git a/src/main/java/com/beust/jcommander/converters/BaseConverter.java b/src/main/java/com/beust/jcommander/converters/BaseConverter.java index 4287163..02e94b8 100644 --- a/src/main/java/com/beust/jcommander/converters/BaseConverter.java +++ b/src/main/java/com/beust/jcommander/converters/BaseConverter.java @@ -27,14 +27,14 @@ import com.beust.jcommander.IStringConverter; */ abstract public class BaseConverter<T> implements IStringConverter<T> { - private String m_optionName; + private String optionName; public BaseConverter(String optionName) { - m_optionName = optionName; + this.optionName = optionName; } public String getOptionName() { - return m_optionName; + return optionName; } protected String getErrorString(String value, String to) { diff --git a/src/main/java/com/beust/jcommander/converters/CharArrayConverter.java b/src/main/java/com/beust/jcommander/converters/CharArrayConverter.java new file mode 100644 index 0000000..5252f63 --- /dev/null +++ b/src/main/java/com/beust/jcommander/converters/CharArrayConverter.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander.converters; + +import com.beust.jcommander.IStringConverter; + +/** + * Converts a String to a char[]. + * + * @author Gary Gregory + */ +public class CharArrayConverter implements IStringConverter<char[]> { + + public char[] convert(final String value) { + return value.toCharArray(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/beust/jcommander/converters/DefaultListConverter.java b/src/main/java/com/beust/jcommander/converters/DefaultListConverter.java new file mode 100644 index 0000000..00bf9ac --- /dev/null +++ b/src/main/java/com/beust/jcommander/converters/DefaultListConverter.java @@ -0,0 +1,36 @@ +package com.beust.jcommander.converters; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.internal.Lists; + +import java.util.List; + +/** + * A converter to obtain a list of elements. + * @param <T> the element type + * @author simon04 + */ +public class DefaultListConverter<T> implements IStringConverter<List<T>> { + + private final IParameterSplitter splitter; + private final IStringConverter<T> converter; + + /** + * Constructs a new converter. + * @param splitter to split value into list of arguments + * @param converter to convert list of arguments to target element type + */ + public DefaultListConverter(IParameterSplitter splitter, IStringConverter<T> converter) { + this.splitter = splitter; + this.converter = converter; + } + + @Override + public List<T> convert(String value) { + List<T> result = Lists.newArrayList(); + for (String param : splitter.split(value)) { + result.add(converter.convert(param)); + } + return result; + } +} diff --git a/src/main/java/com/beust/jcommander/converters/EnumConverter.java b/src/main/java/com/beust/jcommander/converters/EnumConverter.java new file mode 100644 index 0000000..3e850bb --- /dev/null +++ b/src/main/java/com/beust/jcommander/converters/EnumConverter.java @@ -0,0 +1,42 @@ +package com.beust.jcommander.converters; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; + +import java.util.EnumSet; + +/** + * A converter to parse enums + * @param <T> the enum type + * @author simon04 + */ +public class EnumConverter<T extends Enum<T>> implements IStringConverter<T> { + + private final String optionName; + private final Class<T> clazz; + + /** + * Constructs a new converter. + * @param optionName the option name for error reporting + * @param clazz the enum class + */ + public EnumConverter(String optionName, Class<T> clazz) { + this.optionName = optionName; + this.clazz = clazz; + } + + @Override + public T convert(String value) { + try { + try { + return Enum.valueOf(clazz, value); + } catch (IllegalArgumentException e) { + return Enum.valueOf(clazz, value.toUpperCase()); + } + } catch (Exception e) { + throw new ParameterException("Invalid value for " + optionName + " parameter. Allowed values:" + + EnumSet.allOf(clazz)); + + } + } +} diff --git a/src/main/java/com/beust/jcommander/converters/InetAddressConverter.java b/src/main/java/com/beust/jcommander/converters/InetAddressConverter.java new file mode 100644 index 0000000..b6f391a --- /dev/null +++ b/src/main/java/com/beust/jcommander/converters/InetAddressConverter.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander.converters; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import com.beust.jcommander.IStringConverter; + +/** + * Converts {@code String}s to {@code InetAddress}'. + */ +public class InetAddressConverter implements IStringConverter<InetAddress> { + + @Override + public InetAddress convert(String host) { + try { + return InetAddress.getByName(host); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(host, e); + } + } + +}
\ No newline at end of file diff --git a/src/main/java/com/beust/jcommander/defaultprovider/PropertyFileDefaultProvider.java b/src/main/java/com/beust/jcommander/defaultprovider/PropertyFileDefaultProvider.java index d5401a1..ac66df6 100644 --- a/src/main/java/com/beust/jcommander/defaultprovider/PropertyFileDefaultProvider.java +++ b/src/main/java/com/beust/jcommander/defaultprovider/PropertyFileDefaultProvider.java @@ -32,7 +32,7 @@ import java.util.Properties; */ public class PropertyFileDefaultProvider implements IDefaultProvider { public static final String DEFAULT_FILE_NAME = "jcommander.properties"; - private Properties m_properties; + private Properties properties; public PropertyFileDefaultProvider() { init(DEFAULT_FILE_NAME); @@ -44,10 +44,10 @@ public class PropertyFileDefaultProvider implements IDefaultProvider { private void init(String fileName) { try { - m_properties = new Properties(); + properties = new Properties(); URL url = ClassLoader.getSystemResource(fileName); if (url != null) { - m_properties.load(url.openStream()); + properties.load(url.openStream()); } else { throw new ParameterException("Could not find property file: " + fileName + " on the class path"); @@ -64,7 +64,7 @@ public class PropertyFileDefaultProvider implements IDefaultProvider { index++; } String key = optionName.substring(index); - return m_properties.getProperty(key); + return properties.getProperty(key); } } diff --git a/src/main/java/com/beust/jcommander/internal/DefaultConverterFactory.java b/src/main/java/com/beust/jcommander/internal/DefaultConverterFactory.java index 7eb5ae5..51e5d67 100644 --- a/src/main/java/com/beust/jcommander/internal/DefaultConverterFactory.java +++ b/src/main/java/com/beust/jcommander/internal/DefaultConverterFactory.java @@ -43,37 +43,38 @@ public class DefaultConverterFactory implements IStringConverterFactory { /** * A map of converters per class. */ - private static Map<Class, Class<? extends IStringConverter<?>>> m_classConverters; + private static Map<Class, Class<? extends IStringConverter<?>>> classConverters; static { - m_classConverters = Maps.newHashMap(); - m_classConverters.put(String.class, StringConverter.class); - m_classConverters.put(Integer.class, IntegerConverter.class); - m_classConverters.put(int.class, IntegerConverter.class); - m_classConverters.put(Long.class, LongConverter.class); - m_classConverters.put(long.class, LongConverter.class); - m_classConverters.put(Float.class, FloatConverter.class); - m_classConverters.put(float.class, FloatConverter.class); - m_classConverters.put(Double.class, DoubleConverter.class); - m_classConverters.put(double.class, DoubleConverter.class); - m_classConverters.put(Boolean.class, BooleanConverter.class); - m_classConverters.put(boolean.class, BooleanConverter.class); - m_classConverters.put(File.class, FileConverter.class); - m_classConverters.put(BigDecimal.class, BigDecimalConverter.class); - m_classConverters.put(Date.class, ISO8601DateConverter.class); + classConverters = Maps.newHashMap(); + classConverters.put(String.class, StringConverter.class); + classConverters.put(Integer.class, IntegerConverter.class); + classConverters.put(int.class, IntegerConverter.class); + classConverters.put(Long.class, LongConverter.class); + classConverters.put(long.class, LongConverter.class); + classConverters.put(Float.class, FloatConverter.class); + classConverters.put(float.class, FloatConverter.class); + classConverters.put(Double.class, DoubleConverter.class); + classConverters.put(double.class, DoubleConverter.class); + classConverters.put(Boolean.class, BooleanConverter.class); + classConverters.put(boolean.class, BooleanConverter.class); + classConverters.put(File.class, FileConverter.class); + classConverters.put(BigDecimal.class, BigDecimalConverter.class); + classConverters.put(Date.class, ISO8601DateConverter.class); + classConverters.put(URI.class, URIConverter.class); + classConverters.put(URL.class, URLConverter.class); + try { Class<?> pathClass = Class.forName("java.nio.file.Path"); Class<?> pathConverterClass = Class.forName("com.beust.jcommander.converters.PathConverter"); - m_classConverters.put(pathClass, (Class<? extends IStringConverter<?>>)pathConverterClass); - } catch (ClassNotFoundException e) { - // Do nothing: Android does not have java.nio.file.Path + classConverters.put(pathClass, (Class<? extends IStringConverter<?>>)pathConverterClass); + } catch (ClassNotFoundException ex) { + // skip if class is not present (e.g. on Android) } - m_classConverters.put(URI.class, URIConverter.class); - m_classConverters.put(URL.class, URLConverter.class); } public Class<? extends IStringConverter<?>> getConverter(Class forType) { - return m_classConverters.get(forType); + return classConverters.get(forType); } } diff --git a/src/main/java/com/beust/jcommander/internal/JDK6Console.java b/src/main/java/com/beust/jcommander/internal/JDK6Console.java index 70cb186..507a575 100644 --- a/src/main/java/com/beust/jcommander/internal/JDK6Console.java +++ b/src/main/java/com/beust/jcommander/internal/JDK6Console.java @@ -13,8 +13,8 @@ public class JDK6Console implements Console { public JDK6Console(Object console) throws Exception { this.console = console; - Method writerMethod = console.getClass().getDeclaredMethod("writer", new Class<?>[0]); - writer = (PrintWriter) writerMethod.invoke(console, new Object[0]); + Method writerMethod = console.getClass().getDeclaredMethod("writer"); + writer = (PrintWriter) writerMethod.invoke(console); } public void print(String msg) { @@ -30,11 +30,11 @@ public class JDK6Console implements Console { writer.flush(); Method method; if (echoInput) { - method = console.getClass().getDeclaredMethod("readLine", new Class<?>[0]); - return ((String) method.invoke(console, new Object[0])).toCharArray(); + method = console.getClass().getDeclaredMethod("readLine"); + return ((String) method.invoke(console)).toCharArray(); } else { - method = console.getClass().getDeclaredMethod("readPassword", new Class<?>[0]); - return (char[]) method.invoke(console, new Object[0]); + method = console.getClass().getDeclaredMethod("readPassword"); + return (char[]) method.invoke(console); } } catch (Exception e) { diff --git a/src/main/java/com/beust/jcommander/internal/Lists.java b/src/main/java/com/beust/jcommander/internal/Lists.java index fdbee55..f18e6cc 100644 --- a/src/main/java/com/beust/jcommander/internal/Lists.java +++ b/src/main/java/com/beust/jcommander/internal/Lists.java @@ -27,27 +27,27 @@ import java.util.List; public class Lists { public static <K> List<K> newArrayList() { - return new ArrayList<K>(); + return new ArrayList<>(); } public static <K> List<K> newArrayList(Collection<K> c) { - return new ArrayList<K>(c); + return new ArrayList<>(c); } public static <K> List<K> newArrayList(K... c) { - return new ArrayList<K>(Arrays.asList(c)); + return new ArrayList<>(Arrays.asList(c)); } public static <K> List<K> newArrayList(int size) { - return new ArrayList<K>(size); + return new ArrayList<>(size); } public static <K> LinkedList<K> newLinkedList() { - return new LinkedList<K>(); + return new LinkedList<>(); } public static <K> LinkedList<K> newLinkedList(Collection<K> c) { - return new LinkedList<K>(c); + return new LinkedList<>(c); } diff --git a/src/main/java/com/beust/jcommander/internal/Maps.java b/src/main/java/com/beust/jcommander/internal/Maps.java index e272122..2f45010 100644 --- a/src/main/java/com/beust/jcommander/internal/Maps.java +++ b/src/main/java/com/beust/jcommander/internal/Maps.java @@ -25,11 +25,11 @@ import java.util.Map; public class Maps { public static <K, V> Map<K,V> newHashMap() { - return new HashMap<K, V>(); + return new HashMap<>(); } public static <K, V> Map<K,V> newLinkedHashMap() { - return new LinkedHashMap<K, V>(); + return new LinkedHashMap<>(); } public static <T> Map<T, T> newHashMap(T... parameters) { diff --git a/src/main/java/com/beust/jcommander/internal/Sets.java b/src/main/java/com/beust/jcommander/internal/Sets.java index 77949c3..168c55f 100644 --- a/src/main/java/com/beust/jcommander/internal/Sets.java +++ b/src/main/java/com/beust/jcommander/internal/Sets.java @@ -25,11 +25,11 @@ import java.util.Set; public class Sets { public static <K> Set<K> newHashSet() { - return new HashSet<K>(); + return new HashSet<>(); } public static <K> Set<K> newLinkedHashSet() { - return new LinkedHashSet<K>(); + return new LinkedHashSet<>(); } } diff --git a/src/test/java/com/beust/jcommander/ArgMultiNameValidator.java b/src/test/java/com/beust/jcommander/ArgMultiNameValidator.java new file mode 100644 index 0000000..92a79bc --- /dev/null +++ b/src/test/java/com/beust/jcommander/ArgMultiNameValidator.java @@ -0,0 +1,19 @@ +package com.beust.jcommander; + +/** + * Created by jeremysolarz on 12/15/16. + */ +public class ArgMultiNameValidator { + + public static class MultiNameValidator implements IValueValidator<String> { + + public static String parsedName; + + public void validate(String name, String value) throws ParameterException { + parsedName = name; + } + } + + @Parameter(names = { "-name1", "-name2" }, description = "Names of parameter", validateValueWith = MultiNameValidator.class, required = true) + private String parameter; +} diff --git a/src/test/java/com/beust/jcommander/CmdTest.java b/src/test/java/com/beust/jcommander/CmdTest.java index 6601193..229517d 100644 --- a/src/test/java/com/beust/jcommander/CmdTest.java +++ b/src/test/java/com/beust/jcommander/CmdTest.java @@ -17,7 +17,7 @@ public class CmdTest { @Parameters(commandNames = "--cmd-two") class CmdTwo { @Parameter - List<String> params = new java.util.LinkedList<String>(); + List<String> params = new java.util.LinkedList<>(); } public String parseArgs(boolean withDefault, String[] args) { @@ -35,7 +35,7 @@ public class CmdTest { // is named "WithoutValidation". jc.parseWithoutValidation(args); if (jc.getParsedCommand() == null) { - LinkedList<String> newArgs = new LinkedList<String>(); + LinkedList<String> newArgs = new LinkedList<>(); newArgs.add("--cmd-two"); newArgs.addAll(Arrays.asList(args)); jc.parse(newArgs.toArray(new String[0])); @@ -70,7 +70,12 @@ public class CmdTest { public void testArgsWithoutDefaultCmdFail(String expected, boolean requireDefault, String[] args) { if (requireDefault) { - parseArgs(false, args); + try { + parseArgs(false, args); + } catch (MissingCommandException e) { + Assert.assertEquals(e.getUnknownCommand(), args[0]); + throw e; + } } else { throw new MissingCommandException("irrelevant test case"); } @@ -83,4 +88,20 @@ public class CmdTest { Assert.assertEquals(parseArgs(true, args), expected); } -}
\ No newline at end of file + @Test + public void testIssue244() throws Exception { + class P1 {} + class P2 { + @Parameter(names = "--hello") + private int test; + } + P1 p1 = new P1(); + P2 p2 = new P2(); + JCommander j = new JCommander(p1); + j.addCommand("wonderful", p2); + j.setAllowAbbreviatedOptions(true); + j.parse("wond", "--he", "47"); + Assert.assertEquals("wonderful", j.getParsedCommand()); + Assert.assertEquals(47, p2.test); + } +} diff --git a/src/test/java/com/beust/jcommander/ConverterFactoryTest.java b/src/test/java/com/beust/jcommander/ConverterFactoryTest.java index e02166e..4b8b923 100644 --- a/src/test/java/com/beust/jcommander/ConverterFactoryTest.java +++ b/src/test/java/com/beust/jcommander/ConverterFactoryTest.java @@ -62,9 +62,10 @@ public class ConverterFactoryTest { * Test that main parameters can be used with string converters, * either with a factory or from the annotation. */ - private void mainWithHostPortParameters(IStringConverterFactory f, IHostPorts a) { + private void mainWithHostPortParameters(IStringConverterFactory f, IStringConverterInstanceFactory f2, IHostPorts a) { JCommander jc = new JCommander(a); if (f != null) jc.addConverterFactory(f); + if (f2 != null) jc.addConverterInstanceFactory(f2); jc.parse("a.com:10", "b.com:20"); Assert.assertEquals(a.getHostPorts().get(0).host, "a.com"); Assert.assertEquals(a.getHostPorts().get(0).port.intValue(), 10); @@ -74,12 +75,27 @@ public class ConverterFactoryTest { @Test public void mainWithoutFactory() { - mainWithHostPortParameters(null, new ArgsMainParameter1()); + mainWithHostPortParameters(null, null, new ArgsMainParameter2()); + } + + @Test(expectedExceptions = RuntimeException.class) + public void mainWithoutConverterWithoutFactory() { + mainWithHostPortParameters(null, null, new ArgsMainParameter1()); } @Test public void mainWithFactory() { - mainWithHostPortParameters(CONVERTER_FACTORY, new ArgsMainParameter2()); + mainWithHostPortParameters(CONVERTER_FACTORY, null, new ArgsMainParameter1()); + } + + @Test + public void mainWithInstanceFactory() { + mainWithHostPortParameters(null, new IStringConverterInstanceFactory() { + @Override + public IStringConverter<?> getConverterInstance(Parameter parameter, Class<?> forType, String optionName) { + return HostPort.class.equals(forType) ? new HostPortConverter() : null; + } + }, new ArgsMainParameter1()); } } diff --git a/src/test/java/com/beust/jcommander/DefaultProviderTest.java b/src/test/java/com/beust/jcommander/DefaultProviderTest.java index 45ab6b6..45fad38 100644 --- a/src/test/java/com/beust/jcommander/DefaultProviderTest.java +++ b/src/test/java/com/beust/jcommander/DefaultProviderTest.java @@ -117,4 +117,24 @@ public class DefaultProviderTest { Assert.assertEquals(a.log.intValue(), 19); } + @Test + public void missingRequiredParameterWithDefaultValueProviderShouldNotRaiseParameterException() { + class ArgsRequired { + @Parameter(names = "-log", description = "Level of verbosity", required = true) + public Integer log; + } + + IDefaultProvider defaultProvider = new IDefaultProvider() { + public String getDefaultValueFor(String optionName) { + return "-log".equals(optionName) ? "1" : ""; + } + }; + + ArgsRequired a = new ArgsRequired(); + JCommander jc = new JCommander(a); + jc.setDefaultProvider(defaultProvider); + jc.parse(); + + Assert.assertEquals(a.log.intValue(), 1); + } } diff --git a/src/test/java/com/beust/jcommander/DefaultValueTest.java b/src/test/java/com/beust/jcommander/DefaultValueTest.java index 3b1f29c..403ecfd 100644 --- a/src/test/java/com/beust/jcommander/DefaultValueTest.java +++ b/src/test/java/com/beust/jcommander/DefaultValueTest.java @@ -36,7 +36,7 @@ public class DefaultValueTest { public void emptyDefaultValueForListParameterStaysEmptyIfNotAssignedOrIsSetOtherwise() { MyOptsWithEmptyDefaults opts = new MyOptsWithEmptyDefaults(); JCommander cmd = new JCommander(opts); - cmd.parse(new String[]{"-a", "anotherValue"}); + cmd.parse("-a", "anotherValue"); Assert.assertEquals(opts.list.size(), 1); Assert.assertEquals(opts.list.get(0), "anotherValue"); Assert.assertEquals(opts.set.size(), 0); @@ -46,7 +46,7 @@ public class DefaultValueTest { public void defaultValueForListParametersGetsOverwrittenWithSpecifiedValueOrStaysAsDefaultOtherwise() { MyOptsWithDefaultValues opts = new MyOptsWithDefaultValues(); JCommander cmd = new JCommander(opts); - cmd.parse(new String[]{"-a", "anotherValue"}); + cmd.parse("-a", "anotherValue"); Assert.assertEquals(opts.list.size(), 1); Assert.assertEquals(opts.list.get(0), "anotherValue"); Assert.assertEquals(opts.set.size(), 1); @@ -67,8 +67,8 @@ public class DefaultValueTest { private void testSettingMultipleValuesToListTypeParameters(MyOpts opts) { JCommander cmd = new JCommander(opts); - cmd.parse(new String[]{"-a", "anotherValue", "-a", "anotherValue2", - "-b", "anotherValue3", "-b", "anotherValue4"}); + cmd.parse("-a", "anotherValue", "-a", "anotherValue2", + "-b", "anotherValue3", "-b", "anotherValue4"); Assert.assertEquals(opts.list.size(), 2); Assert.assertEquals(opts.list.get(0), "anotherValue"); Assert.assertEquals(opts.list.get(1), "anotherValue2"); diff --git a/src/test/java/com/beust/jcommander/FinderTest.java b/src/test/java/com/beust/jcommander/FinderTest.java index 94bf812..d4adda9 100644 --- a/src/test/java/com/beust/jcommander/FinderTest.java +++ b/src/test/java/com/beust/jcommander/FinderTest.java @@ -17,7 +17,7 @@ public class FinderTest { Arg a = new Arg(); JCommander jc = new JCommander(a); jc.setCaseSensitiveOptions(false); - jc.parse(new String[] { "--PARAM", "foo" }); + jc.parse("--PARAM", "foo"); Assert.assertEquals(a.param, "foo"); } @@ -27,7 +27,6 @@ public class FinderTest { JCommander jc = new JCommander(a); jc.addCommand(conf); jc.setCaseSensitiveOptions(false); -// jc.setCaseSensitiveCommands(false); jc.parse("--CONFIGURE"); String command = jc.getParsedCommand(); Assert.assertEquals(command, "--configure"); @@ -41,7 +40,7 @@ public class FinderTest { Arg a = new Arg(); JCommander jc = new JCommander(a); jc.setAllowAbbreviatedOptions(true); - jc.parse(new String[] { "--par", "foo" }); + jc.parse("--par", "foo"); Assert.assertEquals(a.param, "foo"); } @@ -54,7 +53,7 @@ public class FinderTest { JCommander jc = new JCommander(a); jc.setCaseSensitiveOptions(false); jc.setAllowAbbreviatedOptions(true); - jc.parse(new String[] { "--PAR", "foo" }); + jc.parse("--PAR", "foo"); Assert.assertEquals(a.param, "foo"); } @@ -69,7 +68,7 @@ public class FinderTest { Arg a = new Arg(); JCommander jc = new JCommander(a); jc.setAllowAbbreviatedOptions(true); - jc.parse(new String[] { "--par", "foo" }); + jc.parse("--par", "foo"); Assert.assertEquals(a.param, "foo"); } @@ -85,7 +84,7 @@ public class FinderTest { JCommander jc = new JCommander(a); jc.setCaseSensitiveOptions(false); jc.setAllowAbbreviatedOptions(true); - jc.parse(new String[] { "--PAR", "foo" }); + jc.parse("--PAR", "foo"); Assert.assertEquals(a.param, "foo"); } diff --git a/src/test/java/com/beust/jcommander/HiddenConverter.java b/src/test/java/com/beust/jcommander/HiddenConverter.java new file mode 100644 index 0000000..cd36b85 --- /dev/null +++ b/src/test/java/com/beust/jcommander/HiddenConverter.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander; + +public class HiddenConverter implements IStringConverter<String> { + private HiddenConverter() { + } + + @Override + public String convert(String value) { + return value; + } +} diff --git a/src/test/java/com/beust/jcommander/HiddenParameterSplitter.java b/src/test/java/com/beust/jcommander/HiddenParameterSplitter.java new file mode 100644 index 0000000..a40f36f --- /dev/null +++ b/src/test/java/com/beust/jcommander/HiddenParameterSplitter.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander; + +import java.util.Arrays; +import java.util.List; + +import com.beust.jcommander.converters.IParameterSplitter; + +public class HiddenParameterSplitter implements IParameterSplitter { + @Override + public List<String> split(String value) { + return Arrays.asList(value.split(";")); + } +} diff --git a/src/test/java/com/beust/jcommander/JCommanderTest.java b/src/test/java/com/beust/jcommander/JCommanderTest.java index ad2c5e8..e967ef7 100644 --- a/src/test/java/com/beust/jcommander/JCommanderTest.java +++ b/src/test/java/com/beust/jcommander/JCommanderTest.java @@ -2,13 +2,13 @@ * Copyright (C) 2010 the original author or authors. * See the notice.md file distributed with this work for additional * information regarding copyright ownership. - * + * <p> * 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 - * + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> * 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. @@ -18,1064 +18,1516 @@ package com.beust.jcommander; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.TreeSet; - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import com.beust.jcommander.args.AlternateNamesForListArgs; -import com.beust.jcommander.args.Args1; -import com.beust.jcommander.args.Args1Setter; -import com.beust.jcommander.args.Args2; -import com.beust.jcommander.args.ArgsArityString; -import com.beust.jcommander.args.ArgsBooleanArity; -import com.beust.jcommander.args.ArgsBooleanArity0; -import com.beust.jcommander.args.ArgsConverter; -import com.beust.jcommander.args.ArgsEnum; +import com.beust.jcommander.args.*; import com.beust.jcommander.args.ArgsEnum.ChoiceType; -import com.beust.jcommander.args.ArgsEquals; -import com.beust.jcommander.args.ArgsHelp; -import com.beust.jcommander.args.ArgsI18N1; -import com.beust.jcommander.args.ArgsI18N2; -import com.beust.jcommander.args.ArgsI18N2New; -import com.beust.jcommander.args.ArgsInherited; -import com.beust.jcommander.args.ArgsList; -import com.beust.jcommander.args.ArgsMainParameter1; -import com.beust.jcommander.args.ArgsMaster; -import com.beust.jcommander.args.ArgsMultipleUnparsed; -import com.beust.jcommander.args.ArgsOutOfMemory; -import com.beust.jcommander.args.ArgsPrivate; -import com.beust.jcommander.args.ArgsRequired; -import com.beust.jcommander.args.ArgsSlave; -import com.beust.jcommander.args.ArgsSlaveBogus; -import com.beust.jcommander.args.ArgsValidate1; -import com.beust.jcommander.args.ArgsWithSet; -import com.beust.jcommander.args.Arity1; -import com.beust.jcommander.args.SeparatorColon; -import com.beust.jcommander.args.SeparatorEqual; -import com.beust.jcommander.args.SeparatorMixed; -import com.beust.jcommander.args.SlashSeparator; -import com.beust.jcommander.args.VariableArity; import com.beust.jcommander.command.CommandAdd; import com.beust.jcommander.command.CommandCommit; import com.beust.jcommander.command.CommandMain; +import com.beust.jcommander.converters.FileConverter; import com.beust.jcommander.internal.Lists; import com.beust.jcommander.internal.Maps; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.*; +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.ResourceBundle; @Test public class JCommanderTest { - public void simpleArgs() throws ParseException { - Args1 args = new Args1(); - String[] argv = { "-debug", "-log", "2", "-float", "1.2", "-double", "1.3", "-bigdecimal", "1.4", - "-date", "2011-10-26", "-groups", "unit", "a", "b", "c" }; - new JCommander(args, argv); - - Assert.assertTrue(args.debug); - Assert.assertEquals(args.verbose.intValue(), 2); - Assert.assertEquals(args.groups, "unit"); - Assert.assertEquals(args.parameters, Arrays.asList("a", "b", "c")); - Assert.assertEquals(args.floa, 1.2f, 0.1f); - Assert.assertEquals(args.doub, 1.3f, 0.1f); - Assert.assertEquals(args.bigd, new BigDecimal("1.4")); - Assert.assertEquals(args.date, new SimpleDateFormat("yyyy-MM-dd").parse("2011-10-26")); - } - - @DataProvider - public Object[][] alternateNamesListArgs() { - return new Object[][] { - new String[][] {new String[] {"--servers", "1", "-s", "2", "--servers", "3"}}, - new String[][] {new String[] {"-s", "1", "-s", "2", "--servers", "3"}}, - new String[][] {new String[] {"--servers", "1", "--servers", "2", "-s", "3"}}, - new String[][] {new String[] {"-s", "1", "--servers", "2", "-s", "3"}}, - new String[][] {new String[] {"-s", "1", "-s", "2", "--servers", "3"}}, - }; - } - - /** - * Confirm that List<?> parameters with alternate names return the correct - * List regardless of how the arguments are specified - */ - - @Test(dataProvider = "alternateNamesListArgs") - public void testAlternateNamesForListArguments(String[] argv) { - AlternateNamesForListArgs args = new AlternateNamesForListArgs(); - - new JCommander(args, argv); - - Assert.assertEquals(args.serverNames.size(), 3); - Assert.assertEquals(args.serverNames.get(0), argv[1]); - Assert.assertEquals(args.serverNames.get(1), argv[3]); - Assert.assertEquals(args.serverNames.get(2), argv[5]); - } - - - /** - * Make sure that if there are args with multiple names (e.g. "-log" and "-verbose"), - * the usage will only display it once. - */ - public void repeatedArgs() { - Args1 args = new Args1(); - String[] argv = { "-log", "2" }; - JCommander jc = new JCommander(args, argv); - Assert.assertEquals(jc.getParameters().size(), 8); - } - - /** - * Not specifying a required option should throw an exception. - */ - @Test(expectedExceptions = ParameterException.class) - public void requiredFields1Fail() { - Args1 args = new Args1(); - String[] argv = { "-debug" }; - new JCommander(args, argv); - } - - /** - * Getting the description of a nonexistent command should throw an exception. - */ - @Test(expectedExceptions = ParameterException.class) - public void nonexistentCommandShouldThrow() { - String[] argv = { }; - JCommander jc = new JCommander(new Object(), argv); - jc.getCommandDescription("foo"); - } - - /** - * Required options with multiple names should work with all names. - */ - private void multipleNames(String option) { - Args1 args = new Args1(); - String[] argv = { option, "2" }; - new JCommander(args, argv); - Assert.assertEquals(args.verbose.intValue(), 2); - } - - public void multipleNames1() { - multipleNames("-log"); - } - - public void multipleNames2() { - multipleNames("-verbose"); - } - - private void i18n1(String bundleName, Locale locale, String expectedString) { - ResourceBundle bundle = locale != null ? ResourceBundle.getBundle(bundleName, locale) - : null; - - ArgsI18N1 i18n = new ArgsI18N1(); - String[] argv = { "-host", "localhost" }; - JCommander jc = new JCommander(i18n, bundle, argv); -// jc.usage(); - - ParameterDescription pd = jc.getParameters().get(0); - Assert.assertEquals(pd.getDescription(), expectedString); - } - - public void i18nNoLocale() { - i18n1("MessageBundle", null, "Host"); - } - - public void i18nUsLocale() { - i18n1("MessageBundle", new Locale("en", "US"), "Host"); - } - - public void i18nFrLocale() { - i18n1("MessageBundle", new Locale("fr", "FR"), "Hôte"); - } - - private void i18n2(Object i18n) { - String[] argv = { "-host", "localhost" }; - Locale.setDefault(new Locale("fr", "FR")); - JCommander jc = new JCommander(i18n, argv); - ParameterDescription pd = jc.getParameters().get(0); - Assert.assertEquals(pd.getDescription(), "Hôte"); - } - - public void i18nWithResourceAnnotation() { - i18n2(new ArgsI18N2()); - } - - public void i18nWithResourceAnnotationNew() { - i18n2(new ArgsI18N2New()); - } - - public void noParseConstructor() { - JCommander jCommander = new JCommander(new ArgsMainParameter1()); - jCommander.usage(new StringBuilder()); - // Before fix, this parse would throw an exception, because it calls createDescription, which - // was already called by usage(), and can only be called once. - jCommander.parse(); - } - - /** - * Test a use case where there are required parameters, but you still want - * to interrogate the options which are specified. - */ - public void usageWithRequiredArgsAndResourceBundle() { - ArgsHelp argsHelp = new ArgsHelp(); - JCommander jc = new JCommander(new Object[]{argsHelp, new ArgsRequired()}, - java.util.ResourceBundle.getBundle("MessageBundle")); - // Should be able to display usage without triggering validation - jc.usage(new StringBuilder()); - try { - jc.parse("-h"); - Assert.fail("Should have thrown a required parameter exception"); - } catch (ParameterException e) { - Assert.assertTrue(e.getMessage().contains("are required")); - } - Assert.assertTrue(argsHelp.help); - } - - public void multiObjects() { - ArgsMaster m = new ArgsMaster(); - ArgsSlave s = new ArgsSlave(); - String[] argv = { "-master", "master", "-slave", "slave" }; - new JCommander(new Object[] { m , s }, argv); - - Assert.assertEquals(m.master, "master"); - Assert.assertEquals(s.slave, "slave"); - } - - @Test(expectedExceptions = ParameterException.class) - public void multiObjectsWithDuplicatesFail() { - ArgsMaster m = new ArgsMaster(); - ArgsSlave s = new ArgsSlaveBogus(); - String[] argv = { "-master", "master", "-slave", "slave" }; - new JCommander(new Object[] { m , s }, argv); - } - - public void arityString() { - ArgsArityString args = new ArgsArityString(); - String[] argv = { "-pairs", "pair0", "pair1", "rest" }; - new JCommander(args, argv); - - Assert.assertEquals(args.pairs.size(), 2); - Assert.assertEquals(args.pairs.get(0), "pair0"); - Assert.assertEquals(args.pairs.get(1), "pair1"); - Assert.assertEquals(args.rest.size(), 1); - Assert.assertEquals(args.rest.get(0), "rest"); - } - - @Test(expectedExceptions = ParameterException.class) - public void arity2Fail() { - ArgsArityString args = new ArgsArityString(); - String[] argv = { "-pairs", "pair0" }; - new JCommander(args, argv); - } - - @Test(expectedExceptions = ParameterException.class) - public void multipleUnparsedFail() { - ArgsMultipleUnparsed args = new ArgsMultipleUnparsed(); - String[] argv = { }; - new JCommander(args, argv); - } - - public void privateArgs() { - ArgsPrivate args = new ArgsPrivate(); - new JCommander(args, "-verbose", "3"); - Assert.assertEquals(args.getVerbose().intValue(), 3); - } - - public void converterArgs() { - ArgsConverter args = new ArgsConverter(); - String fileName = "a"; - new JCommander(args, "-file", "/tmp/" + fileName, - "-listStrings", "Tuesday,Thursday", - "-listInts", "-1,8", - "-listBigDecimals", "-11.52,100.12"); - Assert.assertEquals(args.file.getName(), fileName); - Assert.assertEquals(args.listStrings.size(), 2); - Assert.assertEquals(args.listStrings.get(0), "Tuesday"); - Assert.assertEquals(args.listStrings.get(1), "Thursday"); - Assert.assertEquals(args.listInts.size(), 2); - Assert.assertEquals(args.listInts.get(0).intValue(), -1); - Assert.assertEquals(args.listInts.get(1).intValue(), 8); - Assert.assertEquals(args.listBigDecimals.size(), 2); - Assert.assertEquals(args.listBigDecimals.get(0), new BigDecimal("-11.52")); - Assert.assertEquals(args.listBigDecimals.get(1), new BigDecimal("100.12")); - } - - private void argsBoolean1(String[] params, Boolean expected) { - ArgsBooleanArity args = new ArgsBooleanArity(); - new JCommander(args, params); - Assert.assertEquals(args.debug, expected); - } - - private void argsBoolean0(String[] params, Boolean expected) { - ArgsBooleanArity0 args = new ArgsBooleanArity0(); - new JCommander(args, params); - Assert.assertEquals(args.debug, expected); - } - - public void booleanArity1() { - argsBoolean1(new String[] {}, Boolean.FALSE); - argsBoolean1(new String[] { "-debug", "true" }, Boolean.TRUE); - } - - public void booleanArity0() { - argsBoolean0(new String[] {}, Boolean.FALSE); - argsBoolean0(new String[] { "-debug"}, Boolean.TRUE); - } - - @Test(expectedExceptions = ParameterException.class) - public void badParameterShouldThrowParameter1Exception() { - Args1 args = new Args1(); - String[] argv = { "-log", "foo" }; - new JCommander(args, argv); - } - - @Test(expectedExceptions = ParameterException.class) - public void badParameterShouldThrowParameter2Exception() { - Args1 args = new Args1(); - String[] argv = { "-long", "foo" }; - new JCommander(args, argv); - } - - public void listParameters() { - Args2 a = new Args2(); - String[] argv = {"-log", "2", "-groups", "unit", "a", "b", "c", "-host", "host2"}; - new JCommander(a, argv); - Assert.assertEquals(a.verbose.intValue(), 2); - Assert.assertEquals(a.groups, "unit"); - Assert.assertEquals(a.hosts, Arrays.asList("host2")); - Assert.assertEquals(a.parameters, Arrays.asList("a", "b", "c")); - } - - public void separatorEqual() { - SeparatorEqual s = new SeparatorEqual(); - String[] argv = { "-log=3", "--longoption=10" }; - new JCommander(s, argv); - Assert.assertEquals(s.log.intValue(), 3); - Assert.assertEquals(s.longOption.intValue(), 10); - } - - public void separatorColon() { - SeparatorColon s = new SeparatorColon(); - String[] argv = { "-verbose:true" }; - new JCommander(s, argv); - Assert.assertTrue(s.verbose); - } - - public void separatorBoth() { - SeparatorColon s = new SeparatorColon(); - SeparatorEqual s2 = new SeparatorEqual(); - String[] argv = { "-verbose:true", "-log=3" }; - new JCommander(new Object[] { s, s2 }, argv); - Assert.assertTrue(s.verbose); - Assert.assertEquals(s2.log.intValue(), 3); - } - - public void separatorMixed1() { - SeparatorMixed s = new SeparatorMixed(); - String[] argv = { "-long:1", "-level=42" }; - new JCommander(s, argv); - Assert.assertEquals(s.l.longValue(), 1l); - Assert.assertEquals(s.level.intValue(), 42); - } - - public void slashParameters() { - SlashSeparator a = new SlashSeparator(); - String[] argv = { "/verbose", "/file", "/tmp/a" }; - new JCommander(a, argv); - Assert.assertTrue(a.verbose); - Assert.assertEquals(a.file, "/tmp/a"); - } - - public void inheritance() { - ArgsInherited args = new ArgsInherited(); - String[] argv = { "-log", "3", "-child", "2" }; - new JCommander(args, argv); - Assert.assertEquals(args.child.intValue(), 2); - Assert.assertEquals(args.log.intValue(), 3); - } - - public void negativeNumber() { - Args1 a = new Args1(); - String[] argv = { "-verbose", "-3" }; - new JCommander(a, argv); - Assert.assertEquals(a.verbose.intValue(), -3); - } - - @Test(expectedExceptions = ParameterException.class) - public void requiredMainParameters() { - ArgsRequired a = new ArgsRequired(); - String[] argv = {}; - new JCommander(a, argv); - } - - public void usageShouldNotChange() { - JCommander jc = new JCommander(new Args1(), new String[]{"-log", "1"}); - StringBuilder sb = new StringBuilder(); - jc.usage(sb); - String expected = sb.toString(); - jc = new JCommander(new Args1(), new String[]{"-debug", "-log", "2", "-long", "5"}); - sb = new StringBuilder(); - jc.usage(sb); - String actual = sb.toString(); - Assert.assertEquals(actual, expected); - } - - private void verifyCommandOrdering(String[] commandNames, Object[] commands) { - CommandMain cm = new CommandMain(); - JCommander jc = new JCommander(cm); - - for (int i = 0; i < commands.length; i++) { - jc.addCommand(commandNames[i], commands[i]); - } - - Map<String, JCommander> c = jc.getCommands(); - Assert.assertEquals(c.size(), commands.length); - - Iterator<String> it = c.keySet().iterator(); - for (int i = 0; i < commands.length; i++) { - Assert.assertEquals(it.next(), commandNames[i]); - } - } - - public void commandsShouldBeShownInOrderOfInsertion() { - verifyCommandOrdering(new String[] { "add", "commit" }, - new Object[] { new CommandAdd(), new CommandCommit() }); - verifyCommandOrdering(new String[] { "commit", "add" }, - new Object[] { new CommandCommit(), new CommandAdd() }); - } - - @DataProvider - public static Object[][] f() { - return new Integer[][] { - new Integer[] { 3, 5, 1 }, - new Integer[] { 3, 8, 1 }, - new Integer[] { 3, 12, 2 }, - new Integer[] { 8, 12, 2 }, - new Integer[] { 9, 10, 1 }, - }; - } - - @Test(expectedExceptions = ParameterException.class) - public void arity1Fail() { - final Arity1 arguments = new Arity1(); - final JCommander jCommander = new JCommander(arguments); - final String[] commands = { - "-inspect" - }; - jCommander.parse(commands); - } - - public void arity1Success1() { - final Arity1 arguments = new Arity1(); - final JCommander jCommander = new JCommander(arguments); - final String[] commands = { - "-inspect", "true" - }; - jCommander.parse(commands); - Assert.assertTrue(arguments.inspect); - } - - public void arity1Success2() { - final Arity1 arguments = new Arity1(); - final JCommander jCommander = new JCommander(arguments); - final String[] commands = { - "-inspect", "false" - }; - jCommander.parse(commands); - Assert.assertFalse(arguments.inspect); - } - - @Parameters(commandDescription = "Help for the given commands.") - public static class Help { - public static final String NAME = "help"; - - @Parameter(description = "List of commands.") - public List<String> commands=new ArrayList<String>(); - } - - @Test(expectedExceptions = ParameterException.class, - description = "Verify that the main parameter's type is checked to be a List") - public void wrongMainTypeShouldThrow() { - JCommander jc = new JCommander(new ArgsRequiredWrongMain()); - jc.parse(new String[] { "f1", "f2" }); - } - - @Test(description = "This used to run out of memory") - public void oom() { - JCommander jc = new JCommander(new ArgsOutOfMemory()); - jc.usage(new StringBuilder()); - } - - @Test - public void getParametersShouldNotNpe() { - JCommander jc = new JCommander(new Args1()); - List<ParameterDescription> parameters = jc.getParameters(); - } - - public void validationShouldWork1() { - ArgsValidate1 a = new ArgsValidate1(); - JCommander jc = new JCommander(a); - jc.parse(new String[] { "-age", "2 "}); - Assert.assertEquals(a.age, new Integer(2)); - } - - @Test(expectedExceptions = ParameterException.class) - public void validationShouldWorkWithDefaultValues() { - ArgsValidate2 a = new ArgsValidate2(); - new JCommander(a); - } - - @Test(expectedExceptions = ParameterException.class) - public void validationShouldWork2() { - ArgsValidate1 a = new ArgsValidate1(); - JCommander jc = new JCommander(a); - jc.parse(new String[] { "-age", "-2 "}); - } - - public void atFileCanContainEmptyLines() throws IOException { - File f = File.createTempFile("JCommander", null); - f.deleteOnExit(); - FileWriter fw = new FileWriter(f); - fw.write("-log\n"); - fw.write("\n"); - fw.write("2\n"); - fw.close(); - new JCommander(new Args1(), "@" + f.getAbsolutePath()); - } - - public void handleEqualSigns() { - ArgsEquals a = new ArgsEquals(); - JCommander jc = new JCommander(a); - jc.parse(new String[] { "-args=a=b,b=c" }); - Assert.assertEquals(a.args, "a=b,b=c"); - } - - @SuppressWarnings("serial") - public void handleSets() { - ArgsWithSet a = new ArgsWithSet(); - new JCommander(a, new String[] { "-s", "3,1,2" }); - Assert.assertEquals(a.set, new TreeSet<Integer>() {{ add(1); add(2); add(3); }}); - } - - private static final List<String> V = Arrays.asList("a", "b", "c", "d"); - - @DataProvider - public Object[][] variable() { - return new Object[][] { - new Object[] { 0, V.subList(0, 0), V }, - new Object[] { 1, V.subList(0, 1), V.subList(1, 4) }, - new Object[] { 2, V.subList(0, 2), V.subList(2, 4) }, - new Object[] { 3, V.subList(0, 3), V.subList(3, 4) }, - new Object[] { 4, V.subList(0, 4), V.subList(4, 4) }, - }; - } - - @Test(dataProvider = "variable") - public void variableArity(int count, List<String> var, List<String> main) { - VariableArity va = new VariableArity(count); - new JCommander(va).parse("-variable", "a", "b", "c", "d"); - Assert.assertEquals(var, va.var); - Assert.assertEquals(main, va.main); - } - - public void enumArgs() { - ArgsEnum args = new ArgsEnum(); - String[] argv = { "-choice", "ONE", "-choices", "ONE", "Two" }; - JCommander jc = new JCommander(args, argv); - - Assert.assertEquals(args.choice, ArgsEnum.ChoiceType.ONE); - - List<ChoiceType> expected = Arrays.asList(ChoiceType.ONE, ChoiceType.Two); - Assert.assertEquals(expected, args.choices); - Assert.assertEquals(jc.getParameters().get(0).getDescription(), - "Options: " + EnumSet.allOf((Class<? extends Enum>) ArgsEnum.ChoiceType.class)); - - } - - public void enumArgsCaseInsensitive() { - ArgsEnum args = new ArgsEnum(); - String[] argv = { "-choice", "one"}; - JCommander jc = new JCommander(args, argv); - - Assert.assertEquals(args.choice, ArgsEnum.ChoiceType.ONE); - } - - @Test(expectedExceptions = ParameterException.class) - public void enumArgsFail() { - ArgsEnum args = new ArgsEnum(); - String[] argv = { "-choice", "A" }; - new JCommander(args, argv); - } - - public void testListAndSplitters() { - ArgsList al = new ArgsList(); - JCommander j = new JCommander(al); - j.parse("-groups", "a,b", "-ints", "41,42", "-hp", "localhost:1000;example.com:1001", - "-hp2", "localhost:1000,example.com:1001", "-uppercase", "ab,cd"); - Assert.assertEquals(al.groups.get(0), "a"); - Assert.assertEquals(al.groups.get(1), "b"); - Assert.assertEquals(al.ints.get(0).intValue(), 41); - Assert.assertEquals(al.ints.get(1).intValue(), 42); - Assert.assertEquals(al.hostPorts.get(0).host, "localhost"); - Assert.assertEquals(al.hostPorts.get(0).port.intValue(), 1000); - Assert.assertEquals(al.hostPorts.get(1).host, "example.com"); - Assert.assertEquals(al.hostPorts.get(1).port.intValue(), 1001); - Assert.assertEquals(al.hp2.get(1).host, "example.com"); - Assert.assertEquals(al.hp2.get(1).port.intValue(), 1001); - Assert.assertEquals(al.uppercase.get(0), "AB"); - Assert.assertEquals(al.uppercase.get(1), "CD"); - } - - @Test(expectedExceptions = ParameterException.class) - public void shouldThrowIfUnknownOption() { - class A { - @Parameter(names = "-long") - public long l; - } - A a = new A(); - new JCommander(a).parse("-lon", "32"); - } - - @Test(expectedExceptions = ParameterException.class) - public void mainParameterShouldBeValidate() { - class V implements IParameterValidator { - - @Override - public void validate(String name, String value) throws ParameterException { - Assert.assertEquals("a", value); - } - } - - class A { - @Parameter(validateWith = V.class) - public List<String> m; - } - - A a = new A(); - new JCommander(a).parse("b"); - } - - @Parameters(commandNames = { "--configure" }) - public static class ConfigureArgs { - } - - public static class BaseArgs { - @Parameter(names = { "-h", "--help" }, description = "Show this help screen") - private boolean help = false; - - @Parameter(names = { "--version", "-version" }, description = "Show the program version") - private boolean version; - } - - public void commandsWithSamePrefixAsOptionsShouldWork() { - BaseArgs a = new BaseArgs(); - ConfigureArgs conf = new ConfigureArgs(); - JCommander jc = new JCommander(a); - jc.addCommand(conf); - jc.parse("--configure"); - } - - // Tests: - // required unparsed parameter - @Test(enabled = false, - description = "For some reason, this test still asks the password on stdin") - public void askedRequiredPassword() { - class A { - @Parameter(names = { "--password", "-p" }, description = "Private key password", - password = true, required = true) - public String password; - - @Parameter(names = { "--port", "-o" }, description = "Port to bind server to", - required = true) - public int port; - } - A a = new A(); - InputStream stdin = System.in; - try { - System.setIn(new ByteArrayInputStream("password".getBytes())); - new JCommander(a,new String[]{"--port", "7","--password"}); - Assert.assertEquals(a.port, 7); - Assert.assertEquals(a.password, "password"); - } finally { - System.setIn(stdin); - } - } - - public void dynamicParameters() { - class Command { - @DynamicParameter(names = {"-P"}, description = "Additional command parameters") - private Map<String, String> params = Maps.newHashMap(); - } - JCommander commander = new JCommander(); - Command c = new Command(); - commander.addCommand("command", c); - commander.parse(new String[] { "command", "-Pparam='name=value'" }); - Assert.assertEquals(c.params.get("param"), "'name=value'"); - } - - public void exeParser() { - class Params { - @Parameter( names= "-i") - private String inputFile; - } - - String args[] = { "-i", "" }; - Params p = new Params(); - new JCommander(p, args); - } - - public void multiVariableArityList() { - class Params { - @Parameter(names = "-paramA", description = "ParamA", variableArity = true) - private List<String> paramA = Lists.newArrayList(); - - @Parameter(names = "-paramB", description = "ParamB", variableArity = true) - private List<String> paramB = Lists.newArrayList(); - } - - { - String args[] = { "-paramA", "a1", "a2", "-paramB", "b1", "b2", "b3" }; - Params p = new Params(); - new JCommander(p, args).parse(); - Assert.assertEquals(p.paramA, Arrays.asList(new String[] { "a1", "a2" })); - Assert.assertEquals(p.paramB, Arrays.asList(new String[] { "b1", "b2", "b3" })); - } - - { - String args[] = { "-paramA", "a1", "a2", "-paramB", "b1", "-paramA", "a3" }; - Params p = new Params(); - new JCommander(p, args).parse(); - Assert.assertEquals(p.paramA, Arrays.asList(new String[] { "a1", "a2", "a3" })); - Assert.assertEquals(p.paramB, Arrays.asList(new String[] { "b1" })); - } - } - - @Test(enabled = false, - description = "Need to double check that the command description is i18n'ed in the usage") - public void commandKey() { - @Parameters(resourceBundle = "MessageBundle", commandDescriptionKey = "command") - class Args { - @Parameter(names="-myoption", descriptionKey="myoption") - private boolean option; - } - JCommander j = new JCommander(); - Args a = new Args(); - j.addCommand("comm", a); - j.usage(); - } - - public void tmp() { - class A { - @Parameter(names = "-b") - public String b; - } - new JCommander(new A()).parse(""); - } - - public void unknownOptionWithDifferentPrefix() { - @Parameters(optionPrefixes = "/") - class SlashSeparator { - - @Parameter(names = "/verbose") - public boolean verbose = false; - - @Parameter(names = "/file") - public String file; - } - SlashSeparator ss = new SlashSeparator(); - try { - new JCommander(ss).parse("/notAParam"); - } catch (ParameterException ex) { - boolean result = ex.getMessage().contains("Unknown option"); - Assert.assertTrue(result); - } - } - - public void equalSeparator() { - @Parameters(separators = "=", commandDescription = "My command") - class MyClass { - - @Parameter(names = { "-p", "--param" }, required = true, description = "param desc...") - private String param; - } - MyClass c = new MyClass(); - String expected = "\"hello\"world"; - new JCommander(c).parse("--param=" + expected); - Assert.assertEquals(expected, c.param); - } - - public void simpleArgsSetter() throws ParseException { - Args1Setter args = new Args1Setter(); - String[] argv = { "-debug", "-log", "2", "-float", "1.2", "-double", "1.3", "-bigdecimal", "1.4", - "-date", "2011-10-26", "-groups", "unit", "a", "b", "c" }; - new JCommander(args, argv); - - Assert.assertTrue(args.debug); - Assert.assertEquals(args.verbose.intValue(), 2); - Assert.assertEquals(args.groups, "unit"); - Assert.assertEquals(args.parameters, Arrays.asList("a", "b", "c")); - Assert.assertEquals(args.floa, 1.2f, 0.1f); - Assert.assertEquals(args.doub, 1.3f, 0.1f); - Assert.assertEquals(args.bigd, new BigDecimal("1.4")); - Assert.assertEquals(args.date, new SimpleDateFormat("yyyy-MM-dd").parse("2011-10-26")); - } - - public void verifyHelp() { - class Arg { - @Parameter(names = "--help", help = true) - public boolean help = false; - - @Parameter(names = "file", required = true) - public String file; - } - Arg arg = new Arg(); - String[] argv = { "--help" }; - new JCommander(arg, argv); - - Assert.assertTrue(arg.help); - } - - public void helpTest() { - class Arg { - @Parameter(names = { "?", "-help", "--help" }, description = "Shows help", help = true) - private boolean help = false; - } - Arg arg = new Arg(); - JCommander jc = new JCommander(arg); - jc.parse(new String[] { "-help" }); -// System.out.println("helpTest:" + arg.help); - } - - @Test(enabled = false, description = "Should only be enable once multiple parameters are allowed") - public void duplicateParameterNames() { - class ArgBase { - @Parameter(names = { "-host" }) - protected String host; - } - - class Arg1 extends ArgBase {} - Arg1 arg1 = new Arg1(); - - class Arg2 extends ArgBase {} - Arg2 arg2 = new Arg2(); - - JCommander jc = new JCommander(new Object[] { arg1, arg2}); - jc.parse(new String[] { "-host", "foo" }); - Assert.assertEquals(arg1.host, "foo"); - Assert.assertEquals(arg2.host, "foo"); - } - - public void parameterWithOneDoubleQuote() { - @Parameters(separators = "=") - class Arg { - @Parameter(names = { "-p", "--param" }) - private String param; - } - JCommander jc = new JCommander(new MyClass()); - jc.parse("-p=\""); - } - - public void emptyStringAsDefault() { - class Arg { - @Parameter(names = "-x") - String s = ""; - } - Arg a = new Arg(); - StringBuilder sb = new StringBuilder(); - new JCommander(a).usage(sb); - Assert.assertTrue(sb.toString().contains("Default: <empty string>")); - } - - public void spaces() { - class Arg { - @Parameter(names = "-rule", description = "rule") - private List<String> rules = new ArrayList<String>(); - } - Arg a = new Arg(); - new JCommander(a, "-rule", "some test"); - Assert.assertEquals(a.rules, Arrays.asList("some test")); - } - - static class V2 implements IParameterValidator2 { - final static List<String> names = Lists.newArrayList(); - static boolean validateCalled = false; - - @Override - public void validate(String name, String value) throws ParameterException { - validateCalled = true; - } - - @Override - public void validate(String name, String value, ParameterDescription pd) - throws ParameterException { - names.addAll(Arrays.asList(pd.getParameter().names())); - } - } - - public void validator2() { - class Arg { - @Parameter(names = { "-h", "--host" }, validateWith = V2.class) - String host; - } - Arg a = new Arg(); - V2.names.clear(); - V2.validateCalled = false; - JCommander jc = new JCommander(a, "--host", "h"); - jc.setAcceptUnknownOptions(true); - Assert.assertEquals(V2.names, Arrays.asList(new String[] { "-h", "--host" })); - Assert.assertTrue(V2.validateCalled); - } - - public void usageCommandsUnderUsage() { - class Arg { - } - @Parameters(commandDescription = "command a") - class ArgCommandA { - @Parameter(description = "command a parameters") - List<String> parameters; - } - @Parameters(commandDescription = "command b") - class ArgCommandB { - @Parameter(description = "command b parameters") - List<String> parameters; - } - - Arg a = new Arg(); - - JCommander c = new JCommander(a); - c.addCommand("a", new ArgCommandA()); - c.addCommand("b", new ArgCommandB()); - - StringBuilder sb = new StringBuilder(); - c.usage(sb); - Assert.assertTrue(sb.toString().contains("[command options]\n Commands:")); - } - - public void usageWithEmpytLine() { - class Arg { - } - @Parameters(commandDescription = "command a") - class ArgCommandA { - @Parameter(description = "command a parameters") - List<String> parameters; - } - @Parameters(commandDescription = "command b") - class ArgCommandB { - @Parameter(description = "command b parameters") - List<String> parameters; - } - - Arg a = new Arg(); - - JCommander c = new JCommander(a); - c.addCommand("a", new ArgCommandA()); - c.addCommand("b", new ArgCommandB()); - - StringBuilder sb = new StringBuilder(); - c.usage(sb); - Assert.assertTrue(sb.toString().contains("command a parameters\n\n b")); - } - - public void partialValidation() { - class Arg { - @Parameter(names = { "-h", "--host" }) - String host; - } - Arg a = new Arg(); - JCommander jc = new JCommander(); - jc.setAcceptUnknownOptions(true); - jc.addObject(a); - jc.parse("-a", "foo", "-h", "host"); - Assert.assertEquals(a.host, "host"); - Assert.assertEquals(jc.getUnknownOptions(), Lists.newArrayList("-a", "foo")); - } - - /** - * GITHUB-137. - */ - public void listArgShouldBeCleared() { - class Args { - @Parameter(description = "[endpoint]") - public List<String> endpoint = Lists.newArrayList("prod"); - } - Args a = new Args(); - new JCommander(a, new String[] { "dev" }); - Assert.assertEquals(a.endpoint, Lists.newArrayList("dev")); - } - - public void dashDashParameter() { - class Arguments { - @Parameter(names = { "-name" }) - public String name; - @Parameter - public List<String> mainParameters; - } - - Arguments a = new Arguments(); - new JCommander(a, new String[] { - "-name", "theName", "--", "param1", "param2"} - ); - Assert.assertEquals(a.name, "theName"); - Assert.assertEquals(a.mainParameters.size(), 2); - Assert.assertEquals(a.mainParameters.get(0), "param1"); - Assert.assertEquals(a.mainParameters.get(1), "param2"); - } - - public void dashDashParameter2() { - class Arguments { - @Parameter(names = { "-name" }) - public String name; + + @Test + public void testLongMainParameterDescription() { + //setup + JCommander jc = new JCommander(new ArgsLongMainParameterDescription()); + StringBuilder sb = new StringBuilder(); + + //action + jc.usage(sb); + + //verify + for (String line : sb.toString().split("\n")) { + Assert.assertTrue(line.length() <= jc.getColumnSize(), "line length < column size"); + } + } + + @Test + public void testLongCommandDescription() throws Exception { + //setup + JCommander jc = new JCommander(); + jc.addCommand(new ArgsLongCommandDescription()); + StringBuilder sb = new StringBuilder(); + + //action + jc.usage(sb); + + //verify + for (String line : sb.toString().split("\n")) { + Assert.assertTrue(line.length() <= jc.getColumnSize(), "line length < column size"); + } + } + + @Test + public void testDescriptionWrappingLongWord() { + //setup + StringBuilder sb = new StringBuilder(); + final JCommander jc = new JCommander(new ArgsLongDescription()); + + //action + jc.usage(sb); + + //verify + for (String line : sb.toString().split("\n")) { + Assert.assertTrue(line.length() <= jc.getColumnSize(), "line length < column size"); + } + } + + public void simpleArgs() throws ParseException { + Args1 args = new Args1(); + String[] argv = {"-debug", "-log", "2", "-float", "1.2", "-double", "1.3", "-bigdecimal", "1.4", + "-date", "2011-10-26", "-groups", "unit", "a", "b", "c"}; + new JCommander(args, argv); + + Assert.assertTrue(args.debug); + Assert.assertEquals(args.verbose.intValue(), 2); + Assert.assertEquals(args.groups, "unit"); + Assert.assertEquals(args.parameters, Arrays.asList("a", "b", "c")); + Assert.assertEquals(args.floa, 1.2f, 0.1f); + Assert.assertEquals(args.doub, 1.3f, 0.1f); + Assert.assertEquals(args.bigd, new BigDecimal("1.4")); + Assert.assertEquals(args.date, new SimpleDateFormat("yyyy-MM-dd").parse("2011-10-26")); + } + + @DataProvider + public Object[][] alternateNamesListArgs() { + return new Object[][]{ + new String[][]{new String[]{"--servers", "1", "-s", "2", "--servers", "3"}}, + new String[][]{new String[]{"-s", "1", "-s", "2", "--servers", "3"}}, + new String[][]{new String[]{"--servers", "1", "--servers", "2", "-s", "3"}}, + new String[][]{new String[]{"-s", "1", "--servers", "2", "-s", "3"}}, + new String[][]{new String[]{"-s", "1", "-s", "2", "--servers", "3"}}, + }; + } + + /** + * Confirm that List<?> parameters with alternate names return the correct + * List regardless of how the arguments are specified + */ + + @Test(dataProvider = "alternateNamesListArgs") + public void testAlternateNamesForListParameters(String[] argv) { + AlternateNamesForListArgs args = new AlternateNamesForListArgs(); + + new JCommander(args, argv); + + Assert.assertEquals(args.serverNames.size(), 3); + Assert.assertEquals(args.serverNames.get(0), argv[1]); + Assert.assertEquals(args.serverNames.get(1), argv[3]); + Assert.assertEquals(args.serverNames.get(2), argv[5]); + } + + + /** + * Make sure that if there are args with multiple names (e.g. "-log" and "-verbose"), + * the usage will only display it once. + */ + public void repeatedArgs() { + Args1 args = new Args1(); + String[] argv = {"-log", "2"}; + JCommander jc = new JCommander(args, argv); + Assert.assertEquals(jc.getParameters().size(), 8); + } + + /** + * Not specifying a required option should throw an exception. + */ + @Test(expectedExceptions = ParameterException.class) + public void requiredFields1Fail() { + Args1 args = new Args1(); + String[] argv = {"-debug"}; + new JCommander(args, argv); + } + + /** + * Getting the description of a nonexistent command should throw an exception. + */ + @Test(expectedExceptions = ParameterException.class) + public void nonexistentCommandShouldThrow() { + String[] argv = {}; + JCommander jc = new JCommander(new Object(), argv); + jc.getCommandDescription("foo"); + } + + /** + * Required options with multiple names should work with all names. + */ + private void multipleNames(String option) { + Args1 args = new Args1(); + String[] argv = {option, "2"}; + new JCommander(args, argv); + Assert.assertEquals(args.verbose.intValue(), 2); + } + + public void multipleNames1() { + multipleNames("-log"); + } + + public void multipleNames2() { + multipleNames("-verbose"); + } + + private void i18n1(String bundleName, Locale locale, String expectedString) { + ResourceBundle bundle = locale != null ? ResourceBundle.getBundle(bundleName, locale) + : null; + + ArgsI18N1 i18n = new ArgsI18N1(); + String[] argv = {"-host", "localhost"}; + JCommander jc = new JCommander(i18n, bundle, argv); + + ParameterDescription pd = jc.getParameters().get(0); + Assert.assertEquals(pd.getDescription(), expectedString); + } + + public void i18nNoLocale() { + i18n1("MessageBundle", null, "Host"); + } + + public void i18nUsLocale() { + i18n1("MessageBundle", new Locale("en", "US"), "Host"); + } + + public void i18nFrLocale() { + i18n1("MessageBundle", new Locale("fr", "FR"), "Hôte"); + } + + private void i18n2(Object i18n) { + String[] argv = {"-host", "localhost"}; + Locale.setDefault(new Locale("fr", "FR")); + JCommander jc = new JCommander(i18n, argv); + ParameterDescription pd = jc.getParameters().get(0); + Assert.assertEquals(pd.getDescription(), "Hôte"); + } + + public void i18nWithResourceAnnotation() { + i18n2(new ArgsI18N2()); + } + + public void i18nWithResourceAnnotationNew() { + i18n2(new ArgsI18N2New()); + } + + public void i18MissingKeyForCommand() { + ResourceBundle bundle = ResourceBundle.getBundle("MessageBundle", new Locale("en", "US")); + JCommander jc = new JCommander(new ArgsHelp(), bundle); + jc.addCommand(new ArgsLongCommandDescription()); + StringBuilder sb = new StringBuilder(); + jc.usage(sb); + String usage = sb.toString(); + Assert.assertTrue(usage.contains("text")); + } + + public void noParseConstructor() { + JCommander jCommander = new JCommander(new ArgsMainParameter1()); + jCommander.usage(new StringBuilder()); + // Before fix, this parse would throw an exception, because it calls createDescription, which + // was already called by usage(), and can only be called once. + jCommander.parse(); + } + + /** + * Test a use case where there are required parameters, but you still want + * to interrogate the options which are specified. + */ + public void usageWithRequiredArgsAndResourceBundle() { + ArgsHelp argsHelp = new ArgsHelp(); + JCommander jc = new JCommander(new Object[]{argsHelp, new ArgsRequired()}, + java.util.ResourceBundle.getBundle("MessageBundle")); + // Should be able to display usage without triggering validation + jc.usage(new StringBuilder()); + try { + jc.parse("-h"); + Assert.fail("Should have thrown a required parameter exception"); + } catch (ParameterException e) { + Assert.assertTrue(e.getMessage().contains("are required")); + } + Assert.assertTrue(argsHelp.help); + } + + public void multiObjects() { + ArgsMaster m = new ArgsMaster(); + ArgsSlave s = new ArgsSlave(); + String[] argv = {"-master", "master", "-slave", "slave"}; + new JCommander(new Object[]{m, s}, argv); + + Assert.assertEquals(m.master, "master"); + Assert.assertEquals(s.slave, "slave"); + } + + @Test(expectedExceptions = ParameterException.class) + public void multiObjectsWithDuplicatesFail() { + ArgsMaster m = new ArgsMaster(); + ArgsSlave s = new ArgsSlaveBogus(); + String[] argv = {"-master", "master", "-slave", "slave"}; + new JCommander(new Object[]{m, s}, argv); + } + + public void arityString() { + ArgsArityString args = new ArgsArityString(); + String[] argv = {"-pairs", "pair0", "pair1", "rest"}; + new JCommander(args, argv); + + Assert.assertEquals(args.pairs.size(), 2); + Assert.assertEquals(args.pairs.get(0), "pair0"); + Assert.assertEquals(args.pairs.get(1), "pair1"); + Assert.assertEquals(args.rest.size(), 1); + Assert.assertEquals(args.rest.get(0), "rest"); + } + + @Test(expectedExceptions = ParameterException.class) + public void arity2Fail() { + ArgsArityString args = new ArgsArityString(); + String[] argv = {"-pairs", "pair0"}; + new JCommander(args, argv); + } + + @Test(expectedExceptions = ParameterException.class) + public void multipleUnparsedFail() { + ArgsMultipleUnparsed args = new ArgsMultipleUnparsed(); + String[] argv = {}; + new JCommander(args, argv); + } + + public void privateArgs() { + ArgsPrivate args = new ArgsPrivate(); + new JCommander(args, "-verbose", "3"); + Assert.assertEquals(args.getVerbose().intValue(), 3); + } + + @Test( + expectedExceptions = ParameterException.class, + expectedExceptionsMessageRegExp = "Cannot use final field .*#_foo as a parameter;" + + " compile-time constant inlining may hide new values written to it.") + public void finalArgs() { + Object args = new Object() { + @Parameter(names = "-foo") + final int _foo = 0; + }; + new JCommander(args).usage(); + } + + public void converterArgs() { + ArgsConverter args = new ArgsConverter(); + String fileName = "a"; + new JCommander(args, "-file", "/tmp/" + fileName, + "-listStrings", "Tuesday,Thursday", + "-listInts", "-1,8", + "-listBigDecimals", "-11.52,100.12"); + Assert.assertEquals(args.file.getName(), fileName); + Assert.assertEquals(args.listStrings.size(), 2); + Assert.assertEquals(args.listStrings.get(0), "Tuesday"); + Assert.assertEquals(args.listStrings.get(1), "Thursday"); + Assert.assertEquals(args.listInts.size(), 2); + Assert.assertEquals(args.listInts.get(0).intValue(), -1); + Assert.assertEquals(args.listInts.get(1).intValue(), 8); + Assert.assertEquals(args.listBigDecimals.size(), 2); + Assert.assertEquals(args.listBigDecimals.get(0), new BigDecimal("-11.52")); + Assert.assertEquals(args.listBigDecimals.get(1), new BigDecimal("100.12")); + } + + public void hiddenConverter() { + class Args { + @Parameter(names = "--path", converter = HiddenConverter.class) + public String path; + } + + new JCommander(new Args(), "--path", "/tmp/a"); + } + + public void hiddenArgs() { + new JCommander(new HiddenArgs(), "--input", "/tmp/a", "--output", "/tmp/b"); + } + + public void hiddenSplitter() { + class Args { + @Parameter(names = "--extensions", splitter = HiddenParameterSplitter.class) + public List<String> extensions; + } + if (HiddenParameterSplitter.class.getConstructors().length == 0) { + return; // Compiler has optimised away the private constructor + } + + Args args = new Args(); + new JCommander(args, "--extensions", ".txt;.md"); + Assert.assertEquals(Arrays.asList(".txt", ".md"), args.extensions); + } + + private void argsBoolean1(String[] params, Boolean expected) { + ArgsBooleanArity args = new ArgsBooleanArity(); + new JCommander(args, params); + Assert.assertEquals(args.debug, expected); + } + + private void argsBoolean0(String[] params, Boolean expected) { + ArgsBooleanArity0 args = new ArgsBooleanArity0(); + new JCommander(args, params); + Assert.assertEquals(args.debug, expected); + } + + public void booleanArity1() { + argsBoolean1(new String[]{}, Boolean.FALSE); + argsBoolean1(new String[]{"-debug", "true"}, Boolean.TRUE); + } + + public void booleanArity0() { + argsBoolean0(new String[]{}, Boolean.FALSE); + argsBoolean0(new String[]{"-debug"}, Boolean.TRUE); + } + + @Test(expectedExceptions = ParameterException.class) + public void badParameterShouldThrowParameter1Exception() { + Args1 args = new Args1(); + String[] argv = {"-log", "foo"}; + new JCommander(args, argv); + } + + @Test(expectedExceptions = ParameterException.class) + public void badParameterShouldThrowParameter2Exception() { + Args1 args = new Args1(); + String[] argv = {"-long", "foo"}; + new JCommander(args, argv); + } + + public void listParameters() { + Args2 a = new Args2(); + String[] argv = {"-log", "2", "-groups", "unit", "a", "b", "c", "-host", "host2"}; + new JCommander(a, argv); + Assert.assertEquals(a.verbose.intValue(), 2); + Assert.assertEquals(a.groups, "unit"); + Assert.assertEquals(a.hosts, Arrays.asList("host2")); + Assert.assertEquals(a.parameters, Arrays.asList("a", "b", "c")); + } + + public void separatorEqual() { + SeparatorEqual s = new SeparatorEqual(); + String[] argv = {"-log=3", "--longoption=10"}; + new JCommander(s, argv); + Assert.assertEquals(s.log.intValue(), 3); + Assert.assertEquals(s.longOption.intValue(), 10); + } + + public void separatorColon() { + SeparatorColon s = new SeparatorColon(); + String[] argv = {"-verbose:true"}; + new JCommander(s, argv); + Assert.assertTrue(s.verbose); + } + + public void separatorBoth() { + SeparatorColon s = new SeparatorColon(); + SeparatorEqual s2 = new SeparatorEqual(); + String[] argv = {"-verbose:true", "-log=3"}; + new JCommander(new Object[]{s, s2}, argv); + Assert.assertTrue(s.verbose); + Assert.assertEquals(s2.log.intValue(), 3); + } + + public void separatorMixed1() { + SeparatorMixed s = new SeparatorMixed(); + String[] argv = {"-long:1", "-level=42"}; + new JCommander(s, argv); + Assert.assertEquals(s.l.longValue(), 1l); + Assert.assertEquals(s.level.intValue(), 42); + } + + public void slashParameters() { + SlashSeparator a = new SlashSeparator(); + String[] argv = {"/verbose", "/file", "/tmp/a"}; + new JCommander(a, argv); + Assert.assertTrue(a.verbose); + Assert.assertEquals(a.file, "/tmp/a"); + } + + public void inheritance() { + ArgsInherited args = new ArgsInherited(); + String[] argv = {"-log", "3", "-child", "2"}; + new JCommander(args, argv); + Assert.assertEquals(args.child.intValue(), 2); + Assert.assertEquals(args.log.intValue(), 3); + } + + public void negativeNumber() { + Args1 a = new Args1(); + String[] argv = {"-verbose", "-3"}; + new JCommander(a, argv); + Assert.assertEquals(a.verbose.intValue(), -3); + } + + @Test(expectedExceptions = ParameterException.class) + public void requiredMainParameters() { + ArgsRequired a = new ArgsRequired(); + String[] argv = {}; + new JCommander(a, argv); + } + + public void usageShouldNotChange() { + JCommander jc = new JCommander(new Args1(), "-log", "1"); + StringBuilder sb = new StringBuilder(); + jc.usage(sb); + String expected = sb.toString(); + jc = new JCommander(new Args1(), "-debug", "-log", "2", "-long", "5"); + sb = new StringBuilder(); + jc.usage(sb); + String actual = sb.toString(); + Assert.assertEquals(actual, expected); + } + + private void verifyCommandOrdering(String[] commandNames, Object[] commands) { + CommandMain cm = new CommandMain(); + JCommander jc = new JCommander(cm); + + for (int i = 0; i < commands.length; i++) { + jc.addCommand(commandNames[i], commands[i]); + } + + Map<String, JCommander> c = jc.getCommands(); + Assert.assertEquals(c.size(), commands.length); + + Iterator<String> it = c.keySet().iterator(); + for (int i = 0; i < commands.length; i++) { + Assert.assertEquals(it.next(), commandNames[i]); + } + } + + public void commandsShouldBeShownInOrderOfInsertion() { + verifyCommandOrdering(new String[]{"add", "commit"}, + new Object[]{new CommandAdd(), new CommandCommit()}); + verifyCommandOrdering(new String[]{"commit", "add"}, + new Object[]{new CommandCommit(), new CommandAdd()}); + } + + @DataProvider + public static Object[][] f() { + return new Integer[][]{ + new Integer[]{3, 5, 1}, + new Integer[]{3, 8, 1}, + new Integer[]{3, 12, 2}, + new Integer[]{8, 12, 2}, + new Integer[]{9, 10, 1}, + }; + } + + @Test(expectedExceptions = ParameterException.class) + public void arity1Fail() { + final Arity1 arguments = new Arity1(); + final JCommander jCommander = new JCommander(arguments); + final String[] commands = { + "-inspect" + }; + jCommander.parse(commands); + } + + public void arity1Success1() { + final Arity1 arguments = new Arity1(); + final JCommander jCommander = new JCommander(arguments); + final String[] commands = { + "-inspect", "true" + }; + jCommander.parse(commands); + Assert.assertTrue(arguments.inspect); + } + + public void arity1Success2() { + final Arity1 arguments = new Arity1(); + final JCommander jCommander = new JCommander(arguments); + final String[] commands = { + "-inspect", "false" + }; + jCommander.parse(commands); + Assert.assertFalse(arguments.inspect); + } + + @Parameters(commandDescription = "Help for the given commands.") + public static class Help { + public static final String NAME = "help"; + + @Parameter(description = "List of commands.") + public List<String> commands = new ArrayList<>(); + } + + @Test(expectedExceptions = ParameterException.class, + description = "Verify that the main parameter's type is checked to be a List") + public void wrongMainTypeShouldThrow() { + JCommander jc = new JCommander(new ArgsRequiredWrongMain()); + jc.parse("f1", "f2"); + } + + @Test(description = "This used to run out of memory") + public void oom() { + JCommander jc = new JCommander(new ArgsOutOfMemory()); + jc.usage(new StringBuilder()); + } + + @Test + public void getParametersShouldNotNpe() { + JCommander jc = new JCommander(new Args1()); + List<ParameterDescription> parameters = jc.getParameters(); + } + + public void validationShouldWork1() { + ArgsValidate1 a = new ArgsValidate1(); + JCommander jc = new JCommander(a); + jc.parse("-age", "2 "); + Assert.assertEquals(a.age, new Integer(2)); + } + + @Test(expectedExceptions = ParameterException.class) + public void validationShouldWorkWithDefaultValues() { + ArgsValidate2 a = new ArgsValidate2(); + new JCommander(a).usage(); + } + + @Test + public void multipleValidators() { + for (int i = 1; i < 100; i += 2) { + ArgsMultiValidate a = new ArgsMultiValidate(); + JCommander jc = new JCommander(a); + jc.parse("-age", String.valueOf(i)); + } + } + + @Test(expectedExceptions = ParameterException.class) + public void multipleValidatorsFails1() { + ArgsMultiValidate a = new ArgsMultiValidate(); + JCommander jc = new JCommander(a); + jc.parse("-age", "131"); + } + + @Test(expectedExceptions = ParameterException.class) + public void multipleValidatorsFails2() { + ArgsMultiValidate a = new ArgsMultiValidate(); + JCommander jc = new JCommander(a); + jc.parse("-age", "0"); + } + + @Test(expectedExceptions = ParameterException.class) + public void validationShouldWork2() { + ArgsValidate1 a = new ArgsValidate1(); + JCommander jc = new JCommander(a); + jc.parse("-age", "-2 "); + } + + @Test + public void validationShouldReceiveRightParameterName() { + ArgMultiNameValidator validator = new ArgMultiNameValidator(); + JCommander jc = new JCommander(validator); + String paramName = "-name2"; + jc.parse(paramName, "param1"); + Assert.assertEquals(ArgMultiNameValidator.MultiNameValidator.parsedName, paramName); + } + + public void atFileCanContainEmptyLines() throws IOException { + File f = File.createTempFile("JCommander", null); + f.deleteOnExit(); + FileWriter fw = new FileWriter(f); + fw.write("-log\n"); + fw.write("\n"); + fw.write("2\n"); + fw.close(); + new JCommander(new Args1(), "@" + f.getAbsolutePath()); + } + + public void atFileWithInNonDefaultCharset() throws IOException { + final Charset utf32 = Charset.forName("UTF-32"); + final File f = File.createTempFile("JCommander", null); + f.deleteOnExit(); + try (OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(f), utf32)) { + fw.write("-log\n"); + fw.write("2\n"); + fw.write("-groups\n"); + fw.write("\u9731\n"); + } + final Args1 args1 = new Args1(); + final JCommander jc = new JCommander(args1); + try { + jc.parse("@" + f.getAbsolutePath()); + throw new IllegalStateException("Expected exception to be thrown"); + } catch (ParameterException expected) { + Assert.assertTrue(expected.getMessage().startsWith("Could not read file")); + } + jc.setAtFileCharset(utf32); + jc.parse("@" + f.getAbsolutePath()); + Assert.assertEquals("\u9731", args1.groups); + } + + public void handleEqualSigns() { + ArgsEquals a = new ArgsEquals(); + JCommander jc = new JCommander(a); + jc.parse("-args=a=b,b=c"); + Assert.assertEquals(a.args, "a=b,b=c"); + } + + @SuppressWarnings("serial") + public void handleSets() { + ArgsWithSet a = new ArgsWithSet(); + new JCommander(a, "-s", "3,1,2"); + Assert.assertEquals(a.set, new TreeSet<Integer>() {{ + add(1); + add(2); + add(3); + }}); + } + + private static final List<String> V = Arrays.asList("a", "b", "c", "d"); + + @DataProvider + public Object[][] variable() { + return new Object[][]{ + //new Object[]{0, V.subList(0, 0), V}, + new Object[]{1, V.subList(0, 1), V.subList(1, 4)}, + new Object[]{2, V.subList(0, 2), V.subList(2, 4)}, + new Object[]{3, V.subList(0, 3), V.subList(3, 4)}, + new Object[]{4, V.subList(0, 4), V.subList(4, 4)}, + }; + } + + @Test(dataProvider = "variable") + public void variableArity(int count, List<String> var, List<String> main) { + VariableArity va = new VariableArity(count); + new JCommander(va).parse("-variable", "a", "b", "c", "d"); + Assert.assertEquals(var, va.var); + Assert.assertEquals(main, va.main); + } + + @Test(expectedExceptions = ParameterException.class) + public void variableArityZeroNonBoolean() { + VariableArity va = new VariableArity(0); + new JCommander(va).parse("-variable", "a", "b", "c", "d"); + } + + public void enumArgs() { + ArgsEnum args = new ArgsEnum(); + String[] argv = {"-choice", "ONE", "-choices", "ONE", "Two"}; + JCommander jc = new JCommander(args, argv); + + Assert.assertEquals(args.choice, ArgsEnum.ChoiceType.ONE); + + List<ChoiceType> expected = Arrays.asList(ChoiceType.ONE, ChoiceType.Two); + Assert.assertEquals(expected, args.choices); + + for (ParameterDescription param : jc.getParameters()) { + // order can vary depending on JDK version + if (param.getLongestName().equals("-choice")) { + Assert.assertEquals(param.getDescription(), + "Options: " + EnumSet.allOf((Class<? extends Enum>) ArgsEnum.ChoiceType.class)); + return; + } + } + Assert.fail("Could not find -choice parameter."); + } + + public void enumArgs2() { + // issue #266 + ArgsEnum args = new ArgsEnum(); + new JCommander(args, "-choices", "ONE,Two"); + Assert.assertEquals(Arrays.asList(ChoiceType.ONE, ChoiceType.Two), args.choices); + } + + public void enumArgsCaseInsensitive() { + ArgsEnum args = new ArgsEnum(); + String[] argv = {"-choice", "one"}; + JCommander jc = new JCommander(args, argv); + + Assert.assertEquals(args.choice, ArgsEnum.ChoiceType.ONE); + } + + @Test(expectedExceptions = ParameterException.class) + public void enumArgsFail() { + ArgsEnum args = new ArgsEnum(); + String[] argv = {"-choice", "A"}; + new JCommander(args, argv); + } + + @Test + public void testDefaultListConverterForString() { + ArgsList al = new ArgsList(); + JCommander j = new JCommander(al); + j.parse("-groups", "a,b"); + Assert.assertEquals(al.groups.get(0), "a"); + Assert.assertEquals(al.groups.get(1), "b"); + } + + @Test + public void testDefaultListConverterForStandardType() { + ArgsList al = new ArgsList(); + JCommander j = new JCommander(al); + j.parse("-ints", "41,42"); + Assert.assertEquals(al.ints.get(0).intValue(), 41); + Assert.assertEquals(al.ints.get(1).intValue(), 42); + } + + @Test + public void testDefaultListConverterWithCustomConverterAndSplitter() { + ArgsList al = new ArgsList(); + JCommander j = new JCommander(al); + j.parse("-hp", "localhost:1000;example.com:1001"); + Assert.assertEquals(al.hostPorts.get(0).host, "localhost"); + Assert.assertEquals(al.hostPorts.get(0).port.intValue(), 1000); + Assert.assertEquals(al.hostPorts.get(1).host, "example.com"); + Assert.assertEquals(al.hostPorts.get(1).port.intValue(), 1001); + } + + @Test + public void testDefaultListConverterWithCustomConverterAndDefaultSplitter() { + ArgsList al = new ArgsList(); + JCommander j = new JCommander(al); + j.parse("-hp2", "localhost:1000,example.com:1001"); + Assert.assertEquals(al.hp2.get(1).host, "example.com"); + Assert.assertEquals(al.hp2.get(1).port.intValue(), 1001); + } + + @Test + public void testCustomListConverter() { + ArgsList al = new ArgsList(); + JCommander j = new JCommander(al); + j.parse("-uppercase", "ab,cd"); + Assert.assertEquals(al.uppercase.get(0), "AB"); + Assert.assertEquals(al.uppercase.get(1), "CD"); + } + + @Test(expectedExceptions = ParameterException.class) + public void shouldThrowIfUnknownOption() { + class A { + @Parameter(names = "-long") + public long l; + } + A a = new A(); + new JCommander(a).parse("-lon", "32"); + } + + @Test(expectedExceptions = ParameterException.class) + public void mainParameterShouldBeValidate() { + class V implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + Assert.assertEquals("a", value); + } + } + + class A { + @Parameter(validateWith = V.class) + public List<String> m; + } + + A a = new A(); + new JCommander(a).parse("b"); + } + + @Parameters(commandNames = {"--configure"}) + public static class ConfigureArgs { + } + + public static class BaseArgs { + @Parameter(names = {"-h", "--help"}, description = "Show this help screen") + private boolean help = false; + + @Parameter(names = {"--version", "-version"}, description = "Show the program version") + private boolean version; + } + + public void commandsWithSamePrefixAsOptionsShouldWork() { + BaseArgs a = new BaseArgs(); + ConfigureArgs conf = new ConfigureArgs(); + JCommander jc = new JCommander(a); + jc.addCommand(conf); + jc.parse("--configure"); + } + + public void dynamicParameters() { + class Command { + @DynamicParameter(names = {"-P"}, description = "Additional command parameters") + private Map<String, String> params = Maps.newHashMap(); + } + JCommander commander = new JCommander(); + Command c = new Command(); + commander.addCommand("command", c); + commander.parse("command", "-Pparam='name=value'"); + Assert.assertEquals(c.params.get("param"), "'name=value'"); + } + + public void exeParser() { + class Params { + @Parameter(names = "-i") + private String inputFile; + } + + String args[] = {"-i", ""}; + Params p = new Params(); + new JCommander(p, args); + } + + public void multiVariableArityList() { + class Params { + @Parameter(names = "-paramA", description = "ParamA", variableArity = true) + private List<String> paramA = Lists.newArrayList(); + + @Parameter(names = "-paramB", description = "ParamB", variableArity = true) + private List<String> paramB = Lists.newArrayList(); + } + + { + String args[] = {"-paramA", "a1", "a2", "-paramB", "b1", "b2", "b3"}; + Params p = new Params(); + new JCommander(p, args).parse(); + Assert.assertEquals(p.paramA, Arrays.asList("a1", "a2")); + Assert.assertEquals(p.paramB, Arrays.asList("b1", "b2", "b3")); + } + + { + String args[] = {"-paramA", "a1", "a2", "-paramB", "b1", "-paramA", "a3"}; + Params p = new Params(); + new JCommander(p, args).parse(); + Assert.assertEquals(p.paramA, Arrays.asList("a1", "a2", "a3")); + Assert.assertEquals(p.paramB, Arrays.asList("b1")); + } + } + + @Test(enabled = false, + description = "Need to double check that the command description is i18n'ed in the usage") + public void commandKey() { + @Parameters(resourceBundle = "MessageBundle", commandDescriptionKey = "command") + class Args { + @Parameter(names = "-myoption", descriptionKey = "myoption") + private boolean option; + } + JCommander j = new JCommander(); + Args a = new Args(); + j.addCommand("comm", a); + j.usage(); + } + + @Test(expectedExceptions = ParameterException.class, + expectedExceptionsMessageRegExp = "Was passed main parameter '' but no main parameter was defined.*") + public void tmp() { + class A { + @Parameter(names = "-b") + public String b; + } + new JCommander(new A()).parse(""); + } + + @Test(expectedExceptions = ParameterException.class, expectedExceptionsMessageRegExp = "\"--b\": couldn't convert \"ThisIsATest\" to an integer") + public void multipleParameterNames() { + class MultipleParameterNames { + @Parameter(names = {"-b", "--b"}) + public Integer b; + } + new JCommander(new MultipleParameterNames()).parse("--b", "ThisIsATest"); + } + + public void unknownOptionWithDifferentPrefix() { + @Parameters + class SlashSeparator { + + @Parameter(names = "/verbose") + public boolean verbose = false; + + @Parameter(names = "/file") + public String file; + } + SlashSeparator ss = new SlashSeparator(); + try { + new JCommander(ss).parse("/notAParam"); + } catch (ParameterException ex) { + boolean result = ex.getMessage().contains("in your arg class"); + Assert.assertTrue(result); + } + } + + public void equalSeparator() { + @Parameters(separators = "=", commandDescription = "My command") + class MyClass { + + @Parameter(names = {"-p", "--param"}, required = true, description = "param desc...") + private String param; + } + MyClass c = new MyClass(); + String expected = "\"hello\"world"; + new JCommander(c).parse("--param=" + expected); + Assert.assertEquals(expected, c.param); + } + + public void simpleArgsSetter() throws ParseException { + Args1Setter args = new Args1Setter(); + String[] argv = {"-debug", "-log", "2", "-float", "1.2", "-double", "1.3", "-bigdecimal", "1.4", + "-date", "2011-10-26", "-groups", "unit", "a", "b", "c"}; + new JCommander(args, argv); + + Assert.assertTrue(args.debug); + Assert.assertEquals(args.verbose.intValue(), 2); + Assert.assertEquals(args.groups, "unit"); + Assert.assertEquals(args.parameters, Arrays.asList("a", "b", "c")); + Assert.assertEquals(args.floa, 1.2f, 0.1f); + Assert.assertEquals(args.doub, 1.3f, 0.1f); + Assert.assertEquals(args.bigd, new BigDecimal("1.4")); + Assert.assertEquals(args.date, new SimpleDateFormat("yyyy-MM-dd").parse("2011-10-26")); + } + + public void verifyHelp() { + class Arg { + @Parameter(names = "--help", help = true) + public boolean help = false; + + @Parameter(names = "file", required = true) + public String file; + } + Arg arg = new Arg(); + String[] argv = {"--help"}; + new JCommander(arg, argv); + + Assert.assertTrue(arg.help); + } + + public void helpTest() { + class Arg { + @Parameter(names = {"?", "-help", "--help"}, description = "Shows help", help = true) + private boolean help = false; + } + Arg arg = new Arg(); + JCommander jc = new JCommander(arg); + jc.parse("-help"); + } + + @Test + public void doNotDisplayHelpDefaultValue() { + class Arg { + @Parameter(names = "--help", help = true) + public boolean help = false; + } + Arg arg = new Arg(); + String[] argv = {"--help"}; + JCommander jc = new JCommander(arg, argv); + + StringBuilder sb = new StringBuilder(); + + jc.usage(sb); + + Assert.assertFalse(sb.toString().contains("Default")); + } + + @Test(enabled = false, description = "Should only be enable once multiple parameters are allowed") + public void duplicateParameterNames() { + class ArgBase { + @Parameter(names = {"-host"}) + protected String host; + } + + class Arg1 extends ArgBase { + } + Arg1 arg1 = new Arg1(); + + class Arg2 extends ArgBase { + } + Arg2 arg2 = new Arg2(); + + JCommander jc = new JCommander(new Object[]{arg1, arg2}); + jc.parse("-host", "foo"); + Assert.assertEquals(arg1.host, "foo"); + Assert.assertEquals(arg2.host, "foo"); + } + + @Test(enabled = true, description = "Disable top-level @/ampersand file expansion") + public void disabledAtSignExpansionTest() { + class Params { + @Parameter(names = {"-username"}) + protected String username; + } + + Params params = new Params(); + + JCommander jc = new JCommander(params); + jc.setExpandAtSign(false); + jc.parse("-username", "@tzellman"); + Assert.assertEquals(params.username, "@tzellman"); + } + + @Test(enabled = true, description = "Enable top-level @/ampersand file expansion, which should throw in this case", + expectedExceptions = ParameterException.class) + public void enabledAtSignExpansionTest() { + class Params { + @Parameter(names = {"-username"}) + protected String username; + } + + Params params = new Params(); + + JCommander jc = new JCommander(params); + jc.parse("-username", "@tzellman"); + Assert.assertEquals(params.username, "@tzellman"); + } + + public void parameterWithOneDoubleQuote() { + @Parameters(separators = "=") + class Arg { + @Parameter(names = {"-p", "--param"}) + private String param; + } + JCommander jc = new JCommander(new MyClass()); + jc.parse("-p=\""); + } + + public void emptyStringAsDefault() { + class Arg { + @Parameter(names = "-x") + String s = ""; + } + Arg a = new Arg(); + StringBuilder sb = new StringBuilder(); + new JCommander(a).usage(sb); + Assert.assertTrue(sb.toString().contains("Default: <empty string>")); + } + + @Test + public void emptyStringShouldBeConsideredAsParameter() { + class Arg { + @Parameter(description = "parameters") + List<String> params; + } + + Arg a = new Arg(); + String[] args = {""}; + + new JCommander(a).parse(args); + Assert.assertEquals(a.params.size(), 1); +// Assert.assertEquals(); + } + + @Test + public void doubleQuotedStringShouldBeConsideredAsParameter() { + class Arg { + @Parameter(description = "parameters") + List<String> params; + } + + Arg a = new Arg(); + String[] args = {"\"\""}; + + new JCommander(a).parse(args); + Assert.assertEquals(a.params.size(), 1); +// Assert.assertEquals(); + } + + public void spaces() { + class Arg { + @Parameter(names = "-rule", description = "rule") + private List<String> rules = new ArrayList<>(); + } + Arg a = new Arg(); + new JCommander(a, "-rule", "some test"); + Assert.assertEquals(a.rules, Arrays.asList("some test")); + } + + static class V2 implements IParameterValidator2 { + final static List<String> names = Lists.newArrayList(); + static boolean validateCalled = false; + + @Override + public void validate(String name, String value) throws ParameterException { + validateCalled = true; + } + + @Override + public void validate(String name, String value, ParameterDescription pd) + throws ParameterException { + names.addAll(Arrays.asList(pd.getParameter().names())); + } + } + + public void validator2() { + class Arg { + @Parameter(names = {"-h", "--host"}, validateWith = V2.class) + String host; + } + Arg a = new Arg(); + V2.names.clear(); + V2.validateCalled = false; + JCommander jc = new JCommander(a, "--host", "h"); + jc.setAcceptUnknownOptions(true); + Assert.assertEquals(V2.names, Arrays.asList("-h", "--host")); + Assert.assertTrue(V2.validateCalled); + } + + public void usageCommandsUnderUsage() { + class Arg { + } + @Parameters(commandDescription = "command a") + class ArgCommandA { + @Parameter(description = "command a parameters") + List<String> parameters; + } + @Parameters(commandDescription = "command b") + class ArgCommandB { + @Parameter(description = "command b parameters") + List<String> parameters; + } + + Arg a = new Arg(); + + JCommander c = new JCommander(a); + c.addCommand("a", new ArgCommandA()); + c.addCommand("b", new ArgCommandB()); + + StringBuilder sb = new StringBuilder(); + c.usage(sb); + Assert.assertTrue(sb.toString().contains("[command options]\n Commands:")); + } + + public void usageWithEmpytLine() { + class Arg { + } + @Parameters(commandDescription = "command a") + class ArgCommandA { + @Parameter(description = "command a parameters") + List<String> parameters; + } + @Parameters(commandDescription = "command b") + class ArgCommandB { + @Parameter(description = "command b parameters") + List<String> parameters; + } + + Arg a = new Arg(); + + JCommander c = new JCommander(a); + c.addCommand("a", new ArgCommandA()); + c.addCommand("b", new ArgCommandB()); + + StringBuilder sb = new StringBuilder(); + c.usage(sb); + Assert.assertTrue(sb.toString().contains("command a parameters\n\n b")); + } + + public void usageWithSubCommands() { + class Arg { + } + @Parameters(commandDescription = "command a") + class ArgCommandA { + @Parameter(description = "command a parameters") + List<String> parameters; + } + @Parameters(commandDescription = "command b") + class ArgCommandB { + @Parameter(description = "command b parameters") + List<String> parameters; + } + + Arg a = new Arg(); + + JCommander c = new JCommander(a); + c.setColumnSize(100); + c.addCommand("a", new ArgCommandA()); + + // b is a sub-command of a + JCommander aCommand = c.getCommands().get("a"); + aCommand.addCommand("b", new ArgCommandB()); + + StringBuilder sb = new StringBuilder(); + c.usage(sb); + Assert.assertTrue(sb.toString().contains("command a parameters\n Commands:")); + Assert.assertTrue(sb.toString().contains("command b\n Usage:")); + } + + public void partialValidation() { + class Arg { + @Parameter(names = {"-h", "--host"}) + String host; + } + Arg a = new Arg(); + JCommander jc = new JCommander(); + jc.setAcceptUnknownOptions(true); + jc.addObject(a); + jc.parse("-a", "foo", "-h", "host"); + Assert.assertEquals(a.host, "host"); + Assert.assertEquals(jc.getUnknownOptions(), Lists.newArrayList("-a", "foo")); + } + + /** + * GITHUB-137. + */ + public void listArgShouldBeCleared() { + class Args { + @Parameter(description = "[endpoint]") + public List<String> endpoint = Lists.newArrayList("prod"); + } + Args a = new Args(); + new JCommander(a, "dev"); + Assert.assertEquals(a.endpoint, Lists.newArrayList("dev")); + } + + @Test + public void dashDashEmpty() { + class Parameters { + @Parameter + public List<String> mainParameters = new ArrayList<>(); + } + + Parameters a = new Parameters(); + new JCommander(a, "--"); + Assert.assertTrue(a.mainParameters.isEmpty()); + } + + @Test + public void dashDashDashDash() { + class Parameters { + @Parameter + public List<String> mainParameters = new ArrayList<>(); + } + + Parameters a = new Parameters(); + new JCommander(a, "--", "--"); + Assert.assertEquals(a.mainParameters.size(), 1); + Assert.assertEquals(a.mainParameters.get(0), "--"); + } + + public void dashDashParameter() { + class Parameters { + @Parameter(names = {"-name"}) + public String name; + @Parameter + public List<String> mainParameters; + } + + Parameters a = new Parameters(); + new JCommander(a, "-name", "theName", "--", "param1", "param2"); + Assert.assertEquals(a.name, "theName"); + Assert.assertEquals(a.mainParameters.size(), 2); + Assert.assertEquals(a.mainParameters.get(0), "param1"); + Assert.assertEquals(a.mainParameters.get(1), "param2"); + } + + public void dashDashParameter2() { + class Parameters { + @Parameter(names = {"-name"}) + public String name; + @Parameter + public List<String> mainParameters; + } + + Parameters a = new Parameters(); + new JCommander(a, "param1", "param2", "--", "param3", "-name", "theName"); + Assert.assertNull(a.name); + Assert.assertEquals(a.mainParameters.size(), 5); + Assert.assertEquals(a.mainParameters.get(0), "param1"); + Assert.assertEquals(a.mainParameters.get(1), "param2"); + Assert.assertEquals(a.mainParameters.get(2), "param3"); + Assert.assertEquals(a.mainParameters.get(3), "-name"); + Assert.assertEquals(a.mainParameters.get(4), "theName"); + } + + public void access() { + class Parameters { + private int bar; + + @Parameter(names = {"-bar", "-foo"}) + private void setBar(int value) { + bar = value; + } + + @Parameter(names = "-otherName") + private String otherName; + } + + Parameters a = new Parameters(); + new JCommander(a, "-bar", "1"); + Assert.assertEquals(a.bar, 1); + } + + public void noDash() { + class Parameters { + private int bar; + + @Parameter(names = {"bar", "foo"}) + private void setBar(int value) { + bar = value; + } + + @Parameter(names = "otherName") + private String otherName; + } + + Parameters a = new Parameters(); + new JCommander(a, "bar", "1"); + Assert.assertEquals(a.bar, 1); + } + + public void commitTest() { + CommandCommit cc = new CommandCommit(); + new JCommander(cc, "--author=cedric"); + Assert.assertEquals(cc.author, "cedric"); + } + + static class CommandTemplate { @Parameter - public List<String> mainParameters; - } - - Arguments a = new Arguments(); - new JCommander(a, new String[] { - "param1", "param2", "--", "param3", "-name", "theName"} - ); - Assert.assertNull(a.name); - Assert.assertEquals(a.mainParameters.size(), 5); - Assert.assertEquals(a.mainParameters.get(0), "param1"); - Assert.assertEquals(a.mainParameters.get(1), "param2"); - Assert.assertEquals(a.mainParameters.get(2), "param3"); - Assert.assertEquals(a.mainParameters.get(3), "-name"); - Assert.assertEquals(a.mainParameters.get(4), "theName"); - } - - @Test(enabled = false) - public static void main(String[] args) throws Exception { - new JCommanderTest().enumArgsFail(); -// class A { -// @Parameter(names = "-short", required = true) -// List<String> parameters; -// -// @Parameter(names = "-long", required = true) -// public long l; -// } -// A a = new A(); -// new JCommander(a).parse(); -// System.out.println(a.l); -// System.out.println(a.parameters); -// ArgsList al = new ArgsList(); -// JCommander j = new JCommander(al); -// j.setColumnSize(40); -// j.usage(); -// new JCommanderTest().testListAndSplitters(); -// new JCommanderTest().converterArgs(); - } - - // Tests: - // required unparsed parameter + private List<String> parameters = new ArrayList<>(); + + @Parameter(names = "help", help = true) + private boolean help; + } + + public void noDashCommand() { + class P1 { + @Parameter(names = "hello") + private int test; + } + P1 p1 = new P1(); + JCommander j = new JCommander(); + j.addCommand("p1", p1); + j.parse("p1", "hello", "47"); + Assert.assertEquals(p1.test, 47); + } + + static class ValuesValidator implements IValueValidator<List<Integer>> { + @Override + public void validate(String name, List<Integer> values) throws ParameterException { + int previous = Integer.MIN_VALUE; + for (Integer i : values) { + if (i <= previous) { + throw new ParameterException("Invalid: values should be strictly increasing."); + } + previous = i; + } + } + } + + @Test(expectedExceptions = ParameterException.class, expectedExceptionsMessageRegExp = ".*strictly.*") + public void issue() { + class Parameters { + @Parameter(names = {"-v", "--values"}, + required = true, + variableArity = true, + validateValueWith = ValuesValidator.class) + private List<Integer> values; + } + + String[] commands = "-v 1 5 2".split("\\s+"); + Parameters arg = new Parameters(); + new JCommander(arg, commands); + } + + static class MvParameters { + @SubParameter(order = 0) + String from; + @SubParameter(order = 1) + String to; + } + + @Test + public void arity() { + class Parameters { + @Parameter(names = {"--mv"}, arity = 2) + private MvParameters mvParameters; + } + + Parameters args = new Parameters(); + JCommander.newBuilder() + .addObject(args) + .args(new String[]{"--mv", "from", "to"}) + .build(); + + Assert.assertNotNull(args.mvParameters); + Assert.assertEquals(args.mvParameters.from, "from"); + Assert.assertEquals(args.mvParameters.to, "to"); + } + + public void programName() { + JCommander jcommander = new JCommander(); + String programName = "main"; + jcommander.setProgramName(programName); + StringBuilder sb = new StringBuilder(); + jcommander.usage(sb); + + Assert.assertTrue(sb.toString().contains(programName)); + Assert.assertEquals(jcommander.getProgramName(), programName); + } + + public void dontShowOptionUsageIfThereAreNoOptions() { + class CommandTemplate { + @Parameter + List<String> parameters = new ArrayList<>(); + } + + CommandTemplate template = new CommandTemplate(); + JCommander jcommander = new JCommander(template); + jcommander.setProgramName("main"); + StringBuilder sb = new StringBuilder(); + jcommander.usage(sb); + Assert.assertEquals(sb.toString().indexOf("options"), -1); + } + + @Test + public void annotationsAndDynamicParameters() { + class DSimple { + @DynamicParameter(names = "-D", description = "Dynamic parameters go here") + public Map<String, String> params = Maps.newHashMap(); + + @DynamicParameter(names = "-A", assignment = "@") + public Map<String, String> params2 = Maps.newHashMap(); + } + + new JCommander(new DSimple()).usage(new StringBuilder()); + } + + @Test + public void twoCommandsSameOption() { + class GenerateOption { + @Parameter(names = {"--config"}, required = true, converter = FileConverter.class) + public File configFile; + } + + class RegenerateOption { + @Parameter(names = {"--config"}, required = true, converter = FileConverter.class) + public File configFile; + } + + GenerateOption generateOption = new GenerateOption(); + RegenerateOption regenerateOption = new RegenerateOption(); + JCommander.newBuilder() + .addCommand("--generate", generateOption) + .addCommand("--regenerate", regenerateOption) + .args(new String[]{"--generate", "--config", "foo.txt"}) + .build(); + Assert.assertEquals(generateOption.configFile.getName(), "foo.txt"); + } + + @Test + public void invertedBoolean() { + class Args { + @Parameter(names = {"--f"}) + private boolean f = true; + } + Args args = new Args(); + JCommander.newBuilder() + .addObject(args) + .args(new String[]{"--f"}) + .build(); + Assert.assertEquals(args.f, false); + } + + @Test(enabled = false) + public static void main(String[] args) { + + CommandTemplate template = new CommandTemplate(); + JCommander jcommander = new JCommander(template); + jcommander.setProgramName("prog"); + jcommander.parse("help"); + + if (template.help) { + jcommander.usage(); + } + } } diff --git a/src/test/java/com/beust/jcommander/MethodSetterTest.java b/src/test/java/com/beust/jcommander/MethodSetterTest.java index f995ad6..49bb42c 100644 --- a/src/test/java/com/beust/jcommander/MethodSetterTest.java +++ b/src/test/java/com/beust/jcommander/MethodSetterTest.java @@ -26,9 +26,6 @@ public class MethodSetterTest { public void setRest(List<String> rest) { this.rest = rest; } -// public List<String> getRest() { -// return this.rest; -// } public List<String> rest; } ArgsArityStringSetter args = new ArgsArityStringSetter(); @@ -51,7 +48,7 @@ public class MethodSetterTest { } boolean passed = false; try { - new JCommander(new Arg(), new String[] { "--host", "host" }); + new JCommander(new Arg(), "--host", "host"); } catch(ParameterException ex) { Assert.assertEquals(ex.getCause(), null); passed = true; @@ -73,7 +70,7 @@ public class MethodSetterTest { } } Arg arg = new Arg(); - new JCommander(arg, new String[] { "--port", "42" }); + new JCommander(arg, "--port", "42"); Assert.assertEquals(arg.port, new Integer(42)); } @@ -88,7 +85,7 @@ public class MethodSetterTest { } } Arg arg = new Arg(); - JCommander jc = new JCommander(arg, new String[] { "--port", "42" }); + JCommander jc = new JCommander(arg, "--port", "42"); ParameterDescription pd = jc.getParameters().get(0); Assert.assertEquals(pd.getDefault(), 43); } diff --git a/src/test/java/com/beust/jcommander/ParametersDelegateTest.java b/src/test/java/com/beust/jcommander/ParametersDelegateTest.java index 46c7c6a..1f9f9e0 100644 --- a/src/test/java/com/beust/jcommander/ParametersDelegateTest.java +++ b/src/test/java/com/beust/jcommander/ParametersDelegateTest.java @@ -143,7 +143,7 @@ public class ParametersDelegateTest { public void mainParametersTest() { class Delegate { @Parameter - public List<String> mainParams = new ArrayList<String>(); + public List<String> mainParams = new ArrayList<>(); } class Command { @ParametersDelegate @@ -200,11 +200,11 @@ public class ParametersDelegateTest { public void duplicateMainParametersAreNotAllowed() { class Delegate1 { @Parameter - public List<String> mainParams1 = new ArrayList<String>(); + public List<String> mainParams1 = new ArrayList<>(); } class Delegate2 { @Parameter - public List<String> mainParams2 = new ArrayList<String>(); + public List<String> mainParams2 = new ArrayList<>(); } class Command { @ParametersDelegate diff --git a/src/test/java/com/beust/jcommander/ParametersNotEmptyTest.java b/src/test/java/com/beust/jcommander/ParametersNotEmptyTest.java new file mode 100644 index 0000000..7ebfc24 --- /dev/null +++ b/src/test/java/com/beust/jcommander/ParametersNotEmptyTest.java @@ -0,0 +1,37 @@ +package com.beust.jcommander; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Test +public class ParametersNotEmptyTest { + + public class Args1 { + @Parameter(names = "-debug", description = "Debug mode") + public boolean debug = false; + + @Parameter(names = "-date", description = "An ISO 8601 formatted date.") + public Date date; + } + + @Test + public void testParameters() throws Exception { + JCommander jc = new JCommander(new Args1()); + List<String> parameters = new ArrayList<>(); + for (ParameterDescription pd : jc.getParameters()) { + parameters.add(pd.getNames()); + } + Collections.sort(parameters); + + Assert.assertEquals(parameters, new ArrayList<String>() {{ + add("-date"); + add("-debug"); + }} + ); + } +} diff --git a/src/test/java/com/beust/jcommander/PasswordTest.java b/src/test/java/com/beust/jcommander/PasswordTest.java new file mode 100644 index 0000000..ff1792e --- /dev/null +++ b/src/test/java/com/beust/jcommander/PasswordTest.java @@ -0,0 +1,111 @@ +package com.beust.jcommander; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class PasswordTest { + + @DataProvider(name = "args") + public Object[][] createArgs() { + return new Object[][] { + { new PasswordTestingArgs() }, + { new OptionalPasswordTestingArgs() }, + }; + } + + public interface Args { + + String getPassword(); + + int getPort(); + + } + + public static class PasswordTestingArgs implements PasswordTest.Args { + @Parameter(names = {"--password", "-p"}, description = "Private key password", + password = true, required = true) + public String password; + + @Parameter(names = {"--port", "-o"}, description = "Port to bind server to", + required = true) + public int port; + + @Override + public String getPassword() { + return password; + } + + @Override + public int getPort() { + return port; + } + } + + @Test(dataProvider = "args") + public void passwordNotAsked(Args a) { + String expectedPassword = "somepassword"; + int expectedPort = 7; + new JCommander(a, "--password", expectedPassword, "--port", String.valueOf(7)); + Assert.assertEquals(a.getPort(), expectedPort); + Assert.assertEquals(a.getPassword(), expectedPassword); + } + + @Test(dataProvider = "args", expectedExceptions = ParameterException.class) + public void passwordWithExcessiveArity(Args a) { + new JCommander(a, "--password", "somepassword", "someotherarg", "--port", String.valueOf(7)); + } + + @Test(dataProvider = "args") + public void passwordAsked(Args a) { + InputStream stdin = System.in; + String password = "password"; + int port = 7; + try { + System.setIn(new ByteArrayInputStream(password.getBytes())); + new JCommander(a, "--port", String.valueOf(port), "--password"); + Assert.assertEquals(a.getPort(), port); + Assert.assertEquals(a.getPassword(), password); + } finally { + System.setIn(stdin); + } + } + + public static class OptionalPasswordTestingArgs implements PasswordTest.Args { + @Parameter(names = {"--password", "-p"}, description = "Private key password", + password = true) + public String password; + + @Parameter(names = {"--port", "-o"}, description = "Port to bind server to", + required = true) + public int port; + + @Override + public String getPassword() { + return password; + } + + @Override + public int getPort() { + return port; + } + } + + @Test + public void passwordOptionalNotProvided() { + Args a = new OptionalPasswordTestingArgs(); + new JCommander(a, "--port", "7"); + Assert.assertEquals(a.getPort(), 7); + Assert.assertEquals(a.getPassword(), null); + } + + @Test(expectedExceptions = ParameterException.class) + public void passwordRequredNotProvided() { + Args a = new PasswordTestingArgs(); + new JCommander(a, "--port", "7"); + } + +} diff --git a/src/test/java/com/beust/jcommander/PositiveIntegerTest.java b/src/test/java/com/beust/jcommander/PositiveIntegerTest.java index ec7d273..81d9b37 100644 --- a/src/test/java/com/beust/jcommander/PositiveIntegerTest.java +++ b/src/test/java/com/beust/jcommander/PositiveIntegerTest.java @@ -14,7 +14,7 @@ public class PositiveIntegerTest { } Arg arg = new Arg(); JCommander jc = new JCommander(arg); - jc.parse(new String[] { "-p", "8080" }); + jc.parse("-p", "8080"); } @@ -26,7 +26,7 @@ public class PositiveIntegerTest { } Arg arg = new Arg(); JCommander jc = new JCommander(arg); - jc.parse(new String[] { "-p", "" }); + jc.parse("-p", ""); } @Test(expectedExceptions = ParameterException.class) @@ -37,7 +37,7 @@ public class PositiveIntegerTest { } Arg arg = new Arg(); JCommander jc = new JCommander(arg); - jc.parse(new String[] { "-p", "-1" }); + jc.parse("-p", "-1"); } @Test(expectedExceptions = ParameterException.class) @@ -48,7 +48,7 @@ public class PositiveIntegerTest { } Arg arg = new Arg(); JCommander jc = new JCommander(arg); - jc.parse(new String[] { "-p", "abc" }); + jc.parse("-p", "abc"); } @Test(expectedExceptions = ParameterException.class) @@ -60,6 +60,6 @@ public class PositiveIntegerTest { Arg arg = new Arg(); JCommander jc = new JCommander(arg); - jc.parse(new String[] { "--port", " " }); + jc.parse("--port", " "); } }
\ No newline at end of file diff --git a/src/test/java/com/beust/jcommander/SetConverter.java b/src/test/java/com/beust/jcommander/SetConverter.java index c19df11..580fd98 100644 --- a/src/test/java/com/beust/jcommander/SetConverter.java +++ b/src/test/java/com/beust/jcommander/SetConverter.java @@ -6,7 +6,7 @@ import java.util.TreeSet; public class SetConverter implements IStringConverter<SortedSet<Integer>> { public SortedSet<Integer> convert(String value) { - SortedSet<Integer> set = new TreeSet<Integer>(); + SortedSet<Integer> set = new TreeSet<>(); String[] values = value.split(","); for (String num : values) { set.add(Integer.parseInt(num)); diff --git a/src/test/java/com/beust/jcommander/SimpleExample.java b/src/test/java/com/beust/jcommander/SimpleExample.java new file mode 100644 index 0000000..b8000ca --- /dev/null +++ b/src/test/java/com/beust/jcommander/SimpleExample.java @@ -0,0 +1,50 @@ +package com.beust.jcommander; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import static org.testng.Assert.assertEquals; + +class Main { + static PrintWriter out; + @Parameter(names={"--length", "-l"}) + int length; + @Parameter(names={"--pattern", "-p"}) + int pattern; + + public static void main(String ... args) { + Main main = new Main(); + new JCommander(main, args); + main.run(); + } + + public void run() { + out.printf("%d %d", length, pattern); + } +} + +public class SimpleExample { + StringWriter out; + + @BeforeMethod + public void setupMain(){ + out=new StringWriter(); + Main.out=new PrintWriter(out); + } + + @Test + public void testLongArgs() { + Main.main("--length", "512", "--pattern", "2"); + assertEquals("512 2", out.toString()); + } + + @Test + public void testShortArgs() { + Main.main("-l", "256", "-p", "171"); + assertEquals("256 171", out.toString()); + } + +} diff --git a/src/test/java/com/beust/jcommander/TypeHierarchyTest.java b/src/test/java/com/beust/jcommander/TypeHierarchyTest.java new file mode 100644 index 0000000..1ae3771 --- /dev/null +++ b/src/test/java/com/beust/jcommander/TypeHierarchyTest.java @@ -0,0 +1,111 @@ +package com.beust.jcommander; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * <p>Test that parent classes and interfaces are used correctly</p> + */ +public class TypeHierarchyTest { + + public interface Marker { + @Parameter(names = {"--available"}) + void setAvailable(boolean available); + } + + public class Base implements Marker { + boolean available = false; + + public boolean isAvailable() { + return available; + } + + @Override + public void setAvailable(boolean available) { + this.available = true; + } + } + + public interface IMiddle { + @Parameter(names = {"--count"}) + void setCount(int count); + } + + public interface IMiddleMiddle extends IMiddle { + @Parameter(names = {"--again"}) + void setCountAgain(int count); + } + + public class Middle extends Base implements IMiddleMiddle { + + private int count; + private int again; + + public int getCount() { + return count; + } + + @Override + public void setCount(int count) { + this.count = count; + } + + public int getCountAgain() { + return again; + } + + @Override + public void setCountAgain(int again) { + this.again = again; + } + } + + // trying to trip it up and get it to go from Child -> Composite -> Visitor + public interface Composite extends Visitor { + @Parameter(names = {"-n", "--name"}) + void setName(String validate); + } + + public interface Visitor { + @Parameter(names = {"--validate"}) + void setValidate(boolean validate); + } + + public class Child extends Middle implements Composite, Visitor{ + + private String name; + private boolean validate = false; + + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + public boolean isValidate() { + return validate; + } + + @Override + public void setValidate(boolean validate) { + this.validate = validate; + } + } + + @Test + public void testTypeHierarchy() { + final Child child = new Child(); + JCommander commander = new JCommander(child); + commander.parse("--validate","-n","child-one","--count","14","--again","22","--available"); + + // test values through entire hierarchy + Assert.assertTrue(child.isValidate()); + Assert.assertTrue(child.isAvailable()); + Assert.assertEquals(14, child.getCount()); + Assert.assertEquals(22, child.getCountAgain()); + Assert.assertEquals("child-one", child.getName()); + } +} diff --git a/src/test/java/com/beust/jcommander/ValidatePropertiesWhenParsingTest.java b/src/test/java/com/beust/jcommander/ValidatePropertiesWhenParsingTest.java index 6a3a98f..e237d73 100644 --- a/src/test/java/com/beust/jcommander/ValidatePropertiesWhenParsingTest.java +++ b/src/test/java/com/beust/jcommander/ValidatePropertiesWhenParsingTest.java @@ -10,9 +10,8 @@ public class ValidatePropertiesWhenParsingTest { JCommander cmd = new JCommander(); cmd.addCommand("a", new A()); -// cmd.addCommand("b", new B()); - cmd.parse(new String[] { "a", "-path", "myPathToHappiness" }); + cmd.parse("a", "-path", "myPathToHappiness"); } public static class MyPathValidator implements IParameterValidator { diff --git a/src/test/java/com/beust/jcommander/VariableArityTest.java b/src/test/java/com/beust/jcommander/VariableArityTest.java index a90392f..c2d86b8 100644 --- a/src/test/java/com/beust/jcommander/VariableArityTest.java +++ b/src/test/java/com/beust/jcommander/VariableArityTest.java @@ -13,15 +13,15 @@ public class VariableArityTest { @Parameter(names = { "-m", "--matrixData" }, variableArity = true, description = "File containing a list of instances and their runtimes on various configurations", required = false) - public List<String> modelMatrixFile = new LinkedList<String>(); + public List<String> modelMatrixFile = new LinkedList<>(); @Parameter(names = { "-f", "--featureData" }, variableArity = true, description = "File containing a list of instances and their corresponding features", required = true) - public List<String> featureFile = new LinkedList<String>(); + public List<String> featureFile = new LinkedList<>(); @Parameter(names = { "-c", "--configData" }, variableArity = true, description = "File containing a list of configuration parameter values") - public List<String> configFile = new LinkedList<String>(); + public List<String> configFile = new LinkedList<>(); @Parameter(names = { "-o", "--outputFile" }, description = "File to output the resulting data to. Defaults to ./matrix-generation.zip", required = false) @@ -52,10 +52,9 @@ public class VariableArityTest { com.parse(split); -// config.print(); Assert.assertNotEquals(config.seed, 0); - Assert.assertEquals(config.modelMatrixFile, Arrays.asList(new String[] { "foo" })); - Assert.assertEquals(config.featureFile, Arrays.asList(new String[] { "foo" })); + Assert.assertEquals(config.modelMatrixFile, Arrays.asList("foo")); + Assert.assertEquals(config.featureFile, Arrays.asList("foo")); Assert.assertEquals(config.seed, 1024); Assert.assertEquals(config.outputFile, "foo"); } diff --git a/src/test/java/com/beust/jcommander/args/ArgsEnum.java b/src/test/java/com/beust/jcommander/args/ArgsEnum.java index bef663b..ba0628d 100644 --- a/src/test/java/com/beust/jcommander/args/ArgsEnum.java +++ b/src/test/java/com/beust/jcommander/args/ArgsEnum.java @@ -34,12 +34,12 @@ import com.beust.jcommander.Parameter; */ public class ArgsEnum { - public enum ChoiceType { ONE, Two, THREE }; + public enum ChoiceType { ONE, Two, THREE } @Parameter(names = "-choice") public ChoiceType choice = ChoiceType.ONE; @Parameter(names = "-choices", variableArity = true) - public List<ChoiceType> choices = new ArrayList<ChoiceType>(); + public List<ChoiceType> choices = new ArrayList<>(); public static void main(String[] args1) { ArgsEnum args = new ArgsEnum(); diff --git a/src/test/java/com/beust/jcommander/args/ArgsLongCommandDescription.java b/src/test/java/com/beust/jcommander/args/ArgsLongCommandDescription.java new file mode 100644 index 0000000..398e514 --- /dev/null +++ b/src/test/java/com/beust/jcommander/args/ArgsLongCommandDescription.java @@ -0,0 +1,12 @@ +package com.beust.jcommander.args; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +@Parameters(commandNames = {"command"}, commandDescription = "text text text text text " + + "text text text text text text text text text text text text text text text " + + "really-really-really-long-word-or-url text text text text text text text.") +public class ArgsLongCommandDescription { + @Parameter(names = {"-b"}, description = "boolean parameter") + public boolean var; +} diff --git a/src/test/java/com/beust/jcommander/args/ArgsLongMainParameterDescription.java b/src/test/java/com/beust/jcommander/args/ArgsLongMainParameterDescription.java new file mode 100644 index 0000000..18bb06c --- /dev/null +++ b/src/test/java/com/beust/jcommander/args/ArgsLongMainParameterDescription.java @@ -0,0 +1,17 @@ +package com.beust.jcommander.args; + +import com.beust.jcommander.Parameter; + +import java.util.ArrayList; +import java.util.List; + +public class ArgsLongMainParameterDescription { + + @Parameter(description = "[text] [text] text text text text text text text text " + + "text text text text text text text text " + + "really-really-really-long-word-or-url text text text text text text text.") + public List<String> main = new ArrayList<>(); + + @Parameter(names = {"-b"}, description = "boolean parameter") + public boolean var; +} diff --git a/src/test/java/com/beust/jcommander/args/ArgsMultiValidate.java b/src/test/java/com/beust/jcommander/args/ArgsMultiValidate.java new file mode 100644 index 0000000..1a913ff --- /dev/null +++ b/src/test/java/com/beust/jcommander/args/ArgsMultiValidate.java @@ -0,0 +1,36 @@ +package com.beust.jcommander.args; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import com.beust.jcommander.validators.PositiveInteger; + +public class ArgsMultiValidate { + + public static class OddIntegerParameterValidator implements IParameterValidator { + @Override + public void validate(String name, String value) throws ParameterException { + if(Integer.parseInt(value) %2 != 1) throw new ParameterException("param "+name+"="+value+" is not odd"); + } + } + + public static class LowerThan100ValueValidator implements IValueValidator<Integer> { + @Override + public void validate(String name, Integer value) throws ParameterException { + if(value >= 100) throw new ParameterException("param "+name+"="+value+" is greater than 100"); + } + } + + public static class GreaterTha0ValueValidator implements IValueValidator<Integer> { + @Override + public void validate(String name, Integer value) throws ParameterException { + if(value <= 0) throw new ParameterException("param "+name+"="+value+" is lower than 1"); + } + } + + @Parameter(names = "-age", + validateWith = {PositiveInteger.class,OddIntegerParameterValidator.class}, + validateValueWith={GreaterTha0ValueValidator.class,LowerThan100ValueValidator.class}) + public int age=29; +} diff --git a/src/test/java/com/beust/jcommander/args/HiddenArgs.java b/src/test/java/com/beust/jcommander/args/HiddenArgs.java new file mode 100644 index 0000000..1b89334 --- /dev/null +++ b/src/test/java/com/beust/jcommander/args/HiddenArgs.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander.args; + +import com.beust.jcommander.Parameter; + +public class HiddenArgs { + public HiddenArgs() { + } + + @Parameter(names = "--input") + private String input; + + private String output; + + @Parameter(names = "--output") + private void setOutput(String output) { + this.output = output; + } +} diff --git a/src/test/java/com/beust/jcommander/args/SlashSeparator.java b/src/test/java/com/beust/jcommander/args/SlashSeparator.java index 64d3930..06b38e5 100644 --- a/src/test/java/com/beust/jcommander/args/SlashSeparator.java +++ b/src/test/java/com/beust/jcommander/args/SlashSeparator.java @@ -21,7 +21,7 @@ package com.beust.jcommander.args; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -@Parameters(optionPrefixes = "/") +@Parameters public class SlashSeparator { @Parameter(names = "/verbose") diff --git a/src/test/java/com/beust/jcommander/args/VariableArity.java b/src/test/java/com/beust/jcommander/args/VariableArity.java index 21a861d..ec4130e 100644 --- a/src/test/java/com/beust/jcommander/args/VariableArity.java +++ b/src/test/java/com/beust/jcommander/args/VariableArity.java @@ -8,19 +8,19 @@ import java.util.List; public class VariableArity implements IVariableArity { - private int m_count; + private int count; public VariableArity(int count) { - m_count = count; + this.count = count; } @Parameter - public List<String> main = new ArrayList<String>(); + public List<String> main = new ArrayList<>(); @Parameter(names = "-variable", variableArity = true) - public List<String> var = new ArrayList<String>(); + public List<String> var = new ArrayList<>(); public int processVariableArity(String optionName, String[] options) { - return m_count; + return count; } } diff --git a/src/test/java/com/beust/jcommander/command/CommandNoParametersAnnotation.java b/src/test/java/com/beust/jcommander/command/CommandNoParametersAnnotation.java new file mode 100644 index 0000000..8b5c577 --- /dev/null +++ b/src/test/java/com/beust/jcommander/command/CommandNoParametersAnnotation.java @@ -0,0 +1,9 @@ +package com.beust.jcommander.command; + +import com.beust.jcommander.Parameter; +import java.util.List; + +public class CommandNoParametersAnnotation { + @Parameter(description = "Patterns of files to be added") + public List<String> patterns; +} diff --git a/src/test/java/com/beust/jcommander/command/CommandTest.java b/src/test/java/com/beust/jcommander/command/CommandTest.java index cf921bd..5cb7c34 100644 --- a/src/test/java/com/beust/jcommander/command/CommandTest.java +++ b/src/test/java/com/beust/jcommander/command/CommandTest.java @@ -18,12 +18,13 @@ package com.beust.jcommander.command; +import com.beust.jcommander.ArgsValidate2; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; - import org.testng.Assert; import org.testng.annotations.Test; +import java.io.File; import java.util.Arrays; public class CommandTest { @@ -75,11 +76,6 @@ public class CommandTest { jc.addCommand("commit", commit); jc.parse("-v", "commit", "--amend", "--author=cbeust", "A.java", "B.java"); -// jc.setProgramName("TestCommander"); -// jc.usage(); -// jc.usage("add"); -// jc.usage("commit"); - Assert.assertTrue(cm.verbose); Assert.assertEquals(jc.getParsedCommand(), "commit"); Assert.assertTrue(commit.amend); @@ -109,6 +105,55 @@ public class CommandTest { Assert.assertFalse(out.toString().contains("hidden Hidden command to add file contents to the index")); } + @Test + public void noParametersAnnotationOnCommandTest() { + CommandMain cm = new CommandMain(); + JCommander jc = new JCommander(cm); + CommandNoParametersAnnotation noParametersAnnotation = new CommandNoParametersAnnotation(); + jc.addCommand("no-annotation", noParametersAnnotation); + + jc.setProgramName("TestCommander"); + StringBuilder out = new StringBuilder(); + jc.usage(out); + + Assert.assertTrue(out.toString().contains("no-annotation")); + } + + @Test + public void noTrailingSpaceInUsageTest() { + CommandMain cm = new CommandMain(); + JCommander jc = new JCommander(cm); + CommandAdd add = new CommandAdd(); + jc.addCommand("add", add); + CommandCommit commit = new CommandCommit(); + jc.addCommand("commit", commit); + jc.parse("-v", "commit", "--amend", "--author=cbeust", "A.java", "B.java"); + StringBuilder out = new StringBuilder(); + jc.usage(out); + String firstLine = out.toString().split("\n")[0]; + Assert.assertFalse(firstLine.endsWith(" "), "Usage should not have trailing spaces"); + } + + @Test(expectedExceptions = ParameterException.class) + public void validateSubCommand() throws Exception { + JCommander jc = new JCommander(new CommandMain()); + final ArgsValidate2 sub = new ArgsValidate2(); + sub.template = null; + jc.addCommand("sub", sub); + jc.parse("sub", "-template", "foo"); + } + + @Test + public void doNotValidateSubCommand() throws Exception { + JCommander jc = new JCommander(new CommandMain()); + final ArgsValidate2 sub = new ArgsValidate2(); + sub.template = null; + jc.addCommand("sub", sub); + jc.parseWithoutValidation("sub", "-template", "foo"); + Assert.assertEquals(sub.template, new File("foo")); + + } + public static void main(String[] args) { new CommandTest().shouldComplainIfNoAnnotations(); } diff --git a/src/test/java/com/beust/jcommander/converters/CharArrayConverterTest.java b/src/test/java/com/beust/jcommander/converters/CharArrayConverterTest.java new file mode 100644 index 0000000..596c806 --- /dev/null +++ b/src/test/java/com/beust/jcommander/converters/CharArrayConverterTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander.converters; + +import java.net.UnknownHostException; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class CharArrayConverterTest { + + private static final String FIXTURE = "Hello World"; + private static final CharArrayConverter CONVERTER = new CharArrayConverter(); + + @Test + public void testString() throws UnknownHostException { + Assert.assertEquals(CONVERTER.convert(FIXTURE), FIXTURE.toCharArray()); + } + +} diff --git a/src/test/java/com/beust/jcommander/converters/InetAddressConverterTest.java b/src/test/java/com/beust/jcommander/converters/InetAddressConverterTest.java new file mode 100644 index 0000000..d8cf8b4 --- /dev/null +++ b/src/test/java/com/beust/jcommander/converters/InetAddressConverterTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2010 the original author or authors. + * See the notice.md file distributed with this work for additional + * information regarding copyright ownership. + * + * 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 com.beust.jcommander.converters; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class InetAddressConverterTest { + + private static final InetAddressConverter INET_ADDRESS_CONVERTER = new InetAddressConverter(); + private static final InetAddress LOOPBACK_ADDRESS = InetAddress.getLoopbackAddress(); + + @Test + public void testLocalhost() throws UnknownHostException { + test("localhost"); + } + + @Test + public void testLocalhostAddress() throws UnknownHostException { + test("127.0.0.1"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testGargabeInput() throws UnknownHostException { + test("!@#$%"); + } + + @Test + public void testEmptyInput() throws UnknownHostException { + test(""); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testBlankInput() throws UnknownHostException { + test(" "); + } + + private void test(String string) throws UnknownHostException { + Assert.assertEquals(INET_ADDRESS_CONVERTER.convert(string), LOOPBACK_ADDRESS); + + } +} diff --git a/src/test/java/com/beust/jcommander/dynamic/DynamicParameterTest.java b/src/test/java/com/beust/jcommander/dynamic/DynamicParameterTest.java index 98327bd..73e3c9e 100644 --- a/src/test/java/com/beust/jcommander/dynamic/DynamicParameterTest.java +++ b/src/test/java/com/beust/jcommander/dynamic/DynamicParameterTest.java @@ -51,10 +51,5 @@ public class DynamicParameterTest { public static void main(String[] args) { DynamicParameterTest dpt = new DynamicParameterTest(); dpt.simpleWithSpaces(); -// dpt.nonMapShouldThrow(); -// dpt.wrongSeparatorShouldThrow(); -// dpt.differentAssignment(); -// dpt.arity0(); -// dpt.usage(); } } diff --git a/src/test/java/test/QuotedMainTest.java b/src/test/java/test/QuotedMainTest.java index abb97c0..68126ea 100644 --- a/src/test/java/test/QuotedMainTest.java +++ b/src/test/java/test/QuotedMainTest.java @@ -11,7 +11,7 @@ import com.beust.jcommander.Parameter; public class QuotedMainTest { @Parameter - List<String> args = new ArrayList<String>(); + List<String> args = new ArrayList<>(); String quoted = "\" \""; |