Skip to content

Commit

Permalink
Support for generic types in containers
Browse files Browse the repository at this point in the history
  • Loading branch information
mgorniew committed Jul 30, 2021
1 parent 29e88a7 commit 7b141ea
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 6 deletions.
23 changes: 17 additions & 6 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -4886,6 +4886,14 @@ public interface IVersionProvider {
*/
String[] getVersion() throws Exception;
}

public static final class UseDefaultConverter implements ITypeConverter<Object> {

public Object convert(String value) throws Exception {
throw new UnsupportedOperationException("This convert should never be called.");
}
}

private static class NoVersionProvider implements IVersionProvider {
public String[] getVersion() throws Exception { throw new UnsupportedOperationException(); }
}
Expand Down Expand Up @@ -10889,13 +10897,16 @@ static Class<?>[] extractTypeParameters(ParameterizedType genericType) {
} else if (paramTypes[i] instanceof ParameterizedType) { // e.g. Optional<Integer>
ParameterizedType parameterizedParamType = (ParameterizedType) paramTypes[i];
Type raw = parameterizedParamType.getRawType();
if (i == 1 && raw instanceof Class && CommandLine.isOptional((Class) raw)) { // #1108 and #1214
if (raw instanceof Class) {
result.add((Class) raw);
Class<?>[] aux = extractTypeParameters(parameterizedParamType);
if (aux.length == 1) {
result.add(aux[0]);
continue;
if (i == 1 && CommandLine.isOptional((Class) raw)) { // #1108 and #1214
Class<?>[] aux = extractTypeParameters(parameterizedParamType);
if (aux.length == 1) {
result.add(aux[0]);
continue;
}
}
continue;
}
} else if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
WildcardType wildcardType = (WildcardType) paramTypes[i];
Expand Down Expand Up @@ -14258,7 +14269,7 @@ private Collection<Object> createCollection(Class<?> collectionClass, Class<?>[]
return (Map<Object, Object>) factory.create(mapClass);
}
private ITypeConverter<?> getTypeConverter(Class<?>[] types, final ArgSpec argSpec, int index) {
if (argSpec.converters().length > index) { return argSpec.converters()[index]; } // use custom converters if defined
if (argSpec.converters().length > index && !argSpec.converters()[index].getClass().equals(UseDefaultConverter.class)) { return argSpec.converters()[index]; } // use custom converters if defined
Class<?> type = types[index];
if (isOptional(type)) { // #1214 #1108
if (types.length <= index + 1) { throw new PicocliException("Cannot create converter for types " + Arrays.asList(types) + " for " + argSpec); }
Expand Down
132 changes: 132 additions & 0 deletions src/test/java/picocli/GenericTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package picocli;

import org.junit.Test;
import picocli.CommandLine.MissingTypeConverterException;
import picocli.CommandLine.UseDefaultConverter;
import picocli.CommandLine.Option;

import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static picocli.CommandLine.ITypeConverter;
import static picocli.CommandLine.Parameters;

public class GenericTest {

static final class GenericValue<T> {
private final T value;

public GenericValue(T value) {
this.value = value;
}

public T getValue() {
return value;
}

@Override
public String toString() {
return "GenericValue{" +
"value=" + value +
'}';
}
}

static final class GenericValueConverter implements ITypeConverter<GenericValue<?>> {

public GenericValue<?> convert(String value) throws Exception {
if (value.startsWith("s")) {
return new GenericValue<String>(value);
}
return new GenericValue<Integer>(Integer.parseInt(value));
}
}

@Test
public void testListOfGenericClasses() {
class App {
@Parameters(arity = "0..*", converter = GenericValueConverter.class)
List<GenericValue<?>> values;
}
App app = CommandLine.populateCommand(new App(), "sOne", "15");
assertEquals(2, app.values.size());
assertEquals(String.class, app.values.get(0).value.getClass());
assertEquals("sOne", app.values.get(0).value);
assertEquals(Integer.class, app.values.get(1).value.getClass());
assertEquals(15, app.values.get(1).value);
}

@Test
public void testListOfGenericClassesNoConverter() {
class NoConverterApp {
@Parameters(arity = "0..*")
List<GenericValue<?>> values;
}
try {
CommandLine.populateCommand(new NoConverterApp(), "sOne", "15");
fail("Expected exception");
} catch (MissingTypeConverterException ex) {
assertEquals("No TypeConverter registered for picocli.GenericTest$GenericValue of field java.util.List<picocli.GenericTest$GenericValue<?>> picocli.GenericTest$1NoConverterApp.values", ex.getMessage());
}
}

@Test
public void testListOfGenericWithGlobalConverter() {
class NoConverterApp {
@Parameters(arity = "0..*")
List<GenericValue<?>> values;
}
@SuppressWarnings("rawtypes")
class GenericConverter implements ITypeConverter<GenericValue> {

@Override
public GenericValue convert(String value) throws Exception {
return new GenericValue<>("abc");
}
}
NoConverterApp app = new NoConverterApp();
new CommandLine(app).registerConverter(GenericValue.class, new GenericConverter()).parseArgs("xyz");
assertEquals(1, app.values.size());
assertEquals(String.class, app.values.get(0).value.getClass());
assertEquals("abc", app.values.get(0).value);
}

@Test
public void testMapOfGenericClasses() {
class App {
@Option(names = "-D", converter = {UseDefaultConverter.class, GenericValueConverter.class})
Map<String, GenericValue<?>> values;
}
App app = CommandLine.populateCommand(new App(), "-Dkey1=sOne", "-Dkey2=15");
assertEquals(2, app.values.size());
assertEquals(String.class, app.values.get("key1").value.getClass());
assertEquals("sOne", app.values.get("key1").value);
assertEquals(Integer.class, app.values.get("key2").value.getClass());
assertEquals(15, app.values.get("key2").value);
}

@Test
public void testSingleValueGenericWildcardClass() {
class App {
@Parameters(converter = GenericValueConverter.class)
GenericValue<?> value;
}
App app = CommandLine.populateCommand(new App(), "sTest");
assertEquals(String.class, app.value.value.getClass());
assertEquals("sTest", app.value.value);
}

@Test
public void testSingleValueGenericConcreteClass() {
class App {
@Parameters(converter = GenericValueConverter.class)
GenericValue<Integer> value;
}
App app = CommandLine.populateCommand(new App(), "3");
assertEquals(Integer.class, app.value.value.getClass());
assertEquals(Integer.valueOf(3), app.value.value);
}

}

0 comments on commit 7b141ea

Please sign in to comment.