From bc6924eccc7c7874600d78119a3200c8bd628adc Mon Sep 17 00:00:00 2001 From: Remko Popma Date: Sat, 16 Feb 2019 20:27:23 +0900 Subject: [PATCH] [#628] Add support for collecting `enum` multi-value options and positional parameters in `EnumSet<>` collections --- RELEASE-NOTES.md | 10 ++++- src/main/java/picocli/CommandLine.java | 10 +++-- .../CommandLineTypeConversionTest.java | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e76b45876..d0217606b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -5,6 +5,11 @@ The picocli community is pleased to announce picocli 3.9.4. This release contains bugfixes and enhancements. +From this release, `enum`-typed options and positional parameters that are multi-value can be stored in `EnumSet` collections, as well as other Collections, arrays and Maps. + +Bugfixes: `ReflectionConfigGenerator` incorrectly listed superclass fields as fields of the concrete subclass, causing "GraalVM error: Error parsing reflection configuration in json" when creating a native image, +and method subcommands in commands that subclass another command caused `InitializationException`. + This is the forty-nineth public release. Picocli follows [semantic versioning](http://semver.org/). @@ -19,9 +24,10 @@ Picocli follows [semantic versioning](http://semver.org/). ## Fixed issues +- [#628] Add support for collecting `enum` multi-value options and positional parameters in `EnumSet<>` collections. Thanks to [Lee Atkinson](https://github.com/leeatkinson) for raising this. - [#619] Bugfix: Method subcommands in commands that subclass another command caused `InitializationException`: "Another subcommand named 'method' already exists...". Thanks to [PorygonZRocks](https://github.com/PorygonZRocks) for the bug report. -- [#622] Bugfix: ReflectionConfigGenerator incorrectly lists superclass fields as fields of the concrete subclass, causing "GraalVM error: Error parsing reflection configuration in json". Thanks to [Sebastian Thomschke](https://github.com/sebthom) for the bug report. -- [#623] ReflectionConfigGenerator now generates json in alphabetic order. +- [#622] Bugfix: `ReflectionConfigGenerator` incorrectly listed superclass fields as fields of the concrete subclass, causing "GraalVM error: Error parsing reflection configuration in json". Thanks to [Sebastian Thomschke](https://github.com/sebthom) for the bug report. +- [#623] `ReflectionConfigGenerator` now generates json in alphabetic order. ## Deprecations No features were deprecated in this release. diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 65b2336bb..97c200189 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -8836,7 +8836,7 @@ private int applyValuesToCollectionField(ArgSpec argSpec, List converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription); if (collection == null || (!collection.isEmpty() && !initialized.contains(argSpec))) { tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName()); - collection = createCollection(argSpec.type()); // collection type + collection = createCollection(argSpec.type(), type); // collection type, element type argSpec.setValue(collection); } initialized.add(argSpec); @@ -9024,7 +9024,7 @@ private boolean is(ArgSpec p, String attribute, boolean value) { return value; } @SuppressWarnings("unchecked") - private Collection createCollection(Class collectionClass) throws Exception { + private Collection createCollection(Class collectionClass, Class elementType) throws Exception { if (collectionClass.isInterface()) { if (List.class.isAssignableFrom(collectionClass)) { return new ArrayList(); @@ -9037,8 +9037,12 @@ private Collection createCollection(Class collectionClass) throws Exc } return new ArrayList(); } + if (EnumSet.class.isAssignableFrom(collectionClass) && Enum.class.isAssignableFrom(elementType)) { + Object enumSet = EnumSet.noneOf((Class) elementType); + return (Collection) enumSet; + } // custom Collection implementation class must have default constructor - return (Collection) collectionClass.newInstance(); + return (Collection) factory.create(collectionClass); } @SuppressWarnings("unchecked") private Map createMap(Class mapClass) throws Exception { try { // if it is an implementation class, instantiate it diff --git a/src/test/java/picocli/CommandLineTypeConversionTest.java b/src/test/java/picocli/CommandLineTypeConversionTest.java index 87890de50..02fe499d8 100644 --- a/src/test/java/picocli/CommandLineTypeConversionTest.java +++ b/src/test/java/picocli/CommandLineTypeConversionTest.java @@ -47,6 +47,8 @@ import static java.util.concurrent.TimeUnit.*; import static org.junit.Assert.*; +import static picocli.CommandLineTypeConversionTest.ResultTypes.COMPLETE; +import static picocli.CommandLineTypeConversionTest.ResultTypes.PARTIAL; public class CommandLineTypeConversionTest { // allows tests to set any kind of properties they like, without having to individually roll them back @@ -1087,4 +1089,42 @@ class App { assertEquals("Unmatched arguments: a:c, 1:3", ex.getMessage()); } } + enum ResultTypes { + NONE, + PARTIAL, + COMPLETE + } + @Test + public void testIssue628EnumSetWithNullInitialValue() { + class App { + @Option(names = "--result-types", split = ",") + private EnumSet resultTypes = null; + } + App app = new App(); + new CommandLine(app).parseArgs("--result-types", "PARTIAL,COMPLETE"); + + assertEquals(EnumSet.of(PARTIAL, COMPLETE), app.resultTypes); + } + @Test + public void testIssue628EnumSetWithEmptyInitialValue() { + class App { + @Option(names = "--result-types", split = ",") + private EnumSet resultTypes = EnumSet.noneOf(ResultTypes.class); + } + App app = new App(); + new CommandLine(app).parseArgs("--result-types", "PARTIAL,COMPLETE"); + + assertEquals(EnumSet.of(PARTIAL, COMPLETE), app.resultTypes); + } + @Test + public void testIssue628EnumSetWithNonEmptyInitialValue() { + class App { + @Option(names = "--result-types", split = ",") + private EnumSet resultTypes = EnumSet.of(ResultTypes.COMPLETE); + } + App app = new App(); + new CommandLine(app).parseArgs("--result-types", "PARTIAL,COMPLETE"); + + assertEquals(EnumSet.of(PARTIAL, COMPLETE), app.resultTypes); + } }