Skip to content

Commit

Permalink
#83 additional built-in type converters: sql and reflection-based
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop committed Dec 18, 2017
1 parent 603ddd8 commit 87e7f45
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 3 deletions.
55 changes: 55 additions & 0 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
Expand All @@ -41,7 +42,11 @@
import java.net.URL;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.BreakIterator;
import java.text.ParseException;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -1976,8 +1981,28 @@ private class Interpreter {
converterRegistry.put(TimeZone.class, new BuiltIn.TimeZoneConverter());
converterRegistry.put(ByteOrder.class, new BuiltIn.ByteOrderConverter());
converterRegistry.put(Class.class, new BuiltIn.ClassConverter());
converterRegistry.put(Connection.class, new BuiltIn.ConnectionConverter());
converterRegistry.put(Driver.class, new BuiltIn.DriverConverter());
converterRegistry.put(Timestamp.class, new BuiltIn.TimestampConverter());
converterRegistry.put(NetworkInterface.class, new BuiltIn.NetworkInterfaceConverter());

BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Duration", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Instant", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDate", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDateTime", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalTime", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.MonthDay", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetDateTime", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetTime", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Period", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Year", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.YearMonth", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZonedDateTime", "parse", CharSequence.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneId", "of", String.class);
BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneOffset", "of", String.class);

BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.nio.file.Paths", "get", String.class, String[].class);

this.command = Assert.notNull(command, "command");
Class<?> cls = command.getClass();
String declaredName = null;
Expand Down Expand Up @@ -2926,6 +2951,36 @@ public NetworkInterface convert(String s) throws Exception {
}
}
}
static class ConnectionConverter implements ITypeConverter<Connection> {
public Connection convert(String s) throws Exception { return DriverManager.getConnection(s); }
}
static class DriverConverter implements ITypeConverter<Driver> {
public Driver convert(String s) throws Exception { return DriverManager.getDriver(s); }
}
static class TimestampConverter implements ITypeConverter<Timestamp> {
public Timestamp convert(String s) throws Exception { return Timestamp.valueOf(s); }
}
static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String methodName, Class<?>... paramTypes) {
try {
Class<?> cls = Class.forName(fqcn);
Method method = cls.getDeclaredMethod(methodName, paramTypes);
registry.put(cls, new ReflectionConverter(method));
} catch (Exception e) {
tracer.info("Could not register converter %s.%s: %s%n", fqcn, methodName, e.toString());
}
}
static class ReflectionConverter implements ITypeConverter<Object> {
private final Method method;
public ReflectionConverter(Method method) { this.method = Assert.notNull(method, "method"); }

public Object convert(String s) {
try {
return method.invoke(null, s);
} catch (Exception e) {
throw new TypeConversionException("Unable to convert " + s + " to " + method.getReturnType() + ": " + e.getMessage());
}
}
}
private BuiltIn() {} // private constructor: never instantiate
}

Expand Down
36 changes: 33 additions & 3 deletions src/test/java/picocli/CommandLineTypeConversionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
Expand Down Expand Up @@ -87,6 +91,9 @@ static class SupportedTypes {
@Option(names = "-tz") TimeZone aTimeZone;
@Option(names = "-byteOrder") ByteOrder aByteOrder;
@Option(names = "-Class") Class aClass;
@Option(names = "-Connection") Connection aConnection;
@Option(names = "-Driver") Driver aDriver;
@Option(names = "-Timestamp") Timestamp aTimestamp;
@Option(names = "-NetworkInterface") NetworkInterface aNetInterface;
}
@Test
Expand Down Expand Up @@ -127,6 +134,9 @@ public void testDefaults() {
assertEquals("ByteOrder", null, bean.aByteOrder);
assertEquals("Class", null, bean.aClass);
assertEquals("NetworkInterface", null, bean.aNetInterface);
assertEquals("Connection", null, bean.aConnection);
assertEquals("Driver", null, bean.aDriver);
assertEquals("Timestamp", null, bean.aTimestamp);
}
@Test
public void testTypeConversionSucceedsForValidInput() throws Exception {
Expand Down Expand Up @@ -155,7 +165,11 @@ public void testTypeConversionSucceedsForValidInput() throws Exception {
"-tz", "Japan/Tokyo",
"-byteOrder", "LITTLE_ENDIAN",
"-Class", "java.lang.String",
"-NetworkInterface", "127.0.0.0"
"-NetworkInterface", "127.0.0.0",
"-Timestamp", "2017-12-13 13:59:59.123456789"
// ,
// "-Connection", "jdbc:derby:testDB;create=false",
// "-Driver", "org.apache.derby.jdbc.EmbeddedDriver"
);
assertEquals("boolean", true, bean.booleanField);
assertEquals("Boolean", Boolean.TRUE, bean.aBooleanField);
Expand Down Expand Up @@ -193,6 +207,9 @@ public void testTypeConversionSucceedsForValidInput() throws Exception {
assertEquals("ByteOrder", ByteOrder.LITTLE_ENDIAN, bean.aByteOrder);
assertEquals("Class", String.class, bean.aClass);
assertEquals("NetworkInterface", NetworkInterface.getByInetAddress(InetAddress.getByName("127.0.0.0")), bean.aNetInterface);
assertEquals("Timestamp", Timestamp.valueOf("2017-12-13 13:59:59.123456789"), bean.aTimestamp);
// assertEquals("Connection", DriverManager.getConnection("jdbc:derby:testDB;create=false"), bean.aConnection);
// assertEquals("Driver", DriverManager.getDriver("org.apache.derby.jdbc.EmbeddedDriver"), bean.aDriver);
}
@Test
public void testByteFieldsAreDecimal() {
Expand Down Expand Up @@ -453,8 +470,9 @@ public void testTimeZoneConvertersInvalidError() {
assertEquals(TimeZone.getTimeZone("GMT"), bean.aTimeZone);
}
@Test
public void testNetworkInterfaceConvertersInvalidError() {
//parseInvalidValue("-NetworkInterface", "127.127.127.127.127", ": java.lang.IllegalArgumentException");
public void testNetworkInterfaceConvertersInvalidNull() {
SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-NetworkInterface", "no such interface");
assertNull(bean.aNetInterface);
}
@Test
public void testRegexPatternConverterInvalidError() {
Expand All @@ -470,6 +488,18 @@ public void testByteOrderConvertersInvalidError() {
public void testClassConvertersInvalidError() {
parseInvalidValue("-Class", "aa", ": java.lang.ClassNotFoundException: aa");
}
@Test
public void testConnectionConvertersInvalidError() {
parseInvalidValue("-Connection", "aa", ": java.sql.SQLException: No suitable driver found for aa");
}
@Test
public void testDriverConvertersInvalidError() {
parseInvalidValue("-Driver", "aa", ": java.sql.SQLException: No suitable driver");
}
@Test
public void testTimestampConvertersInvalidError() {
parseInvalidValue("-Timestamp", "aa", ": java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]");
}

private void parseInvalidValue(String option, String value, String errorMessage) {
try {
Expand Down

0 comments on commit 87e7f45

Please sign in to comment.