From 6550cf880bcac219c5ecc80750d402c506320318 Mon Sep 17 00:00:00 2001 From: Andrei Dunai Date: Thu, 28 Sep 2023 19:14:06 +0300 Subject: [PATCH] Add stream methods to JsonNullable api. Add semantically important NULL state --- .../jackson/nullable/JsonNullable.java | 54 +++++++++++++- .../nullable/JsonNullableStreamApiTest.java | 71 +++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/openapitools/jackson/nullable/JsonNullableStreamApiTest.java diff --git a/src/main/java/org/openapitools/jackson/nullable/JsonNullable.java b/src/main/java/org/openapitools/jackson/nullable/JsonNullable.java index 43a521b..74be4a6 100644 --- a/src/main/java/org/openapitools/jackson/nullable/JsonNullable.java +++ b/src/main/java/org/openapitools/jackson/nullable/JsonNullable.java @@ -4,15 +4,16 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; public class JsonNullable implements Serializable { private static final long serialVersionUID = 1L; private static final JsonNullable UNDEFINED = new JsonNullable<>(null, false); + private static final JsonNullable NULL = new JsonNullable<>(null, true); private final T value; - private final boolean isPresent; private JsonNullable(T value, boolean isPresent) { @@ -32,6 +33,11 @@ public static JsonNullable undefined() { return t; } + @SuppressWarnings("unchecked") + public static JsonNullable ofNull() { + return (JsonNullable) NULL; + } + /** * Create a JsonNullable from the submitted value. * @@ -43,6 +49,31 @@ public static JsonNullable of(T value) { return new JsonNullable<>(value, true); } + /** + * Returns wrapper of either UNDEFINED or NULL state + * + * @param value the value + * @param type of value inside + * @return JsonNullable in UNDEFINED or NULL state + */ + public static JsonNullable ofMissable(T value) { + return new JsonNullable<>(value, value != null); + } + + public JsonNullable flatMap(Function> mapper) { + if (isNonNull()) { + return mapper.apply(value); + } + return isNull() ? JsonNullable.ofNull() : JsonNullable.undefined(); + } + + public JsonNullable map(Function mapper) { + if (isNonNull()) { + return JsonNullable.ofMissable(mapper.apply(value)); + } + return isNull() ? JsonNullable.ofNull() : JsonNullable.undefined(); + } + /** * Obtain the value of this JsonNullable. * @@ -84,6 +115,25 @@ public void ifPresent( } } + public boolean isNull() { + return isPresent && value == null; + } + + public boolean isNonNull() { + return isPresent && value != null; + } + + /** + * If non-null value is present, performs an action with the value + * + * @param action The action performed with value + */ + public void ifNotNull(Consumer action) { + if (isNonNull()) { + action.accept(value); + } + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -108,4 +158,4 @@ public int hashCode() { public String toString() { return this.isPresent ? String.format("JsonNullable[%s]", value) : "JsonNullable.undefined"; } -} +} \ No newline at end of file diff --git a/src/test/java/org/openapitools/jackson/nullable/JsonNullableStreamApiTest.java b/src/test/java/org/openapitools/jackson/nullable/JsonNullableStreamApiTest.java new file mode 100644 index 0000000..a5ea6cf --- /dev/null +++ b/src/test/java/org/openapitools/jackson/nullable/JsonNullableStreamApiTest.java @@ -0,0 +1,71 @@ +package org.openapitools.jackson.nullable; + +import junit.framework.TestCase; +import org.junit.Test; + +import static junit.framework.TestCase.*; + +public class JsonNullableStreamApiTest { + + + @Test + public void shouldReturnUndefinedAfterStream() { + final BigDto bigDto = new BigDto(null); + + final JsonNullable accessedValue = JsonNullable.ofMissable(bigDto) + .map(BigDto::getChild) + .map(ChildOfBigDto::getGrandChild); + + assertEquals(accessedValue, JsonNullable.undefined()); + } + + + @Test + public void shouldReturnNullAfterStream() { + final BigDto bigDto = new BigDto(null); + + // we want to receive null state in the end + JsonNullable.ofMissable(bigDto) + // that is why we explicitly allow JsonNullable to produce NULL state in the flow + .flatMap(dto -> JsonNullable.of(dto.getChild())) + .map(ChildOfBigDto::getGrandChild) + .ifPresent(TestCase::assertNull); + } + + @Test + public void consumeNotNullValue() { + final String goldenEgg = "goldenEgg"; + final ChildOfBigDto child = new ChildOfBigDto("goldenEgg"); + final BigDto testedDto = new BigDto(child); + + JsonNullable.ofMissable(testedDto) + .map(BigDto::getChild) + .map(ChildOfBigDto::getGrandChild) + .ifNotNull(grandChild -> assertEquals(grandChild, goldenEgg)); + } + + + private static class BigDto { + private ChildOfBigDto child; + + public BigDto(ChildOfBigDto child) { + this.child = child; + } + + public ChildOfBigDto getChild() { + return this.child; + } + } + + private static class ChildOfBigDto { + private String grandChild; + + public ChildOfBigDto(String grandChild) { + this.grandChild = grandChild; + } + + public String getGrandChild() { + return grandChild; + } + } +}