diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java index 3d1bcd7eb2..c9f9536128 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java @@ -90,6 +90,42 @@ public String asString() throw new Uncoercible( type().name(), "Java String" ); } + @Override + public boolean asBoolean( boolean defaultValue ) + { + return computeOrDefault( Value:: asBoolean, defaultValue ); + } + + @Override + public String asString( String defaultValue ) + { + return computeOrDefault( (Value::asString), defaultValue ); + } + + @Override + public long asLong( long defaultValue ) + { + return computeOrDefault( Value::asLong, defaultValue ); + } + + @Override + public int asInt( int defaultValue ) + { + return computeOrDefault( Value::asInt, defaultValue ); + } + + @Override + public double asDouble( double defaultValue ) + { + return computeOrDefault( Value::asDouble, defaultValue ); + } + + @Override + public float asFloat( float defaultValue ) + { + return computeOrDefault( Value::asFloat, defaultValue ); + } + @Override public long asLong() { @@ -150,6 +186,88 @@ public Object asObject() throw new Uncoercible( type().name(), "Java Object" ); } + @Override + public T computeOrDefault( Function mapper, T defaultValue ) + { + if ( isNull() ) + { + return defaultValue; + } + return mapper.apply( this ); + } + + @Override + public Map asMap( Map defaultValue ) + { + return computeOrDefault( Value::asMap, defaultValue ); + } + + @Override + public Map asMap( Function mapFunction, Map defaultValue ) + { + return computeOrDefault( value -> value.asMap( mapFunction ), defaultValue ); + } + + @Override + public byte[] asByteArray( byte[] defaultValue ) + { + return computeOrDefault( Value::asByteArray, defaultValue ); + } + + @Override + public List asList( List defaultValue ) + { + return computeOrDefault( Value::asList, defaultValue ); + } + + @Override + public List asList( Function mapFunction, List defaultValue ) + { + return computeOrDefault( value -> value.asList( mapFunction ), defaultValue ); + } + + @Override + public LocalDate asLocalDate( LocalDate defaultValue ) + { + return computeOrDefault( Value::asLocalDate, defaultValue ); + } + + @Override + public OffsetTime asOffsetTime( OffsetTime defaultValue ) + { + return computeOrDefault( Value::asOffsetTime, defaultValue ); + } + + @Override + public LocalTime asLocalTime( LocalTime defaultValue ) + { + return computeOrDefault( Value::asLocalTime, defaultValue ); + } + + @Override + public LocalDateTime asLocalDateTime( LocalDateTime defaultValue ) + { + return computeOrDefault( Value::asLocalDateTime, defaultValue ); + } + + @Override + public ZonedDateTime asZonedDateTime( ZonedDateTime defaultValue ) + { + return computeOrDefault( Value::asZonedDateTime, defaultValue ); + } + + @Override + public IsoDuration asIsoDuration( IsoDuration defaultValue ) + { + return computeOrDefault( Value::asIsoDuration, defaultValue ); + } + + @Override + public Point asPoint( Point defaultValue ) + { + return computeOrDefault( Value::asPoint, defaultValue ); + } + @Override public byte[] asByteArray() { diff --git a/driver/src/main/java/org/neo4j/driver/v1/Value.java b/driver/src/main/java/org/neo4j/driver/v1/Value.java index 162d4a5c39..ec124ac6e1 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; +import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; import org.neo4j.driver.v1.exceptions.value.Uncoercible; @@ -194,24 +195,54 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Object asObject(); + /** + * Apply the mapping function on the value if the value is not a {@link NullValue}, or the default value if the value is a {@link NullValue}. + * @param mapper The mapping function defines how to map a {@link Value} to T. + * @param defaultValue the value to return if the value is a {@link NullValue} + * @param The return type + * @return The value after applying the given mapping function or the default value if the value is {@link NullValue}. + */ + T computeOrDefault( Function mapper, T defaultValue ); + /** * @return the value as a Java boolean, if possible. * @throws Uncoercible if value types are incompatible. */ boolean asBoolean(); + /** + * @param defaultValue return this value if the value is a {@link NullValue}. + * @return the value as a Java boolean, if possible. + * @throws Uncoercible if value types are incompatible. + */ + boolean asBoolean( boolean defaultValue ); + /** * @return the value as a Java byte array, if possible. * @throws Uncoercible if value types are incompatible. */ byte[] asByteArray(); + /** + * @param defaultValue default to this value if the original value is a {@link NullValue} + * @return the value as a Java byte array, if possible. + * @throws Uncoercible if value types are incompatible. + */ + byte[] asByteArray( byte[] defaultValue ); + /** * @return the value as a Java String, if possible. * @throws Uncoercible if value types are incompatible. */ String asString(); + /** + * @param defaultValue return this value if the value is null. + * @return the value as a Java String, if possible + * @throws Uncoercible if value types are incompatible. + */ + String asString( String defaultValue ); + /** * @return the value as a Java Number, if possible. * @throws Uncoercible if value types are incompatible. @@ -227,6 +258,15 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ long asLong(); + /** + * Returns a Java long if no precision is lost in the conversion. + * @param defaultValue return this default value if the value is a {@link NullValue}. + * @return the value as a Java long. + * @throws LossyCoercion if it is not possible to convert the value without loosing precision. + * @throws Uncoercible if value types are incompatible. + */ + long asLong( long defaultValue ); + /** * Returns a Java int if no precision is lost in the conversion. * @@ -236,6 +276,15 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ int asInt(); + /** + * Returns a Java int if no precision is lost in the conversion. + * @param defaultValue return this default value if the value is a {@link NullValue}. + * @return the value as a Java int. + * @throws LossyCoercion if it is not possible to convert the value without loosing precision. + * @throws Uncoercible if value types are incompatible. + */ + int asInt( int defaultValue ); + /** * Returns a Java double if no precision is lost in the conversion. * @@ -245,6 +294,15 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ double asDouble(); + /** + * Returns a Java double if no precision is lost in the conversion. + * @param defaultValue default to this value if the value is a {@link NullValue}. + * @return the value as a Java double. + * @throws LossyCoercion if it is not possible to convert the value without loosing precision. + * @throws Uncoercible if value types are incompatible. + */ + double asDouble( double defaultValue ); + /** * Returns a Java float if no precision is lost in the conversion. * @@ -254,6 +312,15 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ float asFloat(); + /** + * Returns a Java float if no precision is lost in the conversion. + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a Java float. + * @throws LossyCoercion if it is not possible to convert the value without loosing precision. + * @throws Uncoercible if value types are incompatible. + */ + float asFloat( float defaultValue ); + /** * If the underlying type can be viewed as a list, returns a java list of * values, where each value has been converted using {@link #asObject()}. @@ -263,6 +330,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ List asList(); + + /** + * If the underlying type can be viewed as a list, returns a java list of + * values, where each value has been converted using {@link #asObject()}. + * + * @see #asObject() + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a Java list of values, if possible + */ + List asList( List defaultValue ); + /** * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}. @@ -270,7 +348,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue * @see Values for a long list of built-in conversion functions * @return the value as a list of T obtained by mapping from the list elements, if possible */ - List asList( Function mapFunction ); + List asList( Function mapFunction ); + + /** + * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such + * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}. + * @param the type of target list elements + * @param defaultValue default to this value if the value is a {@link NullValue} + * @see Values for a long list of built-in conversion functions + * @return the value as a list of T obtained by mapping from the list elements, if possible + */ + List asList( Function mapFunction, List defaultValue ); /** * @return the value as a {@link Entity}, if possible. @@ -338,6 +426,76 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Point asPoint(); + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link LocalDate}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDate asLocalDate( LocalDate defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link OffsetTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + OffsetTime asOffsetTime( OffsetTime defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link LocalTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalTime asLocalTime( LocalTime defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link LocalDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDateTime asLocalDateTime( LocalDateTime defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link ZonedDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + ZonedDateTime asZonedDateTime( ZonedDateTime defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link IsoDuration}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + IsoDuration asIsoDuration( IsoDuration defaultValue ); + + /** + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a {@link Point}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + Point asPoint( Point defaultValue ); + + /** + * Return as a map of string keys and values converted using + * {@link Value#asObject()}. + * + * This is equivalent to calling {@link #asMap(Function, Map)} with {@link Values#ofObject()}. + * + * @param defaultValue default to this value if the value is a {@link NullValue} + * @return the value as a Java map + */ + Map asMap( Map defaultValue ); + + /** + * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such + * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}. + * @param the type of map values + * @param defaultValue default to this value if the value is a {@link NullValue} + * @see Values for a long list of built-in conversion functions + * @return the value as a map from string keys to values of type T obtained from mapping he original map values, if possible + */ + Map asMap( Function mapFunction, Map defaultValue ); + @Override boolean equals( Object other ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java index c2db722511..e777105035 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java @@ -20,11 +20,24 @@ import org.junit.Test; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; + import org.neo4j.driver.internal.types.TypeConstructor; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.util.Function; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertTrue; +import static org.neo4j.driver.v1.Values.isoDuration; +import static org.neo4j.driver.v1.Values.ofValue; +import static org.neo4j.driver.v1.Values.point; public class NullValueTest { @@ -45,4 +58,89 @@ public void shouldTypeAsNull() { assertThat( ((InternalValue) NullValue.NULL).typeConstructor(), equalTo( TypeConstructor.NULL ) ); } + + @Test + public void shouldReturnNativeTypesAsDefaultValue() throws Throwable + { + Value value = NullValue.NULL; + // string + assertThat( value.asString( "string value" ), equalTo( "string value" ) ); + + // primitives + assertThat( value.asBoolean( false ), equalTo( false ) ); + assertThat( value.asBoolean( true ), equalTo( true ) ); + assertThat( value.asInt( 10 ), equalTo( 10 ) ); + assertThat( value.asLong( 100L ), equalTo( 100L ) ); + assertThat( value.asFloat( 10.4f ), equalTo( 10.4f ) ); + assertThat( value.asDouble( 10.10 ), equalTo( 10.10 ) ); + + //array, list, map + assertThat( value.asByteArray( new byte[]{1, 2} ), equalTo( new byte[]{1, 2} ) ); + assertThat( value.asList( emptyList() ), equalTo( emptyList() ) ); + assertThat( value.asList( ofValue(), emptyList() ), equalTo( emptyList() ) ); + assertThat( value.asMap( emptyMap() ), equalTo( emptyMap() ) ); + assertThat( value.asMap( ofValue(), emptyMap() ), equalTo( emptyMap() ) ); + + // spatial, temporal + assertAsWithDefaultValueReturnDefault( value::asPoint, point( 1234, 1, 2 ).asPoint() ); + + assertAsWithDefaultValueReturnDefault( value::asLocalDate, LocalDate.now() ); + assertAsWithDefaultValueReturnDefault( value::asOffsetTime, OffsetTime.now() ); + assertAsWithDefaultValueReturnDefault( value::asLocalTime, LocalTime.now() ); + assertAsWithDefaultValueReturnDefault( value::asLocalDateTime, LocalDateTime.now() ); + assertAsWithDefaultValueReturnDefault( value::asZonedDateTime, ZonedDateTime.now() ); + assertAsWithDefaultValueReturnDefault( value::asIsoDuration, + isoDuration( 1, 2, 3, 4 ).asIsoDuration() ); + } + + @Test + public void shouldReturnAsNull() throws Throwable + { + assertComputeOrDefaultReturnNull( Value::asObject ); + assertComputeOrDefaultReturnNull( Value::asNumber ); + + assertComputeOrDefaultReturnNull( Value::asEntity ); + assertComputeOrDefaultReturnNull( Value::asNode ); + assertComputeOrDefaultReturnNull( Value::asRelationship ); + assertComputeOrDefaultReturnNull( Value::asPath ); + + assertComputeOrDefaultReturnNull( Value::asString ); + assertComputeOrDefaultReturnNull( Value::asByteArray ); + assertComputeOrDefaultReturnNull( Value::asList ); + assertComputeOrDefaultReturnNull( v -> v.asList( ofValue() ) ); + assertComputeOrDefaultReturnNull( Value::asMap ); + assertComputeOrDefaultReturnNull( v -> v.asMap( ofValue() ) ); + + assertComputeOrDefaultReturnNull( Value::asPoint ); + + assertComputeOrDefaultReturnNull( Value::asLocalDate ); + assertComputeOrDefaultReturnNull( Value::asOffsetTime ); + assertComputeOrDefaultReturnNull( Value::asLocalTime ); + assertComputeOrDefaultReturnNull( Value::asLocalDateTime ); + assertComputeOrDefaultReturnNull( Value::asZonedDateTime ); + assertComputeOrDefaultReturnNull( Value::asIsoDuration ); + } + + @Test + public void shouldReturnAsDefaultValue() throws Throwable + { + assertComputeOrDefaultReturnDefault( Value::asObject, "null string" ); + assertComputeOrDefaultReturnDefault( Value::asNumber, 10 ); + } + + private void assertComputeOrDefaultReturnDefault( Function f, T defaultAndExpectedValue ) + { + Value value = NullValue.NULL; + assertThat( value.computeOrDefault( f, defaultAndExpectedValue ), equalTo( defaultAndExpectedValue ) ); + } + + private void assertComputeOrDefaultReturnNull( Function f ) + { + assertComputeOrDefaultReturnDefault( f, null ); + } + + private void assertAsWithDefaultValueReturnDefault( Function map, T defaultValue ) + { + assertThat( map.apply( defaultValue ), equalTo( defaultValue ) ); + } }