You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2017/10/24 04:14:04 UTC

[4/5] logging-log4j2 git commit: LOG4J2-2088 Upgrade picocli to 2.0 from 0.9.8

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/82879764/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
index 7f51af3..ed6cec1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
@@ -16,7 +16,6 @@
  */
 package org.apache.logging.log4j.core.tools.picocli;
 
-import java.awt.Point;
 import java.io.File;
 import java.io.PrintStream;
 import java.lang.annotation.ElementType;
@@ -26,6 +25,9 @@ import java.lang.annotation.Target;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.InetAddress;
@@ -49,17 +51,18 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
-import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.Stack;
-import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.UUID;
+import java.util.concurrent.Callable;
 import java.util.regex.Pattern;
 
 import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle;
@@ -78,7 +81,8 @@ import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Colum
  * </p><h2>Example</h2>
  * <pre>import static picocli.CommandLine.*;
  *
- * &#064;Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.")
+ * &#064;Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
+ *          version = "v1.2.3")
  * public class Encrypt {
  *
  *     &#064;Parameters(type = File.class, description = "Any number of input files")
@@ -90,24 +94,26 @@ import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Colum
  *     &#064;Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
  *     private boolean verbose;
  *
- *     &#064;Option(names = { "-h", "--help", "-?", "-help"}, help = true, description = "Display this help and exit")
+ *     &#064;Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
  *     private boolean help;
+ *
+ *     &#064;Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
+ *     private boolean versionHelp;
  * }
  * </pre>
  * <p>
  * Use {@code CommandLine} to initialize a domain object as follows:
  * </p><pre>
  * public static void main(String... args) {
+ *     Encrypt encrypt = new Encrypt();
  *     try {
- *         Encrypt encrypt = CommandLine.populateCommand(new Encrypt(), args);
- *         if (encrypt.help) {
- *             CommandLine.usage(encrypt, System.out);
- *         } else {
+ *         List&lt;CommandLine&gt; parsedCommands = new CommandLine(encrypt).parse(args);
+ *         if (!CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
  *             runProgram(encrypt);
  *         }
  *     } catch (ParameterException ex) { // command line arguments could not be parsed
  *         System.err.println(ex.getMessage());
- *         CommandLine.usage(new Encrypt(), System.err);
+ *         ex.getCommandLine().usage(System.err);
  *     }
  * }
  * </pre><p>
@@ -124,18 +130,14 @@ import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Colum
  * -v -ooutfile in1 in2
  * -vooutfile in1 in2
  * </pre>
- *
- * <p>
- * Copied and modified from <a href="http://github.com/remkop/picocli/">picocli</a>.
- * </p>
- *
- * @since 2.9
  */
 public class CommandLine {
     /** This is picocli version {@value}. */
-    public static final String VERSION = "0.9.8";
+    public static final String VERSION = "2.0.0";
 
+    private final Tracer tracer = new Tracer();
     private final Interpreter interpreter;
+    private String commandName = Help.DEFAULT_COMMAND_NAME;
     private boolean overwrittenOptionsAllowed = false;
     private boolean unmatchedArgumentsAllowed = false;
     private List<String> unmatchedArguments = new ArrayList<String>();
@@ -149,7 +151,7 @@ public class CommandLine {
      * When the {@link #parse(String...)} method is called, fields of the specified object that are annotated
      * with {@code @Option} or {@code @Parameters} will be initialized based on command line arguments.
      * @param command the object to initialize from the command line arguments
-     * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
      */
     public CommandLine(Object command) {
         interpreter = new Interpreter(command);
@@ -186,6 +188,7 @@ public class CommandLine {
      * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
      * To ensure a custom type converter is available to all subcommands, register the type converter last, after
      * adding subcommands.</p>
+     * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
      *
      * @param name the string to recognize on the command line as a subcommand
      * @param command the object to initialize with command line arguments following the subcommand name.
@@ -193,6 +196,7 @@ public class CommandLine {
      * @return this CommandLine object, to allow method chaining
      * @see #registerConverter(Class, ITypeConverter)
      * @since 0.9.7
+     * @see Command#subcommands()
      */
     public CommandLine addSubcommand(String name, Object command) {
         CommandLine commandLine = toCommandLine(command);
@@ -218,21 +222,23 @@ public class CommandLine {
         return parent;
     }
 
-    /**
-     * Returns the annotated object that this {@code CommandLine} instance was constructed with.
+    /** Returns the annotated object that this {@code CommandLine} instance was constructed with.
+     * @param <T> the type of the variable that the return value is being assigned to
      * @return the annotated object that this {@code CommandLine} instance was constructed with
      * @since 0.9.7
      */
-    public Object getCommand() {
-        return interpreter.command;
+    public <T> T getCommand() {
+        return (T) interpreter.command;
     }
 
     /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
-     * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}  */
+     * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
+     * @since 0.9.8 */
     public boolean isUsageHelpRequested() { return usageHelpRequested; }
 
     /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
-     * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}  */
+     * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
+     * @since 0.9.8 */
     public boolean isVersionHelpRequested() { return versionHelpRequested; }
 
     /** Returns whether options for single-value fields can be specified multiple times on the command line.
@@ -278,9 +284,10 @@ public class CommandLine {
      * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
      * later will have the default setting. To ensure a setting is applied to all
      * subcommands, call the setter last, after adding subcommands.</p>
-     * @param newValue the new setting
+     * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
      * @return this {@code CommandLine} object, to allow method chaining
      * @since 0.9.7
+     * @see #getUnmatchedArguments()
      */
     public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) {
         this.unmatchedArgumentsAllowed = newValue;
@@ -315,7 +322,7 @@ public class CommandLine {
      * @param args the command line arguments to parse
      * @param <T> the type of the annotated object
      * @return the specified annotated object
-     * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
      * @throws ParameterException if the specified command line arguments are invalid
      * @since 0.9.7
      */
@@ -325,21 +332,332 @@ public class CommandLine {
         return command;
     }
 
-    /**
+    /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
+     * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
      * <p>
-     * Initializes the annotated object that this {@code CommandLine} was constructed with as well as
-     * possibly any registered commands, based on the specified command line arguments,
-     * and returns a list of all commands and subcommands that were initialized by this method.
+     * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
+     * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
+     * and these subcommands were initialized by matching command line arguments. If parsing fails, a
+     * {@link ParameterException} is thrown.
      * </p>
      *
      * @param args the command line arguments to parse
-     * @return a list with all commands and subcommands initialized by this method
-     * @throws ParameterException if the specified command line arguments are invalid
+     * @return a list with the top-level command and any subcommands initialized by this method
+     * @throws ParameterException if the specified command line arguments are invalid; use
+     *      {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
      */
     public List<CommandLine> parse(String... args) {
         return interpreter.parse(args);
     }
-
+    /**
+     * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
+     * {@linkplain #parse(String...) parsing} the command line arguments. This is a
+     * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
+     * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
+     * <p>
+     * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
+     * methods to take some next step after the command line was successfully parsed.
+     * </p>
+     * @see RunFirst
+     * @see RunLast
+     * @see RunAll
+     * @since 2.0 */
+    public static interface IParseResultHandler {
+        /** Processes a List of {@code CommandLine} objects resulting from successfully
+         * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
+         * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
+         * @param out the {@code PrintStream} to print help to if requested
+         * @param ansi for printing help messages using ANSI styles and colors
+         * @return a list of results, or an empty list if there are no results
+         * @throws ExecutionException if a problem occurred while processing the parse results; use
+         *      {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+         */
+        List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException;
+    }
+    /**
+     * Represents a function that can handle a {@code ParameterException} that occurred while
+     * {@linkplain #parse(String...) parsing} the command line arguments. This is a
+     * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
+     * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
+     * <p>
+     * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
+     * methods to handle situations when the command line could not be parsed.
+     * </p>
+     * @see DefaultExceptionHandler
+     * @since 2.0 */
+    public static interface IExceptionHandler {
+        /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
+         * line arguments and optionally returns a list of results.
+         * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
+         *           and the CommandLine representing the command or subcommand whose input was invalid
+         * @param out the {@code PrintStream} to print help to if requested
+         * @param ansi for printing help messages using ANSI styles and colors
+         * @param args the command line arguments that could not be parsed
+         * @return a list of results, or an empty list if there are no results
+         */
+        List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
+    }
+    /**
+     * Default exception handler that prints the exception message to the specified {@code PrintStream}, followed by the
+     * usage message for the command or subcommand whose input was invalid.
+     * <p>Implementation roughly looks like this:</p>
+     * <pre>
+     *     System.err.println(paramException.getMessage());
+     *     paramException.getCommandLine().usage(System.err);
+     * </pre>
+     * @since 2.0 */
+    public static class DefaultExceptionHandler implements IExceptionHandler {
+        @Override
+        public List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) {
+            out.println(ex.getMessage());
+            ex.getCommandLine().usage(out, ansi);
+            return Collections.emptyList();
+        }
+    }
+    /**
+     * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
+     * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
+     * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
+     * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
+     * and returns {@code true}. Otherwise, if none of the specified {@code CommandLine} objects have help requested,
+     * this method returns {@code false}.
+     * <p>
+     * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
+     * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
+     * </p>
+     * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
+     * @param out the {@code PrintStream} to print help to if requested
+     * @param ansi for printing help messages using ANSI styles and colors
+     * @return {@code true} if help was printed, {@code false} otherwise
+     * @since 2.0 */
+    public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
+        for (CommandLine parsed : parsedCommands) {
+            if (parsed.isUsageHelpRequested()) {
+                parsed.usage(out, ansi);
+                return true;
+            } else if (parsed.isVersionHelpRequested()) {
+                parsed.printVersionHelp(out, ansi);
+                return true;
+            }
+        }
+        return false;
+    }
+    private static Object execute(CommandLine parsed) {
+        Object command = parsed.getCommand();
+        if (command instanceof Runnable) {
+            try {
+                ((Runnable) command).run();
+                return null;
+            } catch (Exception ex) {
+                throw new ExecutionException(parsed, "Error while running command (" + command + ")", ex);
+            }
+        } else if (command instanceof Callable) {
+            try {
+                return ((Callable<Object>) command).call();
+            } catch (Exception ex) {
+                throw new ExecutionException(parsed, "Error while calling command (" + command + ")", ex);
+            }
+        }
+        throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Runnable or Callable");
+    }
+    /**
+     * Command line parse result handler that prints help if requested, and otherwise executes the top-level
+     * {@code Runnable} or {@code Callable} command.
+     * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
+     * <p>
+     * From picocli v2.0, {@code RunFirst} is used to implement the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
+     * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} convenience methods.
+     * </p>
+     * @since 2.0 */
+    public static class RunFirst implements IParseResultHandler {
+        /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
+         * If the top-level command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
+         * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
+         *
+         * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
+         * @param out the {@code PrintStream} to print help to if requested
+         * @param ansi for printing help messages using ANSI styles and colors
+         * @return an empty list if help was requested, or a list containing a single element: the result of calling the
+         *      {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
+         * @throws ExecutionException if a problem occurred while processing the parse results; use
+         *      {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+         */
+        public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
+            if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); }
+            return Arrays.asList(execute(parsedCommands.get(0)));
+        }
+    }
+    /**
+     * Command line parse result handler that prints help if requested, and otherwise executes the most specific
+     * {@code Runnable} or {@code Callable} subcommand.
+     * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
+     * <p>
+     * Something like this:</p>
+     * <pre>
+     *     // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
+     *     if (CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
+     *         return emptyList();
+     *     }
+     *     CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
+     *     Object command = last.getCommand();
+     *     if (command instanceof Runnable) {
+     *         try {
+     *             ((Runnable) command).run();
+     *         } catch (Exception ex) {
+     *             throw new ExecutionException(last, "Error in runnable " + command, ex);
+     *         }
+     *     } else if (command instanceof Callable) {
+     *         Object result;
+     *         try {
+     *             result = ((Callable) command).call();
+     *         } catch (Exception ex) {
+     *             throw new ExecutionException(last, "Error in callable " + command, ex);
+     *         }
+     *         // ...do something with result
+     *     } else {
+     *         throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
+     *     }
+     * </pre>
+     * @since 2.0 */
+    public static class RunLast implements IParseResultHandler {
+        /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
+         * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
+         * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
+         *
+         * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
+         * @param out the {@code PrintStream} to print help to if requested
+         * @param ansi for printing help messages using ANSI styles and colors
+         * @return an empty list if help was requested, or a list containing a single element: the result of calling the
+         *      {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
+         * @throws ExecutionException if a problem occurred while processing the parse results; use
+         *      {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+         */
+        public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
+            if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); }
+            CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
+            return Arrays.asList(execute(last));
+        }
+    }
+    /**
+     * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
+     * all subcommands as {@code Runnable} or {@code Callable}.
+     * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
+     * @since 2.0 */
+    public static class RunAll implements IParseResultHandler {
+        /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
+         * or {@code Callable}. If any of the {@code CommandLine} commands does not implement either
+         * {@code Runnable} or {@code Callable}, a {@code ExecutionException}
+         * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
+         *
+         * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
+         * @param out the {@code PrintStream} to print help to if requested
+         * @param ansi for printing help messages using ANSI styles and colors
+         * @return an empty list if help was requested, or a list containing the result of executing all commands:
+         *      the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
+         * @throws ExecutionException if a problem occurred while processing the parse results; use
+         *      {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+         */
+        public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
+            if (printHelpIfRequested(parsedCommands, out, ansi)) {
+                return null;
+            }
+            List<Object> result = new ArrayList<Object>();
+            for (CommandLine parsed : parsedCommands) {
+                result.add(execute(parsed));
+            }
+            return result;
+        }
+    }
+    /**
+     * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)}
+     * with {@code Help.Ansi.AUTO} and a new {@link DefaultExceptionHandler} in addition to the specified parse result handler,
+     * {@code PrintStream}, and the specified command line arguments.
+     * <p>
+     * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
+     * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
+     * support for nested subcommands.
+     * </p>
+     * <p>Calling this method roughly expands to:</p>
+     * <pre>
+     * try {
+     *     List&lt;CommandLine&gt; parsedCommands = parse(args);
+     *     return parseResultsHandler.handleParseResult(parsedCommands, out, Help.Ansi.AUTO);
+     * } catch (ParameterException ex) {
+     *     return new DefaultExceptionHandler().handleException(ex, out, ansi, args);
+     * }
+     * </pre>
+     * <p>
+     * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
+     * The following handlers are available:</p>
+     * <ul>
+     *   <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
+     * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
+     *   <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
+     *   <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
+     *   <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
+     * </ul>
+     * @param handler the function that will process the result of successfully parsing the command line arguments
+     * @param out the {@code PrintStream} to print help to if requested
+     * @param args the command line arguments
+     * @return a list of results, or an empty list if there are no results
+     * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
+     *      parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+     * @see RunLast
+     * @see RunAll
+     * @since 2.0 */
+    public List<Object> parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) {
+        return parseWithHandlers(handler, out, Help.Ansi.AUTO, new DefaultExceptionHandler(), args);
+    }
+    /**
+     * Tries to {@linkplain #parse(String...) parse} the specified command line arguments, and if successful, delegates
+     * the processing of the resulting list of {@code CommandLine} objects to the specified {@linkplain IParseResultHandler handler}.
+     * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
+     * is caught and passed to the specified {@link IExceptionHandler}.
+     * <p>
+     * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
+     * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
+     * support for nested subcommands.
+     * </p>
+     * <p>Calling this method roughly expands to:</p>
+     * <pre>
+     * try {
+     *     List&lt;CommandLine&gt; parsedCommands = parse(args);
+     *     return parseResultsHandler.handleParseResult(parsedCommands, out, ansi);
+     * } catch (ParameterException ex) {
+     *     return new exceptionHandler.handleException(ex, out, ansi, args);
+     * }
+     * </pre>
+     * <p>
+     * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
+     * The following handlers are available:</p>
+     * <ul>
+     *   <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
+     * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
+     *   <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
+     *   <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
+     *   <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
+     * </ul>
+     *
+     * @param handler the function that will process the result of successfully parsing the command line arguments
+     * @param out the {@code PrintStream} to print help to if requested
+     * @param ansi for printing help messages using ANSI styles and colors
+     * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
+     * @param args the command line arguments
+     * @return a list of results produced by the {@code IParseResultHandler} or the {@code IExceptionHandler}, or an empty list if there are no results
+     * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
+     *      result {@code CommandLine} objects; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
+     * @see RunLast
+     * @see RunAll
+     * @see DefaultExceptionHandler
+     * @since 2.0 */
+    public List<Object> parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) {
+        try {
+            List<CommandLine> result = parse(args);
+            return handler.handleParseResult(result, out, ansi);
+        } catch (ParameterException ex) {
+            return exceptionHandler.handleException(ex, out, ansi, args);
+        }
+    }
     /**
      * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
      * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
@@ -426,6 +744,13 @@ public class CommandLine {
      */
     public void usage(PrintStream out, Help.ColorScheme colorScheme) {
         Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands());
+        if (!Help.DEFAULT_SEPARATOR.equals(getSeparator())) {
+            help.separator = getSeparator();
+            help.parameterLabelRenderer = help.createDefaultParamLabelRenderer(); // update for new separator
+        }
+        if (!Help.DEFAULT_COMMAND_NAME.equals(getCommandName())) {
+            help.commandName = getCommandName();
+        }
         StringBuilder sb = new StringBuilder()
                 .append(help.headerHeading())
                 .append(help.header())
@@ -448,6 +773,7 @@ public class CommandLine {
      * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
      * @param out the printStream to print to
      * @see #printVersionHelp(PrintStream, Help.Ansi)
+     * @since 0.9.8
      */
     public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); }
 
@@ -460,57 +786,169 @@ public class CommandLine {
      * @see Command#version()
      * @see Option#versionHelp()
      * @see #isVersionHelpRequested()
+     * @since 0.9.8
      */
     public void printVersionHelp(PrintStream out, Help.Ansi ansi) {
         for (String versionInfo : versionLines) {
             out.println(ansi.new Text(versionInfo));
         }
     }
+    /**
+     * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
+     * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
+     * specified parameters, and printed on a separate line. Both version strings and parameters may contain
+     * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
+     * @param out the printStream to print to
+     * @param ansi whether the usage message should include ANSI escape codes or not
+     * @param params Arguments referenced by the format specifiers in the version strings
+     * @see Command#version()
+     * @see Option#versionHelp()
+     * @see #isVersionHelpRequested()
+     * @since 1.0.0
+     */
+    public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) {
+        for (String versionInfo : versionLines) {
+            out.println(ansi.new Text(String.format(versionInfo, params)));
+        }
+    }
+
+    /**
+     * Delegates to {@link #call(Callable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
+     * <p>
+     * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
+     * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
+     * </p>
+     * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
+     * @param out the printStream to print to
+     * @param args the command line arguments to parse
+     * @param <C> the annotated object must implement Callable
+     * @param <T> the return type of the most specific command (must implement {@code Callable})
+     * @see #call(Callable, PrintStream, Help.Ansi, String...)
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws ExecutionException if the Callable throws an exception
+     * @return {@code null} if an error occurred while parsing the command line options, otherwise returns the result of calling the Callable
+     * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
+     * @see RunFirst
+     */
+    public static <C extends Callable<T>, T> T call(C callable, PrintStream out, String... args) {
+        return call(callable, out, Help.Ansi.AUTO, args);
+    }
+    /**
+     * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
+     * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
+     * <pre>
+     * CommandLine cmd = new CommandLine(callable);
+     * List&lt;CommandLine&gt; parsedCommands;
+     * try {
+     *     parsedCommands = cmd.parse(args);
+     * } catch (ParameterException ex) {
+     *     out.println(ex.getMessage());
+     *     cmd.usage(out, ansi);
+     *     return null;
+     * }
+     * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
+     *     return null;
+     * }
+     * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
+     * try {
+     *     Callable&lt;Object&gt; subcommand = last.getCommand();
+     *     return subcommand.call();
+     * } catch (Exception ex) {
+     *     throw new ExecutionException(last, "Error calling " + last.getCommand(), ex);
+     * }
+     * </pre>
+     * <p>
+     * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
+     * command line is executed.
+     * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
+     * method with a {@link RunAll} handler or a custom handler.
+     * </p><p>
+     * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
+     * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
+     * </p>
+     * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
+     * @param out the printStream to print to
+     * @param ansi whether the usage message should include ANSI escape codes or not
+     * @param args the command line arguments to parse
+     * @param <C> the annotated object must implement Callable
+     * @param <T> the return type of the specified {@code Callable}
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws ExecutionException if the Callable throws an exception
+     * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
+     * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
+     * @see RunLast
+     */
+    public static <C extends Callable<T>, T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) {
+        CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch
+        List<Object> results = cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
+        return results == null || results.isEmpty() ? null : (T) results.get(0);
+    }
 
     /**
      * Delegates to {@link #run(Runnable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
-     * @param command the command to run when {@linkplain #populateCommand(Object, String...) parsing} succeeds.
+     * <p>
+     * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
+     * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
+     * </p>
+     * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
      * @param out the printStream to print to
      * @param args the command line arguments to parse
      * @param <R> the annotated object must implement Runnable
      * @see #run(Runnable, PrintStream, Help.Ansi, String...)
-     * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws ExecutionException if the Runnable throws an exception
+     * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
+     * @see RunFirst
      */
