diff --git a/build.gradle b/build.gradle index d781a3f..09580da 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { dependencies { testImplementation 'junit:junit:4.12' - testImplementation 'org.assertj:assertj-core:1.7.1' + testImplementation 'org.assertj:assertj-core:3.23.1' } test { diff --git a/src/main/java/com/github/drapostolos/typeparser/DynamicParsers.java b/src/main/java/com/github/drapostolos/typeparser/DynamicParsers.java index 2a2b970..9f527bf 100644 --- a/src/main/java/com/github/drapostolos/typeparser/DynamicParsers.java +++ b/src/main/java/com/github/drapostolos/typeparser/DynamicParsers.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.NavigableMap; +import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.SortedMap; @@ -204,7 +205,20 @@ public Object parse(String input, ParserHelper helper) { } return result; } - }; + }, + OPTIONAL { + + @Override + public Object parse(String input, ParserHelper helper) { + if (!helper.isTargetTypeAssignableTo(Optional.class)) { + return TRY_NEXT; + } + Object value = helper.parseType(input, helper.getParameterizedTypeArguments().get(0)); + return Optional.ofNullable(value); + } + + }, +; static private Collection populateCollection(Collection collection, Class elementType, String input, ParserHelper helper) { diff --git a/src/main/java/com/github/drapostolos/typeparser/Helper.java b/src/main/java/com/github/drapostolos/typeparser/Helper.java index 71b69f6..b1b874d 100644 --- a/src/main/java/com/github/drapostolos/typeparser/Helper.java +++ b/src/main/java/com/github/drapostolos/typeparser/Helper.java @@ -40,6 +40,19 @@ public final Type getTargetType() { public final List> getParameterizedClassArguments() { return tt.getParameterizedClassArguments(); } + + /** + * When the {@code targetType} is a parameterized type this method + * returns a list with the type arguments. + *

+ * + * @return List of {@link Type} types. + * @throws UnsupportedOperationException if the {@code targetType} is not a parameterized type. + */ + public List getParameterizedTypeArguments() { + return tt.getParameterizedTypeArguments(); + } + /** * When the {@code targetType} is an array, this method diff --git a/src/main/java/com/github/drapostolos/typeparser/TargetType.java b/src/main/java/com/github/drapostolos/typeparser/TargetType.java index 73275a4..2782d68 100644 --- a/src/main/java/com/github/drapostolos/typeparser/TargetType.java +++ b/src/main/java/com/github/drapostolos/typeparser/TargetType.java @@ -5,6 +5,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; final class TargetType { @@ -56,7 +57,7 @@ private Class extractRawTargetType() { return targetType.getClass(); } - final public List> getParameterizedClassArguments() { + List> getParameterizedClassArguments() { if (parameterizedClassArguments == null) { parameterizedClassArguments = extractParameterizedClassArguments(); } @@ -95,8 +96,17 @@ public String toString() { return targetType.toString(); } - public boolean isPrimitive() { + boolean isPrimitive() { return targetType instanceof Class && ((Class) targetType).isPrimitive(); } + List getParameterizedTypeArguments() { + if (!(isTargetTypeParameterized())) { + String message = String.format("type must be parameterized: %s", Util.objectToString(targetType)); + throw new UnsupportedOperationException(message); + } + ParameterizedType pt = (ParameterizedType) targetType; + return Arrays.asList(pt.getActualTypeArguments()); + } + } diff --git a/src/test/java/com/github/drapostolos/typeparser/MapTest.java b/src/test/java/com/github/drapostolos/typeparser/MapTest.java index 0394d77..406646d 100644 --- a/src/test/java/com/github/drapostolos/typeparser/MapTest.java +++ b/src/test/java/com/github/drapostolos/typeparser/MapTest.java @@ -56,15 +56,20 @@ public void canParseToConcreteMapTypes() throws Exception { new GenericType>() {}, new GenericType>() {} ); - } private void parseToConcreteMapTypes(@SuppressWarnings("unchecked") GenericType>... types) { for (GenericType> type : types) { Class rawType = toRawType(type); Map map = parser.parse("a=A", type); + /* + * Workaround for testing IdentityHashMap with assertJ (version: 3.23.1) + */ + map.forEach((k, v) -> assertThat(k).describedAs("key: %s", k).isEqualTo("a")); + map.forEach((k, v) -> assertThat(v).describedAs("value: %s", v).isEqualTo("A")); + assertThat(map) - .containsOnly(MapEntry.entry("a", "A")) + .describedAs("type: %s", type) .isInstanceOf(rawType); assertThat(map.getClass()) .isEqualTo(rawType); diff --git a/src/test/java/com/github/drapostolos/typeparser/OptionalTest.java b/src/test/java/com/github/drapostolos/typeparser/OptionalTest.java new file mode 100644 index 0000000..88fb4c6 --- /dev/null +++ b/src/test/java/com/github/drapostolos/typeparser/OptionalTest.java @@ -0,0 +1,60 @@ +package com.github.drapostolos.typeparser; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; + +import org.junit.Test; + +public class OptionalTest extends TestBase { + + @Test + public void canParseSpaceToOptionalString() throws Exception { + GenericType> type = new GenericType>() {}; + assertThat(parser.parse(" ", type)).contains(" "); + } + + @Test + public void canParseStringToEmptyOptional() throws Exception { + GenericType> type = new GenericType>() {}; + assertThat(parser.parse("null", type)).isEmpty(); + } + + @Test + public void canParseStringToEmptyOptionalWithWildcard() throws Exception { + GenericType> type = new GenericType>() {}; + assertThat(parser.parse("null", type)).isEmpty(); + } + + @Test + public void canParseStringToOptionalListOfIntegers() throws Exception { + Optional> integers = parser.parse("1,2,3", new GenericType>>() {}); + assertThat(integers.get()).containsExactly(1, 2, 3); + } + + @Test + public void canParseStringToOptionalLong() throws Exception { + GenericType> type = new GenericType>() {}; + assertThat(parser.parse("1", type)).contains(1l); + } + + @Test + public void shouldThrowExceptionWhenParsingWildcardType() throws Exception { + shouldThrow(NoSuchRegisteredParserException.class) + .containingErrorMessage("Can not parse \"dummy-string\"") + .containingErrorMessage("to type \"?\"") + .containingErrorMessage("due to: There is no registered 'Parser' for that type.") + .whenParsing(DUMMY_STRING) + .to(new GenericType>() {}); + } + + @Test + public void canChangeNullStrategy() throws Exception { + parser = TypeParser.newBuilder() + .setNullStringStrategy((input, helper) -> input.isEmpty()) + .build(); + + assertThat(parser.parse("", new GenericType>() {})).isEmpty(); + } +} diff --git a/src/test/java/com/github/drapostolos/typeparser/TargetTypeTest.java b/src/test/java/com/github/drapostolos/typeparser/TargetTypeTest.java index 39a5f84..1d52b94 100644 --- a/src/test/java/com/github/drapostolos/typeparser/TargetTypeTest.java +++ b/src/test/java/com/github/drapostolos/typeparser/TargetTypeTest.java @@ -1,6 +1,7 @@ package com.github.drapostolos.typeparser; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.Test; @@ -12,4 +13,12 @@ public void testToString() throws Exception { assertThat(type.toString()).isEqualTo(String.class.toString()); } + @Test + public void shouldThrowWhenGettingParameterizedTypeArgumentsAndNotParameterized() throws Exception { + TargetType type = new TargetType(String.class); + + assertThatThrownBy(() -> type.getParameterizedTypeArguments()) + .hasMessageContaining("type must be parameterized") + .hasNoCause(); + } }