-    public static <R extends Runnable> void run(R command, PrintStream out, String... args) {
-        run(command, out, Help.Ansi.AUTO, args);
+    public static <R extends Runnable> void run(R runnable, PrintStream out, String... args) {
+        run(runnable, out, Help.Ansi.AUTO, args);
     }
     /**
      * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
      * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
      * <pre>
-     * CommandLine cmd = new CommandLine(command);
+     * CommandLine cmd = new CommandLine(runnable);
+     * List&lt;CommandLine&gt; parsedCommands;
      * try {
-     *     cmd.parse(args);
-     * } catch (Exception ex) {
-     *     System.err.println(ex.getMessage());
+     *     parsedCommands = cmd.parse(args);
+     * } catch (ParameterException ex) {
+     *     out.println(ex.getMessage());
      *     cmd.usage(out, ansi);
-     *     return;
+     *     return null;
+     * }
+     * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
+     *     return null;
+     * }
+     * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
+     * try {
+     *     Runnable subcommand = last.getCommand();
+     *     subcommand.run();
+     * } catch (Exception ex) {
+     *     throw new ExecutionException(last, "Error running " + last.getCommand(), ex);
      * }
-     * command.run();
      * </pre>
-     * Note that this method is not suitable for commands with subcommands.
-     * @param command the command to run when {@linkplain #populateCommand(Object, String...) parsing} succeeds.
+     * <p>
+     * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
+     * command line is executed.
+     * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
+     * method with a {@link RunAll} handler or a custom handler.
+     * </p><p>
+     * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
+     * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
+     * </p>
+     * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
      * @param out the printStream to print to
      * @param ansi whether the usage message should include ANSI escape codes or not
      * @param args the command line arguments to parse
      * @param <R> the annotated object must implement Runnable
-     * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
+     * @throws ExecutionException if the Runnable throws an exception
+     * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
+     * @see RunLast
      */
-    public static <R extends Runnable> void run(R command, PrintStream out, Help.Ansi ansi, String... args) {
-        CommandLine cmd = new CommandLine(command); // validate command outside of try-catch
-        try {
-            cmd.parse(args);
-        } catch (Exception ex) {
-            out.println(ex.getMessage());
-            cmd.usage(out, ansi);
-            return;
-        }
-        command.run();
+    public static <R extends Runnable> void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) {
+        CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch
+        cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
     }
 
     /**
@@ -564,16 +1002,35 @@ public class CommandLine {
         return this;
     }
 
-    /** Returns the String that separates option names from option values when parsing command line options. {@code '='} by default.
+    /** Returns the String that separates option names from option values when parsing command line options. {@value Help#DEFAULT_SEPARATOR} by default.
      * @return the String the parser uses to separate option names from option values */
     public String getSeparator() {
         return interpreter.separator;
     }
 
     /** Sets the String the parser uses to separate option names from option values to the specified value.
-     * @param separator the String that separates option names from option values */
-    public void setSeparator(String separator) {
+     * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
+     * @param separator the String that separates option names from option values
+     * @return this {@code CommandLine} object, to allow method chaining */
+    public CommandLine setSeparator(String separator) {
         interpreter.separator = Assert.notNull(separator, "separator");
+        return this;
+    }
+
+    /** Returns the command name (also called program name) displayed in the usage help synopsis. {@value Help#DEFAULT_COMMAND_NAME} by default.
+     * @return the command name (also called program name) displayed in the usage */
+    public String getCommandName() {
+        return commandName;
+    }
+
+    /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
+     * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
+     * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
+     * @param commandName command name (also called program name) displayed in the usage help synopsis
+     * @return this {@code CommandLine} object, to allow method chaining */
+    public CommandLine setCommandName(String commandName) {
+        this.commandName = Assert.notNull(commandName, "commandName");
+        return this;
     }
     private static boolean empty(String str) { return str == null || str.trim().length() == 0; }
     private static boolean empty(Object[] array) { return array == null || array.length == 0; }
@@ -581,6 +1038,35 @@ public class CommandLine {
     private static String str(String[] arr, int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; }
     private static boolean isBoolean(Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; }
     private static CommandLine toCommandLine(Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);}
+    private static boolean isMultiValue(Field field) {  return isMultiValue(field.getType()); }
+    private static boolean isMultiValue(Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); }
+    private static Class<?>[] getTypeAttribute(Field field) {
+        Class<?>[] explicit = field.isAnnotationPresent(Parameters.class) ? field.getAnnotation(Parameters.class).type() : field.getAnnotation(Option.class).type();
+        if (explicit.length > 0) { return explicit; }
+        if (field.getType().isArray()) { return new Class<?>[] { field.getType().getComponentType() }; }
+        if (isMultiValue(field)) {
+            Type type = field.getGenericType(); // e.g. Map<Long, ? extends Number>
+            if (type instanceof ParameterizedType) {
+                ParameterizedType parameterizedType = (ParameterizedType) type;
+                Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
+                Class<?>[] result = new Class<?>[paramTypes.length];
+                for (int i = 0; i < paramTypes.length; i++) {
+                    if (paramTypes[i] instanceof Class) { result[i] = (Class<?>) paramTypes[i]; continue; } // e.g. Long
+                    if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
+                        WildcardType wildcardType = (WildcardType) paramTypes[i];
+                        Type[] lower = wildcardType.getLowerBounds(); // e.g. []
+                        if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; }
+                        Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
+                        if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class<?>) upper[0]; continue; }
+                    }
+                    Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up
+                }
+                return result; // we inferred all types from ParameterizedType
+            }
+            return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
+        }
+        return new Class<?>[] {field.getType()}; // not a multi-value field
+    }
     /**
      * <p>
      * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
@@ -600,10 +1086,10 @@ public class CommandLine {
      *     &#064;Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
      *     private boolean verbose;
      *
-     *     &#064;Option(names = { "-h", "--help", "-?", "-help"}, help = true, description = "Display this help and exit")
+     *     &#064;Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
      *     private boolean help;
      *
-     *     &#064;Option(names = { "-V", "--version"}, help = true, description = "Display version information and exit")
+     *     &#064;Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version information and exit")
      *     private boolean version;
      * }
      * </pre>
@@ -681,6 +1167,7 @@ public class CommandLine {
          * and take the appropriate action.
          * </p>
          * @return whether this option disables validation of the other arguments
+         * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
          */
         boolean help() default false;
 
@@ -698,6 +1185,7 @@ public class CommandLine {
          * and take the appropriate action.
          * </p>
          * @return whether this option allows the user to request usage help
+         * @since 0.9.8
          */
         boolean usageHelp() default false;
 
@@ -715,6 +1203,7 @@ public class CommandLine {
          * and take the appropriate action.
          * </p>
          * @return whether this option allows the user to request version information
+         * @since 0.9.8
          */
         boolean versionHelp() default false;
 
@@ -783,22 +1272,34 @@ public class CommandLine {
          */
         String paramLabel() default "";
 
-        /**
-         * <p>
-         * Specify a {@code type} if the annotated field is a {@code Collection} that should hold objects other than Strings.
+        /** <p>
+         * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
+         * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
+         * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
+         * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
          * </p><p>
-         * If the field's type is a {@code Collection}, the generic type parameter of the collection is erased and
-         * cannot be determined at runtime. Specify a {@code type} attribute to store values other than String in
-         * the Collection. Picocli will use the {@link ITypeConverter}
-         * that is {@linkplain #registerConverter(Class, ITypeConverter) registered} for that type to convert
-         * the raw String values before they are added to the collection.
+         * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
+         * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
+         * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
          * </p><p>
-         * When the field's type is an array, the {@code type} attribute is ignored: the values will be converted
-         * to the array component type and the array will be replaced with a new instance containing both the old and
-         * the new values. </p>
-         * @return the type to convert the raw String values to before adding them to the Collection
+         * Picocli will use the {@link ITypeConverter} that is
+         * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
+         * the raw String values before modifying the field value.
+         * </p><p>
+         * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
+         * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
+         * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
+         * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
+         * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
+         * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
+         * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
+         * </p><p>
+         * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
+         * or if the generic type's type arguments are interfaces or abstract classes, you may
+         * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
+         * @return the type(s) to convert the raw String values
          */
-        Class<?> type() default String.class;
+        Class<?>[] type() default {};
 
         /**
          * Specify a regular expression to use to split option parameter values before applying them to the field.
@@ -881,20 +1382,33 @@ public class CommandLine {
 
         /**
          * <p>
-         * Specify a {@code type} if the annotated field is a {@code Collection} that should hold objects other than Strings.
+         * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
+         * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
+         * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
+         * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
+         * </p><p>
+         * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
+         * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
+         * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
          * </p><p>
-         * If the field's type is a {@code Collection}, the generic type parameter of the collection is erased and
-         * cannot be determined at runtime. Specify a {@code type} attribute to store values other than String in
-         * the Collection. Picocli will use the {@link ITypeConverter}
-         * that is {@linkplain #registerConverter(Class, ITypeConverter) registered} for that type to convert
-         * the raw String values before they are added to the collection.
+         * Picocli will use the {@link ITypeConverter} that is
+         * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
+         * the raw String values before modifying the field value.
          * </p><p>
-         * When the field's type is an array, the {@code type} attribute is ignored: the values will be converted
-         * to the array component type and the array will be replaced with a new instance containing both the old and
-         * the new values. </p>
-         * @return the type to convert the raw String values to before adding them to the Collection
+         * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
+         * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
+         * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
+         * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
+         * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
+         * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
+         * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
+         * </p><p>
+         * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
+         * or if the generic type's type arguments are interfaces or abstract classes, you may
+         * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
+         * @return the type(s) to convert the raw String values
          */
-        Class<?> type() default String.class;
+        Class<?>[] type() default {};
 
         /**
          * Specify a regular expression to use to split positional parameter values before applying them to the field.
@@ -936,7 +1450,7 @@ public class CommandLine {
      *   <li>[footer]</li>
      * </ul> */
     @Retention(RetentionPolicy.RUNTIME)
-    @Target(ElementType.TYPE)
+    @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.PACKAGE})
     public @interface Command {
         /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
          * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
@@ -1174,19 +1688,41 @@ public class CommandLine {
                     : new Range(0, 0, false, true, "0");
         }
         static Range adjustForType(Range result, Field field) {
-            return result.isUnspecified ? defaultArity(field.getType()) : result;
+            return result.isUnspecified ? defaultArity(field) : result;
         }
-        /** Returns a new {@code Range} based on the specified type: booleans have arity 0, arrays or Collections have
+        /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
+         * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
          * arity "0..*", and other types have arity 1.
+         * @param field the field whose default arity to return
+         * @return a new {@code Range} indicating the default arity of the specified field
+         * @since 2.0 */
+        public static Range defaultArity(Field field) {
+            Class<?> type = field.getType();
+            if (field.isAnnotationPresent(Option.class)) {
+                return defaultArity(type);
+            }
+            if (isMultiValue(type)) {
+                return Range.valueOf("0..1");
+            }
+            return Range.valueOf("1");// for single-valued fields (incl. boolean positional parameters)
+        }
+        /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
          * @param type the type whose default arity to return
          * @return a new {@code Range} indicating the default arity of the specified type */
         public static Range defaultArity(Class<?> type) {
-            if (isBoolean(type)) {
-                return Range.valueOf("0");
-            } else if (type.isArray() || Collection.class.isAssignableFrom(type)) {
-                return Range.valueOf("0..*");
-            }
-            return Range.valueOf("1");// for single-valued fields
+            return isBoolean(type) ? Range.valueOf("0") : Range.valueOf("1");
+        }
+        private int size() { return 1 + max - min; }
+        static Range parameterCapacity(Field field) {
+            Range arity = parameterArity(field);
+            if (!isMultiValue(field)) { return arity; }
+            Range index = parameterIndex(field);
+            if (arity.max == 0)    { return arity; }
+            if (index.size() == 1) { return arity; }
+            if (index.isVariable)  { return Range.valueOf(arity.min + "..*"); }
+            if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); }
+            if (arity.isVariable)  { return Range.valueOf(arity.min * index.size() + "..*"); }
+            return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
         }
         /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
          * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
@@ -1231,6 +1767,13 @@ public class CommandLine {
          * @return a new Range object with the specified {@code max} value */
         public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); }
 
+        /**
+         * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
+         * @param value the value to check
+         * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
+         */
+        public boolean contains(int value) { return min <= value && max >= value; }
+
         public boolean equals(Object object) {
             if (!(object instanceof Range)) { return false; }
             Range other = (Range) object;
@@ -1247,11 +1790,11 @@ public class CommandLine {
             return (result == 0) ? max - other.max : result;
         }
     }
-    private static void init(Class<?> cls,
-                             List<Field> requiredFields,
-                             Map<String, Field> optionName2Field,
-                             Map<Character, Field> singleCharOption2Field,
-                             List<Field> positionalParametersFields) {
+    static void init(Class<?> cls,
+                     List<Field> requiredFields,
+                     Map<String, Field> optionName2Field,
+                     Map<Character, Field> singleCharOption2Field,
+                     List<Field> positionalParametersFields) {
         Field[] declaredFields = cls.getDeclaredFields();
         for (Field field : declaredFields) {
             field.setAccessible(true);
@@ -1276,7 +1819,7 @@ public class CommandLine {
             }
             if (field.isAnnotationPresent(Parameters.class)) {
                 if (field.isAnnotationPresent(Option.class)) {
-                    throw new ParameterException("A field can be either @Option or @Parameters, but '"
+                    throw new DuplicateOptionAnnotationsException("A field can be either @Option or @Parameters, but '"
                             + field.getName() + "' is both.");
                 }
                 positionalParametersFields.add(field);
@@ -1315,7 +1858,8 @@ public class CommandLine {
         private final List<Field> positionalParametersFields             = new ArrayList<Field>();
         private final Object command;
         private boolean isHelpRequested;
-        private String separator = "=";
+        private String separator = Help.DEFAULT_SEPARATOR;
+        private int position;
 
         Interpreter(Object command) {
             converterRegistry.put(Path.class,          new BuiltIn.PathConverter());
@@ -1350,9 +1894,10 @@ public class CommandLine {
             converterRegistry.put(Pattern.class,       new BuiltIn.PatternConverter());
             converterRegistry.put(UUID.class,          new BuiltIn.UUIDConverter());
 
-            this.command             = Assert.notNull(command, "command");
-            Class<?> cls             = command.getClass();
-            String declaredSeparator = null;
+            this.command                 = Assert.notNull(command, "command");
+            Class<?> cls                 = command.getClass();
+            String declaredName          = null;
+            String declaredSeparator     = null;
             boolean hasCommandAnnotation = false;
             while (cls != null) {
                 init(cls, requiredFields, optionName2Field, singleCharOption2Field, positionalParametersFields);
@@ -1360,12 +1905,13 @@ public class CommandLine {
                     hasCommandAnnotation = true;
                     Command cmd = cls.getAnnotation(Command.class);
                     declaredSeparator = (declaredSeparator == null) ? cmd.separator() : declaredSeparator;
+                    declaredName = (declaredName == null) ? cmd.name() : declaredName;
                     CommandLine.this.versionLines.addAll(Arrays.asList(cmd.version()));
 
                     for (Class<?> sub : cmd.subcommands()) {
                         Command subCommand = sub.getAnnotation(Command.class);
                         if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
-                            throw new IllegalArgumentException("Subcommand " + sub.getName() +
+                            throw new InitializationException("Subcommand " + sub.getName() +
                                     " is missing the mandatory @Command annotation with a 'name' attribute");
                         }
                         try {
@@ -1375,11 +1921,11 @@ public class CommandLine {
                             commandLine.parent = CommandLine.this;
                             commands.put(subCommand.name(), commandLine);
                         }
-                        catch (IllegalArgumentException ex) { throw ex; }
-                        catch (NoSuchMethodException ex) { throw new IllegalArgumentException("Cannot instantiate subcommand " +
+                        catch (InitializationException ex) { throw ex; }
+                        catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " +
                                 sub.getName() + ": the class has no constructor", ex); }
                         catch (Exception ex) {
-                            throw new IllegalStateException("Could not instantiate and add subcommand " +
+                            throw new InitializationException("Could not instantiate and add subcommand " +
                                     sub.getName() + ": " + ex, ex);
                         }
                     }
@@ -1387,11 +1933,12 @@ public class CommandLine {
                 cls = cls.getSuperclass();
             }
             separator = declaredSeparator != null ? declaredSeparator : separator;
+            CommandLine.this.commandName = declaredName != null ? declaredName : CommandLine.this.commandName;
             Collections.sort(positionalParametersFields, new PositionalParametersSorter());
             validatePositionalParameters(positionalParametersFields);
 
             if (positionalParametersFields.isEmpty() && optionName2Field.isEmpty() && !hasCommandAnnotation) {
-                throw new IllegalArgumentException(command + " (" + command.getClass() +
+                throw new InitializationException(command + " (" + command.getClass() +
                         ") is not a command: it has no @Command, @Option or @Parameters annotations");
             }
         }
@@ -1404,6 +1951,7 @@ public class CommandLine {
          */
         List<CommandLine> parse(String... args) {
             Assert.notNull(args, "argument array");
+            if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));}
             Stack<String> arguments = new Stack<String>();
             for (int i = args.length - 1; i >= 0; i--) {
                 arguments.push(args[i]);
@@ -1419,6 +1967,8 @@ public class CommandLine {
             CommandLine.this.versionHelpRequested = false;
             CommandLine.this.usageHelpRequested = false;
 
+            Class<?> cmdClass = this.command.getClass();
+            if (tracer.isDebug()) {tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n", cmdClass.getName(), new HashSet<Field>(optionName2Field.values()).size(), positionalParametersFields.size(), requiredFields.size(), commands.size());}
             parsedCommands.add(CommandLine.this);
             List<Field> required = new ArrayList<Field>(requiredFields);
             Set<Field> initialized = new HashSet<Field>();
@@ -1428,20 +1978,23 @@ public class CommandLine {
             } catch (ParameterException ex) {
                 throw ex;
             } catch (Exception ex) {
-                int offendingArgIndex = originalArgs.length - argumentStack.size();
+                int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
                 String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?";
-                throw ParameterException.create(ex, arg, argumentStack.size(), originalArgs);
+                throw ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs);
             }
             if (!isAnyHelpRequested() && !required.isEmpty()) {
-                if (required.get(0).isAnnotationPresent(Option.class)) {
-                    throw MissingParameterException.create(required);
-                } else {
-                    try {
-                        processPositionalParameters0(required, true, new Stack<String>());
-                    } catch (ParameterException ex) { throw ex;
-                    } catch (Exception ex) { throw new IllegalStateException("Internal error: " + ex, ex); }
+                for (Field missing : required) {
+                    if (missing.isAnnotationPresent(Option.class)) {
+                        throw MissingParameterException.create(CommandLine.this, required, separator);
+                    } else {
+                        assertNoMissingParameters(missing, Range.parameterArity(missing).min, argumentStack);
+                    }
                 }
             }
+            if (!unmatchedArguments.isEmpty()) {
+                if (!isUnmatchedArgumentsAllowed()) { throw new UnmatchedArgumentException(CommandLine.this, unmatchedArguments); }
+                if (tracer.isWarn()) { tracer.warn("Unmatched arguments: %s%n", unmatchedArguments); }
+            }
         }
 
         private void processArguments(List<CommandLine> parsedCommands,
@@ -1460,19 +2013,22 @@ public class CommandLine {
 
             while (!args.isEmpty()) {
                 String arg = args.pop();
+                if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse((Stack<String>) args.clone()));}
 
                 // Double-dash separates options from positional arguments.
                 // If found, then interpret the remaining args as positional parameters.
                 if ("--".equals(arg)) {
-                    processPositionalParameters(required, args);
+                    tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
+                    processRemainderAsPositionalParameters(required, initialized, args);
                     return; // we are done
                 }
 
                 // if we find another command, we are done with the current command
                 if (commands.containsKey(arg)) {
                     if (!isHelpRequested && !required.isEmpty()) { // ensure current command portion is valid
-                        throw MissingParameterException.create(required);
+                        throw MissingParameterException.create(CommandLine.this, required, separator);
                     }
+                    if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, commands.get(arg).interpreter.command.getClass().getName());}
                     commands.get(arg).interpreter.parse(parsedCommands, args, originalArgs);
                     return; // remainder done by the command
                 }
@@ -1491,7 +2047,12 @@ public class CommandLine {
                         String optionParam = arg.substring(separatorIndex + separator.length());
                         args.push(optionParam);
                         arg = key;
+                        if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);}
+                    } else {
+                        if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);}
                     }
+                } else {
+                    if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);}
                 }
                 if (optionName2Field.containsKey(arg)) {
                     processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
@@ -1499,56 +2060,66 @@ public class CommandLine {
                 // Compact (single-letter) options can be grouped with other options or with an argument.
                 // only single-letter options can be combined with other options or with an argument
                 else if (arg.length() > 2 && arg.startsWith("-")) {
+                    if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);}
                     processClusteredShortOptions(required, initialized, arg, args);
                 }
                 // The argument could not be interpreted as an option.
                 // We take this to mean that the remainder are positional arguments
                 else {
                     args.push(arg);
-                    processPositionalParameters(required, args);
-                    return;
+                    if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);}
+                    if (resemblesOption(arg)) { handleUnmatchedArguments(args.pop()); continue; } // #149
+                    if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing remainder as positional parameters%n", arg);}
+                    processPositionalParameter(required, initialized, args);
                 }
             }
         }
-
-        private void processPositionalParameters(Collection<Field> required, Stack<String> args) throws Exception {
-            processPositionalParameters0(required, false, args);
-            if (!args.empty()) {
-                handleUnmatchedArguments(args);
-                return;
-            };
+        private boolean resemblesOption(String arg) {
+            int count = 0;
+            for (String optionName : optionName2Field.keySet()) {
+                for (int i = 0; i < arg.length(); i++) {
+                    if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; }
+                }
+            }
+            boolean result = count > 0 && count * 10 >= optionName2Field.size() * 9; // at least one prefix char in common with 9 out of 10 options
+            if (tracer.isDebug()) {tracer.debug("%s %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionName2Field.size());}
+            return result;
         }
-
+        private void handleUnmatchedArguments(String arg) {Stack<String> args = new Stack<String>(); args.add(arg); handleUnmatchedArguments(args);}
         private void handleUnmatchedArguments(Stack<String> args) {
-            if (!isUnmatchedArgumentsAllowed()) { throw new UnmatchedArgumentException(args); }
             while (!args.isEmpty()) { unmatchedArguments.add(args.pop()); } // addAll would give args in reverse order
         }
 
-        private void processPositionalParameters0(Collection<Field> required, boolean validateOnly, Stack<String> args) throws Exception {
-            int max = -1;
+        private void processRemainderAsPositionalParameters(Collection<Field> required, Set<Field> initialized, Stack<String> args) throws Exception {
+            while (!args.empty()) {
+                processPositionalParameter(required, initialized, args);
+            }
+        }
+        private void processPositionalParameter(Collection<Field> required, Set<Field> initialized, Stack<String> args) throws Exception {
+            if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter at index=%d. Remainder=%s%n", position, reverse((Stack<String>) args.clone()));}
+            int consumed = 0;
             for (Field positionalParam : positionalParametersFields) {
                 Range indexRange = Range.parameterIndex(positionalParam);
-                max = Math.max(max, indexRange.max);
-                @SuppressWarnings("unchecked")
-                Stack<String> argsCopy = reverse((Stack<String>) args.clone());
-                if (!indexRange.isVariable) {
-                    for (int i = argsCopy.size() - 1; i > indexRange.max; i--) {
-                        argsCopy.removeElementAt(i);
-                    }
+                if (!indexRange.contains(position)) {
+                    continue;
                 }
-                Collections.reverse(argsCopy);
-                for (int i = 0; i < indexRange.min && !argsCopy.isEmpty(); i++) { argsCopy.pop(); }
+                @SuppressWarnings("unchecked")
+                Stack<String> argsCopy = (Stack<String>) args.clone();
                 Range arity = Range.parameterArity(positionalParam);
+                if (tracer.isDebug()) {tracer.debug("Position %d is in index range %s. Trying to assign args to %s, arity=%s%n", position, indexRange, positionalParam, arity);}
                 assertNoMissingParameters(positionalParam, arity.min, argsCopy);
-                if (!validateOnly) {
-                    applyOption(positionalParam, Parameters.class, arity, false, argsCopy, null);
-                    required.remove(positionalParam);
-                }
+                int originalSize = argsCopy.size();
+                applyOption(positionalParam, Parameters.class, arity, false, argsCopy, initialized, "args[" + indexRange + "]");
+                int count = originalSize - argsCopy.size();
+                if (count > 0) { required.remove(positionalParam); }
+                consumed = Math.max(consumed, count);
             }
             // remove processed args from the stack
-            if (!validateOnly && !positionalParametersFields.isEmpty()) {
-                int processedArgCount = Math.min(args.size(), max < Integer.MAX_VALUE ? max + 1 : Integer.MAX_VALUE);
-                for (int i = 0; i < processedArgCount; i++) { args.pop(); }
+            for (int i = 0; i < consumed; i++) { args.pop(); }
+            position += consumed;
+            if (tracer.isDebug()) {tracer.debug("Consumed %d arguments, moving position to index %d.%n", consumed, position);}
+            if (consumed == 0 && !args.isEmpty()) {
+                handleUnmatchedArguments(args.pop());
             }
         }
 
@@ -1563,7 +2134,8 @@ public class CommandLine {
             if (paramAttachedToKey) {
                 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
             }
-            applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized);
+            if (tracer.isDebug()) {tracer.debug("Found option named '%s': field %s, arity=%s%n", arg, field, arity);}
+            applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized, "option " + arg);
         }
 
         private void processClusteredShortOptions(Collection<Field> required,
@@ -1577,19 +2149,27 @@ public class CommandLine {
             do {
                 if (cluster.length() > 0 && singleCharOption2Field.containsKey(cluster.charAt(0))) {
                     Field field = singleCharOption2Field.get(cluster.charAt(0));
+                    Range arity = Range.optionArity(field);
+                    String argDescription = "option " + prefix + cluster.charAt(0);
+                    if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: field %s, arity=%s%n", prefix, cluster.charAt(0), arg, field, arity);}
                     required.remove(field);
                     cluster = cluster.length() > 0 ? cluster.substring(1) : "";
                     paramAttachedToOption = cluster.length() > 0;
-                    Range arity = Range.optionArity(field);
                     if (cluster.startsWith(separator)) {// attached with separator, like -f=FILE or -v=true
                         cluster = cluster.substring(separator.length());
                         arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
                     }
+                    if (arity.min > 0 && !empty(cluster)) {
+                        if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);}
+                    }
                     args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!)
                     // arity may be >= 1, or
                     // arity <= 0 && !cluster.startsWith(separator)
                     // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
-                    int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized);
+                    if (!args.isEmpty() && args.peek().length() == 0 && !paramAttachedToOption) {
+                        args.pop(); // throw out empty string we get at the end of a group of clustered short options
+                    }
+                    int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription);
                     // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop
                     if (consumed > 0) {
                         return;
@@ -1602,12 +2182,21 @@ public class CommandLine {
                     // We get here when the remainder of the cluster group is neither an option,
                     // nor a parameter that the last option could consume.
                     if (arg.endsWith(cluster)) {
-                        // remainder was part of a clustered group that could not be completely parsed
                         args.push(paramAttachedToOption ? prefix + cluster : cluster);
-                        handleUnmatchedArguments(args);
+                        if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
+                            if (tracer.isDebug()) {tracer.debug("Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n", arg);}
+                            if (resemblesOption(arg)) { handleUnmatchedArguments(args.pop()); return; } // #149
+                            processPositionalParameter(required, initialized, args);
+                            return;
+                        }
+                        // remainder was part of a clustered group that could not be completely parsed
+                        if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);}
+                        handleUnmatchedArguments(args.pop());
+                    } else {
+                        args.push(cluster);
+                        if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);}
+                        processPositionalParameter(required, initialized, args);
                     }
-                    args.push(cluster);
-                    processPositionalParameters(required, args);
                     return;
                 }
             } while (true);
@@ -1618,28 +2207,32 @@ public class CommandLine {
                                 Range arity,
                                 boolean valueAttachedToOption,
                                 Stack<String> args,
-                                Set<Field> initialized) throws Exception {
+                                Set<Field> initialized,
+                                String argDescription) throws Exception {
             updateHelpRequested(field);
-            if (!args.isEmpty() && args.peek().length() == 0 && !valueAttachedToOption) {
-                args.pop(); // throw out empty string we get at the end of a group of clustered short options
-            }
             int length = args.size();
             assertNoMissingParameters(field, arity.min, args);
 
             Class<?> cls = field.getType();
             if (cls.isArray()) {
-                return applyValuesToArrayField(field, annotation, arity, args, cls);
+                return applyValuesToArrayField(field, annotation, arity, args, cls, argDescription);
             }
             if (Collection.class.isAssignableFrom(cls)) {
-                return applyValuesToCollectionField(field, annotation, arity, args, cls);
+                return applyValuesToCollectionField(field, annotation, arity, args, cls, argDescription);
+            }
+            if (Map.class.isAssignableFrom(cls)) {
+                return applyValuesToMapField(field, annotation, arity, args, cls, argDescription);
             }
-            return applyValueToSingleValuedField(field, arity, args, cls, initialized);
+            cls = getTypeAttribute(field)[0]; // field may be interface/abstract type, use annotation to get concrete type
+            return applyValueToSingleValuedField(field, arity, args, cls, initialized, argDescription);
         }
+
         private int applyValueToSingleValuedField(Field field,
                                                   Range arity,
                                                   Stack<String> args,
                                                   Class<?> cls,
-                                                  Set<Field> initialized) throws Exception {
+                                                  Set<Field> initialized,
+                                                  String argDescription) throws Exception {
             boolean noMoreValues = args.isEmpty();
             String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
             int result = arity.min; // the number or args we need to consume
@@ -1661,28 +2254,117 @@ public class CommandLine {
             if (noMoreValues && value == null) {
                 return 0;
             }
+            ITypeConverter<?> converter = getTypeConverter(cls, field);
+            Object newValue = tryConvert(field, -1, converter, value, cls);
+            Object oldValue = field.get(command);
+            TraceLevel level = TraceLevel.INFO;
+            String traceMessage = "Setting %s field '%s.%s' to '%5$s' (was '%4$s') for %6$s%n";
             if (initialized != null) {
-                if (initialized.contains(field) && !isOverwrittenOptionsAllowed()) {
-                    throw new OverwrittenOptionException(optionDescription("", field, 0) +  " should be specified only once");
+                if (initialized.contains(field)) {
+                    if (!isOverwrittenOptionsAllowed()) {
+                        throw new OverwrittenOptionException(CommandLine.this, optionDescription("", field, 0) +  " should be 

<TRUNCATED>