diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java new file mode 100644 index 0000000000..9afd8baef4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.time.Duration; +import java.time.Period; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.List; +import java.util.Objects; + +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + +public class InternalIsoDuration implements IsoDuration +{ + private static final List SUPPORTED_UNITS = unmodifiableList( asList( MONTHS, DAYS, SECONDS, NANOS ) ); + + private final long months; + private final long days; + private final long seconds; + private final long nanoseconds; + + public InternalIsoDuration( Period period ) + { + this( period.toTotalMonths(), period.getDays(), 0, 0 ); + } + + public InternalIsoDuration( Duration duration ) + { + this( 0, 0, duration.getSeconds(), duration.getNano() ); + } + + public InternalIsoDuration( long months, long days, long seconds, long nanoseconds ) + { + this.months = months; + this.days = days; + this.seconds = seconds; + this.nanoseconds = nanoseconds; + } + + @Override + public long months() + { + return months; + } + + @Override + public long days() + { + return days; + } + + @Override + public long seconds() + { + return seconds; + } + + @Override + public long nanoseconds() + { + return nanoseconds; + } + + @Override + public long get( TemporalUnit unit ) + { + if ( unit == MONTHS ) + { + return months; + } + else if ( unit == DAYS ) + { + return days; + } + else if ( unit == SECONDS ) + { + return seconds; + } + else if ( unit == NANOS ) + { + return nanoseconds; + } + else + { + throw new UnsupportedTemporalTypeException( "Unsupported unit: " + unit ); + } + } + + @Override + public List getUnits() + { + return SUPPORTED_UNITS; + } + + @Override + public Temporal addTo( Temporal temporal ) + { + if ( months != 0 ) + { + temporal = temporal.plus( months, MONTHS ); + } + if ( days != 0 ) + { + temporal = temporal.plus( days, DAYS ); + } + if ( seconds != 0 ) + { + temporal = temporal.plus( seconds, SECONDS ); + } + if ( nanoseconds != 0 ) + { + temporal = temporal.plus( nanoseconds, NANOS ); + } + return temporal; + } + + @Override + public Temporal subtractFrom( Temporal temporal ) + { + if ( months != 0 ) + { + temporal = temporal.minus( months, MONTHS ); + } + if ( days != 0 ) + { + temporal = temporal.minus( days, DAYS ); + } + if ( seconds != 0 ) + { + temporal = temporal.minus( seconds, SECONDS ); + } + if ( nanoseconds != 0 ) + { + temporal = temporal.minus( nanoseconds, NANOS ); + } + return temporal; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalIsoDuration that = (InternalIsoDuration) o; + return months == that.months && + days == that.days && + seconds == that.seconds && + nanoseconds == that.nanoseconds; + } + + @Override + public int hashCode() + { + return Objects.hash( months, days, seconds, nanoseconds ); + } + + @Override + public String toString() + { + return "Duration{" + + "months=" + months + + ", days=" + days + + ", seconds=" + seconds + + ", nanoseconds=" + nanoseconds + + '}'; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java index 9e4d654acb..62f194523f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java @@ -20,15 +20,15 @@ import java.util.Objects; -import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point; -public class InternalPoint2D implements Point2D +public class InternalPoint2D implements Point { - private final long srid; + private final int srid; private final double x; private final double y; - public InternalPoint2D( long srid, double x, double y ) + public InternalPoint2D( int srid, double x, double y ) { this.srid = srid; this.x = x; @@ -36,7 +36,7 @@ public InternalPoint2D( long srid, double x, double y ) } @Override - public long srid() + public int srid() { return srid; } @@ -53,6 +53,12 @@ public double y() return y; } + @Override + public double z() + { + return Double.NaN; + } + @Override public boolean equals( Object o ) { @@ -79,7 +85,7 @@ public int hashCode() @Override public String toString() { - return "Point2D{" + + return "Point{" + "srid=" + srid + ", x=" + x + ", y=" + y + diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java index 96fdd0d005..76c1106f83 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java @@ -20,16 +20,16 @@ import java.util.Objects; -import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Point; -public class InternalPoint3D implements Point3D +public class InternalPoint3D implements Point { - private final long srid; + private final int srid; private final double x; private final double y; private final double z; - public InternalPoint3D( long srid, double x, double y, double z ) + public InternalPoint3D( int srid, double x, double y, double z ) { this.srid = srid; this.x = x; @@ -38,7 +38,7 @@ public InternalPoint3D( long srid, double x, double y, double z ) } @Override - public long srid() + public int srid() { return srid; } @@ -88,11 +88,11 @@ public int hashCode() @Override public String toString() { - return "Point3D{" + + return "Point({" + "srid=" + srid + ", x=" + x + ", y=" + y + ", z=" + z + - '}'; + "})"; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java index 01966de341..101ed38966 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java @@ -23,9 +23,8 @@ import java.util.Map; import java.util.NoSuchElementException; -import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue; +import org.neo4j.driver.internal.util.Extract; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; @@ -129,7 +128,7 @@ public Map asMap( Function mapper ) @Override public String toString() { - return format( "Record<%s>", formatPairs( InternalValue.Format.VALUE_ONLY, asMap( ofValue() ) ) ); + return format( "Record<%s>", formatPairs( asMap( ofValue() ) ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java index 837b011839..c5124ad257 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java @@ -33,6 +33,7 @@ import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.packstream.PackStream; import org.neo4j.driver.internal.packstream.PackType; +import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.internal.value.ListValue; @@ -210,120 +211,120 @@ void packInternalValue( InternalValue value ) throws IOException { switch ( value.typeConstructor() ) { - case NULL_TyCon: - packer.packNull(); - break; + case NULL: + packer.packNull(); + break; - case BYTES_TyCon: - packer.pack( value.asByteArray() ); - break; + case BYTES: + packer.pack( value.asByteArray() ); + break; - case STRING_TyCon: - packer.pack( value.asString() ); - break; + case STRING: + packer.pack( value.asString() ); + break; - case BOOLEAN_TyCon: - packer.pack( value.asBoolean() ); - break; + case BOOLEAN: + packer.pack( value.asBoolean() ); + break; - case INTEGER_TyCon: - packer.pack( value.asLong() ); - break; + case INTEGER: + packer.pack( value.asLong() ); + break; - case FLOAT_TyCon: - packer.pack( value.asDouble() ); - break; + case FLOAT: + packer.pack( value.asDouble() ); + break; - case MAP_TyCon: - packer.packMapHeader( value.size() ); - for ( String s : value.keys() ) - { - packer.pack( s ); - packValue( value.get( s ) ); - } - break; + case MAP: + packer.packMapHeader( value.size() ); + for ( String s : value.keys() ) + { + packer.pack( s ); + packValue( value.get( s ) ); + } + break; - case LIST_TyCon: - packer.packListHeader( value.size() ); - for ( Value item : value.values() ) - { - packValue( item ); - } - break; + case LIST: + packer.packListHeader( value.size() ); + for ( Value item : value.values() ) + { + packValue( item ); + } + break; - case NODE_TyCon: - { - Node node = value.asNode(); - packNode( node ); - } - break; + case NODE: + { + Node node = value.asNode(); + packNode( node ); + } + break; - case RELATIONSHIP_TyCon: - { - Relationship rel = value.asRelationship(); - packer.packStructHeader( 5, RELATIONSHIP ); - packer.pack( rel.id() ); - packer.pack( rel.startNodeId() ); - packer.pack( rel.endNodeId() ); + case RELATIONSHIP: + { + Relationship rel = value.asRelationship(); + packer.packStructHeader( 5, RELATIONSHIP ); + packer.pack( rel.id() ); + packer.pack( rel.startNodeId() ); + packer.pack( rel.endNodeId() ); - packer.pack( rel.type() ); + packer.pack( rel.type() ); - packProperties( rel ); - } - break; + packProperties( rel ); + } + break; - case PATH_TyCon: - Path path = value.asPath(); - packer.packStructHeader( 3, PATH ); + case PATH: + Path path = value.asPath(); + packer.packStructHeader( 3, PATH ); - // Unique nodes - Map nodeIdx = Iterables.newLinkedHashMapWithSize( path.length() + 1 ); - for ( Node node : path.nodes() ) - { - if ( !nodeIdx.containsKey( node ) ) - { - nodeIdx.put( node, nodeIdx.size() ); - } - } - packer.packListHeader( nodeIdx.size() ); - for ( Node node : nodeIdx.keySet() ) + // Unique nodes + Map nodeIdx = Iterables.newLinkedHashMapWithSize( path.length() + 1 ); + for ( Node node : path.nodes() ) + { + if ( !nodeIdx.containsKey( node ) ) { - packNode( node ); + nodeIdx.put( node, nodeIdx.size() ); } + } + packer.packListHeader( nodeIdx.size() ); + for ( Node node : nodeIdx.keySet() ) + { + packNode( node ); + } - // Unique rels - Map relIdx = Iterables.newLinkedHashMapWithSize( path.length() ); - for ( Relationship rel : path.relationships() ) - { - if ( !relIdx.containsKey( rel ) ) - { - relIdx.put( rel, relIdx.size() + 1 ); - } - } - packer.packListHeader( relIdx.size() ); - for ( Relationship rel : relIdx.keySet() ) + // Unique rels + Map relIdx = Iterables.newLinkedHashMapWithSize( path.length() ); + for ( Relationship rel : path.relationships() ) + { + if ( !relIdx.containsKey( rel ) ) { - packer.packStructHeader( 3, UNBOUND_RELATIONSHIP ); - packer.pack( rel.id() ); - packer.pack( rel.type() ); - packProperties( rel ); + relIdx.put( rel, relIdx.size() + 1 ); } + } + packer.packListHeader( relIdx.size() ); + for ( Relationship rel : relIdx.keySet() ) + { + packer.packStructHeader( 3, UNBOUND_RELATIONSHIP ); + packer.pack( rel.id() ); + packer.pack( rel.type() ); + packProperties( rel ); + } - // Sequence - packer.packListHeader( path.length() * 2 ); - for ( Path.Segment seg : path ) - { - Relationship rel = seg.relationship(); - long relEndId = rel.endNodeId(); - long segEndId = seg.end().id(); - int size = relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ); - packer.pack( size ); - packer.pack( nodeIdx.get( seg.end() ) ); - } - break; + // Sequence + packer.packListHeader( path.length() * 2 ); + for ( Path.Segment seg : path ) + { + Relationship rel = seg.relationship(); + long relEndId = rel.endNodeId(); + long segEndId = seg.end().id(); + int size = relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ); + packer.pack( size ); + packer.pack( nodeIdx.get( seg.end() ) ); + } + break; - default: - throw new IOException( "Unknown type: " + value ); + default: + throw new IOException( "Unknown type: " + value ); } } @@ -514,14 +515,14 @@ Value unpackStruct( long size, byte type ) throws IOException switch ( type ) { case NODE: - ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, size ); InternalNode adapted = unpackNode(); return new NodeValue( adapted ); case RELATIONSHIP: - ensureCorrectStructSize( "RELATIONSHIP", 5, size ); + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 5, size ); return unpackRelationship(); case PATH: - ensureCorrectStructSize( "PATH", 3, size ); + ensureCorrectStructSize( TypeConstructor.PATH, 3, size ); return unpackPath(); default: throw new IOException( "Unknown struct type: " + type ); @@ -567,7 +568,7 @@ private Value unpackPath() throws IOException Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqNodes.length; i++ ) { - ensureCorrectStructSize( "NODE", NODE_FIELDS, unpacker.unpackStructHeader() ); + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() ); uniqNodes[i] = unpackNode(); } @@ -576,7 +577,7 @@ private Value unpackPath() throws IOException InternalRelationship[] uniqRels = new InternalRelationship[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqRels.length; i++ ) { - ensureCorrectStructSize( "RELATIONSHIP", 3, unpacker.unpackStructHeader() ); + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 3, unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); long id = unpacker.unpackLong(); String relType = unpacker.unpackString(); @@ -619,10 +620,11 @@ private Value unpackPath() throws IOException return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); } - void ensureCorrectStructSize( String structName, int expected, long actual ) + void ensureCorrectStructSize( TypeConstructor typeConstructor, int expected, long actual ) { if ( expected != actual ) { + String structName = typeConstructor.toString(); throw new ClientException( String.format( "Invalid message received, serialized %s structures should have %d fields, " + "received %s structure has %d fields.", structName, expected, structName, actual ) ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java index 0cd8f90ab4..880fde25f6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -19,27 +19,55 @@ package org.neo4j.driver.internal.messaging; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import org.neo4j.driver.internal.InternalPoint2D; import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.internal.value.InternalValue; -import org.neo4j.driver.internal.value.Point2DValue; -import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.types.Point2D; -import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Point; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; +import static java.time.ZoneOffset.UTC; +import static org.neo4j.driver.v1.Values.isoDuration; +import static org.neo4j.driver.v1.Values.point; +import static org.neo4j.driver.v1.Values.value; public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 { - private static final byte POINT_2D_STRUCT_TYPE = 'X'; - private static final byte POINT_3D_STRUCT_TYPE = 'Y'; + private static final byte DATE = 'D'; + private static final int DATE_STRUCT_SIZE = 1; + + private static final byte TIME = 'T'; + private static final int TIME_STRUCT_SIZE = 2; + + private static final byte LOCAL_TIME = 't'; + private static final int LOCAL_TIME_STRUCT_SIZE = 1; + + private static final byte LOCAL_DATE_TIME = 'd'; + private static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; + private static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + private static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + private static final int DATE_TIME_STRUCT_SIZE = 3; + + private static final byte DURATION = 'E'; + private static final int DURATION_TIME_STRUCT_SIZE = 4; + + private static final byte POINT_2D_STRUCT_TYPE = 'X'; private static final int POINT_2D_STRUCT_SIZE = 3; + + private static final byte POINT_3D_STRUCT_TYPE = 'Y'; private static final int POINT_3D_STRUCT_SIZE = 4; @Override @@ -68,21 +96,119 @@ private static class WriterV2 extends WriterV1 @Override void packInternalValue( InternalValue value ) throws IOException { - if ( value.typeConstructor() == POINT_2D_TyCon ) + TypeConstructor typeConstructor = value.typeConstructor(); + switch ( typeConstructor ) { - packPoint2D( value.asPoint2D() ); + case DATE: + packDate( value.asLocalDate() ); + break; + case TIME: + packTime( value.asOffsetTime() ); + break; + case LOCAL_TIME: + packLocalTime( value.asLocalTime() ); + break; + case LOCAL_DATE_TIME: + packLocalDateTime( value.asLocalDateTime() ); + break; + case DATE_TIME: + packZonedDateTime( value.asZonedDateTime() ); + break; + case DURATION: + packDuration( value.asIsoDuration() ); + break; + case POINT: + packPoint( value.asPoint() ); + break; + default: + super.packInternalValue( value ); } - else if ( value.typeConstructor() == POINT_3D_TyCon ) + } + + private void packDate( LocalDate localDate ) throws IOException + { + packer.packStructHeader( DATE_STRUCT_SIZE, DATE ); + packer.pack( localDate.toEpochDay() ); + } + + private void packTime( OffsetTime offsetTime ) throws IOException + { + OffsetTime offsetTimeUtc = offsetTime.withOffsetSameInstant( UTC ); + long nanoOfDayUtc = offsetTimeUtc.toLocalTime().toNanoOfDay(); + int offsetSeconds = offsetTime.getOffset().getTotalSeconds(); + + packer.packStructHeader( TIME_STRUCT_SIZE, TIME ); + packer.pack( nanoOfDayUtc ); + packer.pack( offsetSeconds ); + } + + private void packLocalTime( LocalTime localTime ) throws IOException + { + packer.packStructHeader( LOCAL_TIME_STRUCT_SIZE, LOCAL_TIME ); + packer.pack( localTime.toNanoOfDay() ); + } + + private void packLocalDateTime( LocalDateTime localDateTime ) throws IOException + { + long epochSecondUtc = localDateTime.toEpochSecond( UTC ); + int nano = localDateTime.getNano(); + + packer.packStructHeader( LOCAL_DATE_TIME_STRUCT_SIZE, LOCAL_DATE_TIME ); + packer.pack( epochSecondUtc ); + packer.pack( nano ); + } + + private void packZonedDateTime( ZonedDateTime zonedDateTime ) throws IOException + { + Instant instant = zonedDateTime.toInstant(); + ZoneId zone = zonedDateTime.getZone(); + + if ( zone instanceof ZoneOffset ) { - packPoint3D( value.asPoint3D() ); + int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds(); + + packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_OFFSET ); + packer.pack( instant.getEpochSecond() ); + packer.pack( instant.getNano() ); + packer.pack( offsetSeconds ); } else { - super.packInternalValue( value ); + String zoneId = zone.getId(); + + packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_ID ); + packer.pack( instant.getEpochSecond() ); + packer.pack( instant.getNano() ); + packer.pack( zoneId ); + } + } + + private void packDuration( IsoDuration duration ) throws IOException + { + packer.packStructHeader( DURATION_TIME_STRUCT_SIZE, DURATION ); + packer.pack( duration.months() ); + packer.pack( duration.days() ); + packer.pack( duration.seconds() ); + packer.pack( duration.nanoseconds() ); + } + + private void packPoint( Point point ) throws IOException + { + if ( point instanceof InternalPoint2D ) + { + packPoint2D( point ); + } + else if ( point instanceof InternalPoint3D ) + { + packPoint3D( point ); + } + else + { + throw new IOException( String.format( "Unknown type: type: %s, value: %s", point.getClass(), point.toString() ) ); } } - private void packPoint2D( Point2D point ) throws IOException + private void packPoint2D ( Point point ) throws IOException { packer.packStructHeader( POINT_2D_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); packer.pack( point.srid() ); @@ -90,7 +216,7 @@ private void packPoint2D( Point2D point ) throws IOException packer.pack( point.y() ); } - private void packPoint3D( Point3D point ) throws IOException + private void packPoint3D( Point point ) throws IOException { packer.packStructHeader( POINT_3D_STRUCT_SIZE, POINT_3D_STRUCT_TYPE ); packer.pack( point.srid() ); @@ -110,37 +236,115 @@ private static class ReaderV2 extends ReaderV1 @Override Value unpackStruct( long size, byte type ) throws IOException { - if ( type == POINT_2D_STRUCT_TYPE ) + switch ( type ) { - ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_2D_STRUCT_SIZE, size ); + case DATE: + ensureCorrectStructSize( TypeConstructor.DATE, DATE_STRUCT_SIZE, size ); + return unpackDate(); + case TIME: + ensureCorrectStructSize( TypeConstructor.TIME, TIME_STRUCT_SIZE, size ); + return unpackTime(); + case LOCAL_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_TIME, LOCAL_TIME_STRUCT_SIZE, size ); + return unpackLocalTime(); + case LOCAL_DATE_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_DATE_TIME, LOCAL_DATE_TIME_STRUCT_SIZE, size ); + return unpackLocalDateTime(); + case DATE_TIME_WITH_ZONE_OFFSET: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneOffset(); + case DATE_TIME_WITH_ZONE_ID: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneId(); + case DURATION: + ensureCorrectStructSize( TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size ); + return unpackDuration(); + case POINT_2D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT, POINT_2D_STRUCT_SIZE, size ); return unpackPoint2D(); - } - else if ( type == POINT_3D_STRUCT_TYPE ) - { - ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_3D_STRUCT_SIZE, size ); + case POINT_3D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT, POINT_3D_STRUCT_SIZE, size ); return unpackPoint3D(); - } - else - { + default: return super.unpackStruct( size, type ); } } + private Value unpackDate() throws IOException + { + long epochDay = unpacker.unpackLong(); + return value( LocalDate.ofEpochDay( epochDay ) ); + } + + private Value unpackTime() throws IOException + { + long nanoOfDayUtc = unpacker.unpackLong(); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + + Instant instant = Instant.ofEpochSecond( 0, nanoOfDayUtc ); + ZoneOffset offset = ZoneOffset.ofTotalSeconds( offsetSeconds ); + return value( OffsetTime.ofInstant( instant, offset ) ); + } + + private Value unpackLocalTime() throws IOException + { + long nanoOfDay = unpacker.unpackLong(); + return value( LocalTime.ofNanoOfDay( nanoOfDay ) ); + } + + private Value unpackLocalDateTime() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + return value( LocalDateTime.ofEpochSecond( epochSecondUtc, nano, UTC ) ); + } + + private Value unpackDateTimeWithZoneOffset() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + + Instant instant = Instant.ofEpochSecond( epochSecondUtc, nano ); + ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds( offsetSeconds ); + return value( ZonedDateTime.ofInstant( instant, zoneOffset ) ); + } + + private Value unpackDateTimeWithZoneId() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + String zoneIdString = unpacker.unpackString(); + + Instant instant = Instant.ofEpochSecond( epochSecondUtc, nano ); + ZoneId zoneId = ZoneId.of( zoneIdString ); + return value( ZonedDateTime.ofInstant( instant, zoneId ) ); + } + + private Value unpackDuration() throws IOException + { + long months = unpacker.unpackLong(); + long days = unpacker.unpackLong(); + long seconds = unpacker.unpackLong(); + long nanoseconds = unpacker.unpackLong(); + return isoDuration( months, days, seconds, nanoseconds ); + } + private Value unpackPoint2D() throws IOException { - long srid = unpacker.unpackLong(); + int srid = Math.toIntExact( unpacker.unpackLong() ); double x = unpacker.unpackDouble(); double y = unpacker.unpackDouble(); - return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + return point( srid, x, y ); } private Value unpackPoint3D() throws IOException { - long srid = unpacker.unpackLong(); + int srid = Math.toIntExact( unpacker.unpackLong() ); double x = unpacker.unpackDouble(); double y = unpacker.unpackDouble(); double z = unpacker.unpackDouble(); - return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + return point( srid, x, y, z ); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java index d184dee9aa..78ef958c63 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java @@ -22,21 +22,26 @@ import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; -import static org.neo4j.driver.internal.types.TypeConstructor.ANY_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.MAP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NODE_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NULL_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.PATH_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.ANY; +import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN; +import static org.neo4j.driver.internal.types.TypeConstructor.BYTES; +import static org.neo4j.driver.internal.types.TypeConstructor.DATE; +import static org.neo4j.driver.internal.types.TypeConstructor.DATE_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.DURATION; +import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT; +import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER; +import static org.neo4j.driver.internal.types.TypeConstructor.LIST; +import static org.neo4j.driver.internal.types.TypeConstructor.LOCAL_DATE_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.LOCAL_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.MAP; +import static org.neo4j.driver.internal.types.TypeConstructor.NODE; +import static org.neo4j.driver.internal.types.TypeConstructor.NULL; +import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER; +import static org.neo4j.driver.internal.types.TypeConstructor.PATH; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT; +import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP; +import static org.neo4j.driver.internal.types.TypeConstructor.STRING; +import static org.neo4j.driver.internal.types.TypeConstructor.TIME; /** * Utility class for determining and working with the Cypher types of values @@ -48,104 +53,97 @@ public class InternalTypeSystem implements TypeSystem { public static InternalTypeSystem TYPE_SYSTEM = new InternalTypeSystem(); - private final TypeRepresentation anyType = constructType( ANY_TyCon ); - private final TypeRepresentation booleanType = constructType( BOOLEAN_TyCon ); - private final TypeRepresentation bytesType = constructType( BYTES_TyCon ); - private final TypeRepresentation stringType = constructType( STRING_TyCon ); - private final TypeRepresentation numberType = constructType( NUMBER_TyCon ); - private final TypeRepresentation integerType = constructType( INTEGER_TyCon ); - private final TypeRepresentation floatType = constructType( FLOAT_TyCon ); - private final TypeRepresentation listType = constructType( LIST_TyCon ); - private final TypeRepresentation mapType = constructType( MAP_TyCon ); - private final TypeRepresentation nodeType = constructType( NODE_TyCon ); - private final TypeRepresentation relationshipType = constructType( RELATIONSHIP_TyCon ); - private final TypeRepresentation pathType = constructType( PATH_TyCon ); - private final TypeRepresentation point2dType = constructType( POINT_2D_TyCon ); - private final TypeRepresentation point3dType = constructType( POINT_3D_TyCon ); - private final TypeRepresentation nullType = constructType( NULL_TyCon ); + private final TypeRepresentation anyType = constructType( ANY ); + private final TypeRepresentation booleanType = constructType( BOOLEAN ); + private final TypeRepresentation bytesType = constructType( BYTES ); + private final TypeRepresentation stringType = constructType( STRING ); + private final TypeRepresentation numberType = constructType( NUMBER ); + private final TypeRepresentation integerType = constructType( INTEGER ); + private final TypeRepresentation floatType = constructType( FLOAT ); + private final TypeRepresentation listType = constructType( LIST ); + private final TypeRepresentation mapType = constructType( MAP ); + private final TypeRepresentation nodeType = constructType( NODE ); + private final TypeRepresentation relationshipType = constructType( RELATIONSHIP ); + private final TypeRepresentation pathType = constructType( PATH ); + private final TypeRepresentation pointType = constructType( POINT ); + private final TypeRepresentation dateType = constructType( DATE ); + private final TypeRepresentation timeType = constructType( TIME ); + private final TypeRepresentation localTimeType = constructType( LOCAL_TIME ); + private final TypeRepresentation localDateTimeType = constructType( LOCAL_DATE_TIME ); + private final TypeRepresentation dateTimeType = constructType( DATE_TIME ); + private final TypeRepresentation durationType = constructType( DURATION ); + private final TypeRepresentation nullType = constructType( NULL ); private InternalTypeSystem() { } - /** the Cypher type ANY */ @Override public Type ANY() { return anyType; } - /** the Cypher type BOOLEAN */ @Override public Type BOOLEAN() { return booleanType; } - /** the Cypher type BYTES */ @Override public Type BYTES() { return bytesType; } - /** the Cypher type STRING */ @Override public Type STRING() { return stringType; } - /** the Cypher type NUMBER */ @Override public Type NUMBER() { return numberType; } - /** the Cypher type INTEGER */ @Override public Type INTEGER() { return integerType; } - /** the Cypher type FLOAT */ @Override public Type FLOAT() { return floatType; } - /** the Cypher type LIST */ @Override public Type LIST() { return listType; } - /** the Cypher type MAP */ @Override public Type MAP() { return mapType; } - /** the Cypher type NODE */ @Override public Type NODE() { return nodeType; } - /** the Cypher type RELATIONSHIP */ @Override public Type RELATIONSHIP() { return relationshipType; } - /** the Cypher type PATH */ @Override public Type PATH() { @@ -153,18 +151,47 @@ public Type PATH() } @Override - public Type POINT_2D() + public Type POINT() { - return point2dType; + return pointType; } @Override - public Type POINT_3D() + public Type DATE() { - return point3dType; + return dateType; + } + + @Override + public Type TIME() + { + return timeType; + } + + @Override + public Type LOCAL_TIME() + { + return localTimeType; + } + + @Override + public Type LOCAL_DATE_TIME() + { + return localDateTimeType; + } + + @Override + public Type DATE_TIME() + { + return dateTimeType; + } + + @Override + public Type DURATION() + { + return durationType; } - /** the Cypher type NULL */ @Override public Type NULL() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java index 0a037f40d1..bc9361f0c3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java @@ -23,155 +23,55 @@ public enum TypeConstructor { - ANY_TyCon { - @Override - public String typeName() - { - return "ANY"; - } - - @Override - public boolean covers( Value value ) - { - return ! value.isNull(); - } - }, - BOOLEAN_TyCon { - @Override - public String typeName() - { - return "BOOLEAN"; - } - }, - - BYTES_TyCon { - @Override - public String typeName() - { - return "BYTES"; - } - }, - - STRING_TyCon { - @Override - public String typeName() - { - return "STRING"; - } - }, - - NUMBER_TyCon { - @Override - public boolean covers( Value value ) - { - TypeConstructor valueType = typeConstructorOf( value ); - return valueType == this || valueType == INTEGER_TyCon || valueType == FLOAT_TyCon; - } - - @Override - public String typeName() - { - return "NUMBER"; - } - }, - - INTEGER_TyCon { - @Override - public String typeName() - { - return "INTEGER"; - } - }, - - FLOAT_TyCon { - @Override - public String typeName() - { - return "FLOAT"; - } - }, - - LIST_TyCon { - @Override - public String typeName() - { - return "LIST"; - } - }, - - MAP_TyCon { - @Override - public String typeName() - { - return "MAP"; - } - - @Override - public boolean covers( Value value ) - { - TypeConstructor valueType = typeConstructorOf( value ); - return valueType == MAP_TyCon || valueType == NODE_TyCon || valueType == RELATIONSHIP_TyCon; - } - }, - - NODE_TyCon { - @Override - public String typeName() - { - return "NODE"; - } - }, - - RELATIONSHIP_TyCon + ANY { - @Override - public String typeName() - { - return "RELATIONSHIP"; - } - }, - - PATH_TyCon { - @Override - public String typeName() - { - return "PATH"; - } - }, - - POINT_2D_TyCon + @Override + public boolean covers( Value value ) + { + return !value.isNull(); + } + }, + BOOLEAN, + BYTES, + STRING, + NUMBER { @Override - public String typeName() + public boolean covers( Value value ) { - return "POINT"; + TypeConstructor valueType = typeConstructorOf( value ); + return valueType == this || valueType == INTEGER || valueType == FLOAT; } }, - - POINT_3D_TyCon + INTEGER, + FLOAT, + LIST, + MAP { @Override - public String typeName() + public boolean covers( Value value ) { - return "POINT"; + TypeConstructor valueType = typeConstructorOf( value ); + return valueType == MAP || valueType == NODE || valueType == RELATIONSHIP; } }, - - NULL_TyCon { - @Override - public String typeName() - { - return "NULL"; - } - }; + NODE, + RELATIONSHIP, + PATH, + POINT, + DATE, + TIME, + LOCAL_TIME, + LOCAL_DATE_TIME, + DATE_TIME, + DURATION, + NULL; private static TypeConstructor typeConstructorOf( Value value ) { - return ( (InternalValue) value ).typeConstructor(); + return ((InternalValue) value).typeConstructor(); } - public abstract String typeName(); - public boolean covers( Value value ) { return this == typeConstructorOf( value ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java index 69338c98e5..522640eb50 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java @@ -18,10 +18,10 @@ */ package org.neo4j.driver.internal.types; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Type; -import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.LIST; public class TypeRepresentation implements Type { @@ -41,12 +41,12 @@ public boolean isTypeOf( Value value ) @Override public String name() { - if (tyCon == LIST_TyCon) + if ( tyCon == LIST ) { return "LIST OF ANY?"; } - return tyCon.typeName(); + return tyCon.toString(); } public TypeConstructor constructor() diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/Format.java b/driver/src/main/java/org/neo4j/driver/internal/util/Format.java index ca8592eddf..cd14991982 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/Format.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/Format.java @@ -22,8 +22,6 @@ import java.util.Map; import java.util.Map.Entry; -import org.neo4j.driver.v1.util.Function; - public abstract class Format { private Format() @@ -31,8 +29,8 @@ private Format() throw new UnsupportedOperationException(); } - public static String formatPairs( Function printValue, - Map entries ) + // formats map using ':' as key-value separator instead of default '=' + public static String formatPairs( Map entries ) { Iterator> iterator = entries.entrySet().iterator(); switch ( entries.size() ) { @@ -41,19 +39,19 @@ public static String formatPairs( Function printValue, case 1: { - return String.format( "{%s}", keyValueString( iterator.next(), printValue ) ); + return String.format( "{%s}", keyValueString( iterator.next() ) ); } default: { StringBuilder builder = new StringBuilder(); builder.append( "{" ); - builder.append( keyValueString( iterator.next(), printValue ) ); + builder.append( keyValueString( iterator.next() ) ); while ( iterator.hasNext() ) { builder.append( ',' ); builder.append( ' ' ); - builder.append( keyValueString( iterator.next(), printValue ) ); + builder.append( keyValueString( iterator.next() ) ); } builder.append( "}" ); return builder.toString(); @@ -61,33 +59,8 @@ public static String formatPairs( Function printValue, } } - private static String keyValueString( Entry entry, Function printValue ) - { - return String.format("%s: %s", entry.getKey(), printValue.apply( entry.getValue() )); - } - - public static String formatElements( Function printValue, V[] elements ) + private static String keyValueString( Entry entry ) { - int elementCount = elements.length; - switch ( elementCount ) { - case 0: - return "[]"; - - case 1: - return String.format( "[%s]", printValue.apply( elements[0] ) ); - - default: - StringBuilder builder = new StringBuilder(); - builder.append("["); - builder.append( printValue.apply( elements[0] ) ); - for (int i = 1; i < elementCount; i++ ) - { - builder.append( ',' ); - builder.append( ' ' ); - builder.append( printValue.apply( elements[i] ) ); - } - builder.append("]"); - return builder.toString(); - } + return String.format( "%s: %s", entry.getKey(), String.valueOf( entry.getValue() ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java index 85b36d64d2..02d80202cc 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java @@ -21,7 +21,7 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -public abstract class BooleanValue extends ScalarValueAdapter +public abstract class BooleanValue extends ValueAdapter { private BooleanValue() { @@ -86,7 +86,7 @@ public boolean equals( Object obj ) } @Override - public String asLiteralString() + public String toString() { return "TRUE"; } @@ -126,7 +126,7 @@ public boolean equals( Object obj ) } @Override - public String asLiteralString() + public String toString() { return "FALSE"; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java index 8ac08d6e71..929e0fd8e1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java @@ -18,11 +18,11 @@ */ package org.neo4j.driver.internal.value; +import java.util.Arrays; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -import java.util.Arrays; - public class BytesValue extends ValueAdapter { private final byte[] val; @@ -89,7 +89,7 @@ public int hashCode() } @Override - public String toString(Format valueFormat) + public String toString() { StringBuilder s = new StringBuilder("#"); for (byte b : val) @@ -100,9 +100,6 @@ public String toString(Format valueFormat) } s.append(Integer.toHexString(b)); } - return maybeWithType( - valueFormat.includeType(), - s.toString() - ); + return s.toString(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java similarity index 63% rename from driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java rename to driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java index 4b49f58f1e..c33a46ffa1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java @@ -18,40 +18,27 @@ */ package org.neo4j.driver.internal.value; +import java.time.ZonedDateTime; + import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Type; -public class Point3DValue extends ValueAdapter +public class DateTimeValue extends ObjectValueAdapter { - private final Point3D point; - - public Point3DValue( Point3D point ) - { - this.point = point; - } - - @Override - public Point3D asPoint3D() - { - return point; - } - - @Override - public Object asObject() + public DateTimeValue( ZonedDateTime zonedDateTime ) { - return point; + super( zonedDateTime ); } @Override - public String toString( Format valueFormat ) + public ZonedDateTime asZonedDateTime() { - return maybeWithType( valueFormat.includeType(), point.toString() ); + return asObject(); } @Override public Type type() { - return InternalTypeSystem.TYPE_SYSTEM.POINT_3D(); + return InternalTypeSystem.TYPE_SYSTEM.DATE_TIME(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java similarity index 65% rename from driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java rename to driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java index 3609fccad6..29a02bb867 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java @@ -18,14 +18,27 @@ */ package org.neo4j.driver.internal.value; -public abstract class ScalarValueAdapter extends ValueAdapter +import java.time.LocalDate; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class DateValue extends ObjectValueAdapter { + public DateValue( LocalDate date ) + { + super( date ); + } + @Override - public abstract String asLiteralString(); + public LocalDate asLocalDate() + { + return asObject(); + } @Override - public String toString( Format valueFormat ) + public Type type() { - return maybeWithType( valueFormat.includeType(), asLiteralString() ); + return InternalTypeSystem.TYPE_SYSTEM.DATE(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java new file mode 100644 index 0000000000..4622f57bd5 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Type; + +public class DurationValue extends ObjectValueAdapter +{ + public DurationValue( IsoDuration duration ) + { + super( duration ); + } + + @Override + public IsoDuration asIsoDuration() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.DURATION(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java index fbcace4ab4..976f00a1c5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java @@ -24,7 +24,7 @@ import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.util.Function; -public abstract class EntityValueAdapter extends GraphValueAdapter +public abstract class EntityValueAdapter extends ObjectValueAdapter { protected EntityValueAdapter( V adapted ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java index fe6094b869..255cf8d913 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java @@ -19,8 +19,8 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.Type; public class FloatValue extends NumberValueAdapter { @@ -109,7 +109,7 @@ public int hashCode() } @Override - public String asLiteralString() + public String toString() { return Double.toString( val ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java index cbed10d8f7..1646e863f2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java @@ -19,8 +19,8 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.Type; public class IntegerValue extends NumberValueAdapter { @@ -78,7 +78,7 @@ public float asFloat() } @Override - public String asLiteralString() + public String toString() { return Long.toString( val ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java index a5a000c1a3..e186c72ef9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java @@ -20,34 +20,9 @@ import org.neo4j.driver.internal.AsValue; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.util.Function; import org.neo4j.driver.v1.Value; public interface InternalValue extends Value, AsValue { TypeConstructor typeConstructor(); - - String toString( Format valueFormat ); - - enum Format implements Function - { - VALUE_ONLY, - VALUE_WITH_TYPE; - - public boolean includeType() - { - return this != VALUE_ONLY; - } - - public Format inner() - { - return VALUE_ONLY; - } - - @Override - public String apply( Value value ) - { - return ( (InternalValue) value ).toString( this ); - } - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java index 4db362b6e4..edfcc8670f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java @@ -24,13 +24,11 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.v1.util.Function; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.types.Type; +import org.neo4j.driver.v1.util.Function; -import static org.neo4j.driver.internal.util.Format.formatElements; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; import static org.neo4j.driver.v1.Values.ofObject; public class ListValue extends ValueAdapter @@ -115,12 +113,6 @@ public void remove() }; } - @Override - public String asLiteralString() - { - return toString( VALUE_ONLY ); - } - @Override public Type type() { @@ -128,12 +120,9 @@ public Type type() } @Override - public String toString( Format valueFormat ) + public String toString() { - return maybeWithType( - valueFormat.includeType(), - formatElements( valueFormat.inner(), values ) - ); + return Arrays.toString( values ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java similarity index 64% rename from driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java rename to driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java index 75afa39d82..9fc569135d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java @@ -18,40 +18,27 @@ */ package org.neo4j.driver.internal.value; +import java.time.LocalDateTime; + import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Point2D; import org.neo4j.driver.v1.types.Type; -public class Point2DValue extends ValueAdapter +public class LocalDateTimeValue extends ObjectValueAdapter { - private final Point2D point; - - public Point2DValue( Point2D point ) - { - this.point = point; - } - - @Override - public Point2D asPoint2D() - { - return point; - } - - @Override - public Object asObject() + public LocalDateTimeValue( LocalDateTime localDateTime ) { - return point; + super( localDateTime ); } @Override - public String toString( Format valueFormat ) + public LocalDateTime asLocalDateTime() { - return maybeWithType( valueFormat.includeType(), point.toString() ); + return asObject(); } @Override public Type type() { - return InternalTypeSystem.TYPE_SYSTEM.POINT_2D(); + return InternalTypeSystem.TYPE_SYSTEM.LOCAL_DATE_TIME(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java new file mode 100644 index 0000000000..505a7af287 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import java.time.LocalTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class LocalTimeValue extends ObjectValueAdapter +{ + public LocalTimeValue( LocalTime time ) + { + super( time ); + } + + @Override + public LocalTime asLocalTime() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.LOCAL_TIME(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java index 7d08b85922..8152b0e63f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java @@ -28,9 +28,8 @@ import org.neo4j.driver.v1.util.Function; import static org.neo4j.driver.internal.util.Format.formatPairs; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; -import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.Values.ofObject; +import static org.neo4j.driver.v1.Values.ofValue; public class MapValue extends ValueAdapter { @@ -107,18 +106,9 @@ public Value get( String key ) } @Override - public String asLiteralString() - { - return toString( VALUE_ONLY ); - } - - @Override - public String toString( Format valueFormat ) + public String toString() { - return maybeWithType( - valueFormat.includeType(), - formatPairs( valueFormat.inner(), asMap( ofValue()) ) - ); + return formatPairs( asMap( ofValue() ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java index ff3f34ab9f..cc5fb3c839 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java @@ -19,12 +19,12 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Type; -public final class NullValue extends ScalarValueAdapter +public final class NullValue extends ValueAdapter { - public static Value NULL = new NullValue(); + public static final Value NULL = new NullValue(); private NullValue() { @@ -68,7 +68,7 @@ public int hashCode() } @Override - public String asLiteralString() + public String toString() { return "NULL"; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java index b4abf426e2..1a190e842d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java @@ -18,7 +18,7 @@ */ package org.neo4j.driver.internal.value; -public abstract class NumberValueAdapter extends ScalarValueAdapter +public abstract class NumberValueAdapter extends ValueAdapter { @Override public final V asObject() diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java similarity index 79% rename from driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java rename to driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java index 1c53e80269..d67d26a808 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java @@ -18,13 +18,15 @@ */ package org.neo4j.driver.internal.value; +import java.util.Objects; + import static java.lang.String.format; -public abstract class GraphValueAdapter extends ValueAdapter +public abstract class ObjectValueAdapter extends ValueAdapter { private final V adapted; - protected GraphValueAdapter( V adapted ) + protected ObjectValueAdapter( V adapted ) { if ( adapted == null ) { @@ -34,17 +36,11 @@ protected GraphValueAdapter( V adapted ) } @Override - public V asObject() + public final V asObject() { return adapted; } - @Override - public String toString( Format valueFormat ) - { - return maybeWithType( valueFormat.includeType(), adapted.toString() ); - } - @Override public boolean equals( Object o ) { @@ -56,10 +52,8 @@ public boolean equals( Object o ) { return false; } - - GraphValueAdapter values = (GraphValueAdapter) o; - return adapted.equals( values.adapted ); - + ObjectValueAdapter that = (ObjectValueAdapter) o; + return Objects.equals( adapted, that.adapted ); } @Override @@ -67,4 +61,10 @@ public int hashCode() { return adapted.hashCode(); } + + @Override + public String toString() + { + return adapted.toString(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java index f288a50ab3..eee940fbb8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java @@ -22,13 +22,14 @@ import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.types.Type; -public class PathValue extends GraphValueAdapter +public class PathValue extends ObjectValueAdapter { public PathValue( Path adapted ) { super( adapted ); } + @Override public Path asPath() { return asObject(); diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java similarity index 59% rename from driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java rename to driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java index a452fe1a23..efc59df4b8 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java @@ -16,16 +16,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.v1.types; +package org.neo4j.driver.internal.value; -import org.neo4j.driver.v1.util.Experimental; +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Type; -@Experimental -public interface Point2D +public class PointValue extends ObjectValueAdapter { - long srid(); + public PointValue( Point point ) + { + super( point ); + } - double x(); + @Override + public Point asPoint() + { + return asObject(); + } - double y(); + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.POINT(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java index 19c490d2f6..42b8ba6083 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java @@ -18,10 +18,12 @@ */ package org.neo4j.driver.internal.value; +import java.util.Objects; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -public class StringValue extends ScalarValueAdapter +public class StringValue extends ValueAdapter { private final String val; @@ -59,7 +61,7 @@ public String asString() } @Override - public String asLiteralString() + public String toString() { return String.format( "\"%s\"", val.replace( "\"", "\\\"" ) ); } @@ -70,7 +72,6 @@ public Type type() return InternalTypeSystem.TYPE_SYSTEM.STRING(); } - @SuppressWarnings("StringEquality") @Override public boolean equals( Object o ) { @@ -82,9 +83,8 @@ public boolean equals( Object o ) { return false; } - - StringValue values = (StringValue) o; - return val == values.val || val.equals( values.val ); + StringValue that = (StringValue) o; + return Objects.equals( val, that.val ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java similarity index 59% rename from driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java rename to driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java index 11b6cfce2d..69289418b0 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java @@ -16,18 +16,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.v1.types; +package org.neo4j.driver.internal.value; -import org.neo4j.driver.v1.util.Experimental; +import java.time.OffsetTime; -@Experimental -public interface Point3D -{ - long srid(); +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; - double x(); +public class TimeValue extends ObjectValueAdapter +{ + public TimeValue( OffsetTime time ) + { + super( time ); + } - double y(); + @Override + public OffsetTime asOffsetTime() + { + return asObject(); + } - double z(); + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.TIME(); + } } 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 7e976e92b6..3d1bcd7eb2 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 @@ -18,6 +18,11 @@ */ package org.neo4j.driver.internal.value; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -29,17 +34,15 @@ import org.neo4j.driver.v1.exceptions.value.Uncoercible; import org.neo4j.driver.v1.exceptions.value.Unsizable; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point2D; -import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.util.Function; -import static java.lang.String.format; import static java.util.Collections.emptyList; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; import static org.neo4j.driver.v1.Values.ofObject; import static org.neo4j.driver.v1.Values.ofValue; @@ -87,11 +90,6 @@ public String asString() throw new Uncoercible( type().name(), "Java String" ); } - public String asLiteralString() - { - throw new Uncoercible( type().name(), "Java String representation of Cypher literal" ); - } - @Override public long asLong() { @@ -189,15 +187,45 @@ public Relationship asRelationship() } @Override - public Point2D asPoint2D() + public LocalDate asLocalDate() + { + throw new Uncoercible( type().name(), "LocalDate" ); + } + + @Override + public OffsetTime asOffsetTime() + { + throw new Uncoercible( type().name(), "OffsetTime" ); + } + + @Override + public LocalTime asLocalTime() + { + throw new Uncoercible( type().name(), "LocalTime" ); + } + + @Override + public LocalDateTime asLocalDateTime() + { + throw new Uncoercible( type().name(), "LocalDateTime" ); + } + + @Override + public ZonedDateTime asZonedDateTime() + { + throw new Uncoercible( type().name(), "ZonedDateTime" ); + } + + @Override + public IsoDuration asIsoDuration() { - throw new Uncoercible( type().name(), "Point2D" ); + throw new Uncoercible( type().name(), "Duration" ); } @Override - public Point3D asPoint3D() + public Point asPoint() { - throw new Uncoercible( type().name(), "Point3D" ); + throw new Uncoercible( type().name(), "Point" ); } @Override @@ -242,26 +270,23 @@ public Iterable values( Function mapFunction ) throw new NotMultiValued( type().name() + " is not iterable" ); } - public String toString() + @Override + public final TypeConstructor typeConstructor() { - return toString( VALUE_ONLY ); + return ( (TypeRepresentation) type() ).constructor(); } - protected String maybeWithType( boolean includeType, String text ) - { - return includeType ? withType( text ) : text; - } + // Force implementation + @Override + public abstract boolean equals( Object obj ); - private String withType( String text ) - { - return format( "%s :: %s", text, type().name() ); - } + // Force implementation + @Override + public abstract int hashCode(); + // Force implementation @Override - public final TypeConstructor typeConstructor() - { - return ( (TypeRepresentation) type() ).constructor(); - } + public abstract String toString(); } 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 f974285711..162d4a5c39 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -18,6 +18,11 @@ */ package org.neo4j.driver.v1; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -25,12 +30,12 @@ import org.neo4j.driver.v1.exceptions.value.LossyCoercion; import org.neo4j.driver.v1.exceptions.value.Uncoercible; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; import org.neo4j.driver.v1.types.MapAccessor; import org.neo4j.driver.v1.types.MapAccessorWithDefaultValue; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point2D; -import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; @@ -291,19 +296,54 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Path asPath(); - Point2D asPoint2D(); + /** + * @return the value as a {@link LocalDate}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDate asLocalDate(); + + /** + * @return the value as a {@link OffsetTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + OffsetTime asOffsetTime(); + + /** + * @return the value as a {@link LocalTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalTime asLocalTime(); + + /** + * @return the value as a {@link LocalDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDateTime asLocalDateTime(); - Point3D asPoint3D(); + /** + * @return the value as a {@link ZonedDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + ZonedDateTime asZonedDateTime(); + + /** + * @return the value as a {@link IsoDuration}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + IsoDuration asIsoDuration(); + + /** + * @return the value as a {@link Point}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + Point asPoint(); - // Force implementation @Override boolean equals( Object other ); - // Force implementation @Override int hashCode(); - //Force implementation @Override String toString(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 96572329fe..0868efd307 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -18,6 +18,13 @@ */ package org.neo4j.driver.v1; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -28,24 +35,31 @@ import java.util.Map; import org.neo4j.driver.internal.AsValue; +import org.neo4j.driver.internal.InternalIsoDuration; import org.neo4j.driver.internal.InternalPoint2D; import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.internal.value.BytesValue; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.DateValue; +import org.neo4j.driver.internal.value.DurationValue; import org.neo4j.driver.internal.value.FloatValue; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; +import org.neo4j.driver.internal.value.LocalTimeValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NullValue; -import org.neo4j.driver.internal.value.Point2DValue; -import org.neo4j.driver.internal.value.Point3DValue; +import org.neo4j.driver.internal.value.PointValue; import org.neo4j.driver.internal.value.StringValue; +import org.neo4j.driver.internal.value.TimeValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.MapAccessor; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point2D; -import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.util.Function; @@ -55,7 +69,7 @@ /** * Utility for wrapping regular Java types and exposing them as {@link Value} * objects, and vice versa. - * + *

* The long set of {@code ofXXX} methods in this class are meant to be used as * arguments for methods like {@link Value#asList(Function)}, {@link Value#asMap(Function)}, * {@link Record#asMap(Function)} and so on. @@ -72,12 +86,12 @@ private Values() throw new UnsupportedOperationException(); } - @SuppressWarnings("unchecked") + @SuppressWarnings( "unchecked" ) public static Value value( Object value ) { if ( value == null ) { return NullValue.NULL; } - if ( value instanceof AsValue ) { return ( (AsValue) value ).asValue(); } + if ( value instanceof AsValue ) { return ((AsValue) value).asValue(); } if ( value instanceof Boolean ) { return value( (boolean) value ); } if ( value instanceof String ) { return value( (String) value ); } if ( value instanceof Character ) { return value( (char) value ); } @@ -87,9 +101,18 @@ public static Value value( Object value ) if ( value instanceof Integer ) { return value( (int) value ); } if ( value instanceof Double ) { return value( (double) value ); } if ( value instanceof Float ) { return value( (float) value ); } + if ( value instanceof LocalDate ) { return value( (LocalDate) value ); } + if ( value instanceof OffsetTime ) { return value( (OffsetTime) value ); } + if ( value instanceof LocalTime ) { return value( (LocalTime) value ); } + if ( value instanceof LocalDateTime ) { return value( (LocalDateTime) value ); } + if ( value instanceof ZonedDateTime ) { return value( (ZonedDateTime) value ); } + if ( value instanceof IsoDuration ) { return value( (IsoDuration) value ); } + if ( value instanceof Period ) { return value( (Period) value ); } + if ( value instanceof Duration ) { return value( (Duration) value ); } + if ( value instanceof Point ) { return value( (Point) value ); } if ( value instanceof List ) { return value( (List) value ); } - if ( value instanceof Map ) { return value( (Map) value ); } + if ( value instanceof Map ) { return value( (Map) value ); } if ( value instanceof Iterable ) { return value( (Iterable) value ); } if ( value instanceof Iterator ) { return value( (Iterator) value ); } @@ -101,12 +124,11 @@ public static Value value( Object value ) if ( value instanceof double[] ) { return value( (double[]) value ); } if ( value instanceof float[] ) { return value( (float[]) value ); } if ( value instanceof Value[] ) { return value( (Value[]) value ); } - if ( value instanceof Object[] ) { return value( Arrays.asList( (Object[]) value )); } + if ( value instanceof Object[] ) { return value( Arrays.asList( (Object[]) value ) ); } throw new ClientException( "Unable to convert " + value.getClass().getName() + " to Neo4j Value." ); } - public static Value[] values( final Object... input ) { Value[] values = new Value[input.length]; @@ -226,7 +248,10 @@ public static Value value( Iterator val ) return new ListValue( values.toArray( new Value[values.size()] ) ); } - public static Value value (final char val ) { return new StringValue( String.valueOf( val ) ); } + public static Value value( final char val ) + { + return new StringValue( String.valueOf( val ) ); + } public static Value value( final String val ) { @@ -263,14 +288,64 @@ public static Value value( final Map val ) return new MapValue( asValues ); } - public static Value point2D( long srid, double x, double y ) + public static Value value( LocalDate localDate ) + { + return new DateValue( localDate ); + } + + public static Value value( OffsetTime offsetTime ) + { + return new TimeValue( offsetTime ); + } + + public static Value value( LocalTime localTime ) + { + return new LocalTimeValue( localTime ); + } + + public static Value value( LocalDateTime localDateTime ) + { + return new LocalDateTimeValue( localDateTime ); + } + + public static Value value( ZonedDateTime zonedDateTime ) + { + return new DateTimeValue( zonedDateTime ); + } + + public static Value value( Period period ) { - return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + return value( new InternalIsoDuration( period ) ); } - public static Value point3D( long srid, double x, double y, double z ) + public static Value value( Duration duration ) { - return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + return value( new InternalIsoDuration( duration ) ); + } + + public static Value isoDuration( long months, long days, long seconds, long nanoseconds ) + { + return value( new InternalIsoDuration( months, days, seconds, nanoseconds ) ); + } + + private static Value value( IsoDuration duration ) + { + return new DurationValue( duration ); + } + + public static Value point( int srid, double x, double y ) + { + return value( new InternalPoint2D( srid, x, y ) ); + } + + private static Value value( Point point ) + { + return new PointValue( point ); + } + + public static Value point( int srid, double x, double y, double z ) + { + return value( new InternalPoint3D( srid, x, y, z ) ); } /** @@ -279,14 +354,14 @@ public static Value point3D( long srid, double x, double y, double z ) *

* Allowed parameter types are: *

    - *
  • {@link Integer}
  • - *
  • {@link Long}
  • - *
  • {@link Boolean}
  • - *
  • {@link Double}
  • - *
  • {@link Float}
  • - *
  • {@link String}
  • - *
  • {@link Map} with String keys and values being any type in this list
  • - *
  • {@link Collection} of any type in this list
  • + *
  • {@link Integer}
  • + *
  • {@link Long}
  • + *
  • {@link Boolean}
  • + *
  • {@link Double}
  • + *
  • {@link Float}
  • + *
  • {@link String}
  • + *
  • {@link Map} with String keys and values being any type in this list
  • + *
  • {@link Collection} of any type in this list
  • *
* * @param keysAndValues alternating sequence of keys and values @@ -309,39 +384,42 @@ public static Value parameters( Object... keysAndValues ) assertParameter( value ); map.put( keysAndValues[i].toString(), value( value ) ); } - return value(map); + return value( map ); } /** * The identity function for value conversion - returns the value untouched. + * * @return a function that returns the value passed into it - the identity function */ public static Function ofValue() { - return VALUE; + return val -> val; } /** * Converts values to objects using {@link Value#asObject()}. + * * @return a function that returns {@link Value#asObject()} of a {@link Value} */ public static Function ofObject() { - return OBJECT; + return Value::asObject; } /** * Converts values to {@link Number}. + * * @return a function that returns {@link Value#asNumber()} of a {@link Value} */ public static Function ofNumber() { - return NUMBER; + return Value::asNumber; } /** * Converts values to {@link String}. - * + *

* If you want to access a string you've retrieved from the database, this is * the right choice. If you want to print any value for human consumption, for * instance in a log, {@link #ofToString()} is the right choice. @@ -350,16 +428,16 @@ public static Function ofNumber() */ public static Function ofString() { - return STRING; + return Value::asString; } /** * Converts values using {@link Value#toString()}, a human-readable string * description of any value. - * + *

* This is different from {@link #ofString()}, which returns a java * {@link String} value from a database {@link TypeSystem#STRING()}. - * + *

* If you are wanting to print any value for human consumption, this is the * right choice. If you are wanting to access a string value stored in the * database, you should use {@link #ofString()}. @@ -368,297 +446,223 @@ public static Function ofString() */ public static Function ofToString() { - return TO_STRING; + return Value::toString; } /** * Converts values to {@link Integer}. + * * @return a function that returns {@link Value#asInt()} of a {@link Value} */ public static Function ofInteger() { - return INTEGER; + return Value::asInt; } /** * Converts values to {@link Long}. + * * @return a function that returns {@link Value#asLong()} of a {@link Value} */ public static Function ofLong() { - return LONG; + return Value::asLong; } /** * Converts values to {@link Float}. + * * @return a function that returns {@link Value#asFloat()} of a {@link Value} */ public static Function ofFloat() { - return FLOAT; + return Value::asFloat; } /** * Converts values to {@link Double}. + * * @return a function that returns {@link Value#asDouble()} of a {@link Value} */ public static Function ofDouble() { - return DOUBLE; + return Value::asDouble; } /** * Converts values to {@link Boolean}. + * * @return a function that returns {@link Value#asBoolean()} of a {@link Value} */ public static Function ofBoolean() { - return BOOLEAN; + return Value::asBoolean; } /** * Converts values to {@link Map}. + * * @return a function that returns {@link Value#asMap()} of a {@link Value} */ - public static Function> ofMap() + public static Function> ofMap() { - return MAP; + return MapAccessor::asMap; } /** * Converts values to {@link Map}, with the map values further converted using * the provided converter. + * * @param valueConverter converter to use for the values of the map * @param the type of values in the returned map * @return a function that returns {@link Value#asMap(Function)} of a {@link Value} */ - public static Function> ofMap( final Function valueConverter) + public static Function> ofMap( final Function valueConverter ) { - return new Function>() - { - public Map apply( Value val ) - { - return val.asMap(valueConverter); - } - }; + return val -> val.asMap( valueConverter ); } /** * Converts values to {@link Entity}. + * * @return a function that returns {@link Value#asEntity()} of a {@link Value} */ public static Function ofEntity() { - return ENTITY; + return Value::asEntity; } /** * Converts values to {@link Long entity id}. + * * @return a function that returns the id an entity {@link Value} */ - public static Function ofEntityId() + public static Function ofEntityId() { - return ENTITY_ID; + return val -> val.asEntity().id(); } /** * Converts values to {@link Node}. + * * @return a function that returns {@link Value#asNode()} of a {@link Value} */ public static Function ofNode() { - return NODE; + return Value::asNode; } /** * Converts values to {@link Relationship}. + * * @return a function that returns {@link Value#asRelationship()} of a {@link Value} */ public static Function ofRelationship() { - return RELATIONSHIP; + return Value::asRelationship; } /** * Converts values to {@link Path}. + * * @return a function that returns {@link Value#asPath()} of a {@link Value} */ public static Function ofPath() { - return PATH; + return Value::asPath; } - public static Function ofPoint2D() + /** + * Converts values to {@link LocalDate}. + * + * @return a function that returns {@link Value#asLocalDate()} of a {@link Value} + */ + public static Function ofLocalDate() { - return POINT_2D; + return Value::asLocalDate; } - public static Function ofPoint3D() + /** + * Converts values to {@link OffsetTime}. + * + * @return a function that returns {@link Value#asOffsetTime()} of a {@link Value} + */ + public static Function ofOffsetTime() { - return POINT_3D; + return Value::asOffsetTime; } /** - * Converts values to {@link List} of {@link Object}. - * @return a function that returns {@link Value#asList()} of a {@link Value} + * Converts values to {@link LocalTime}. + * + * @return a function that returns {@link Value#asLocalTime()} of a {@link Value} */ - public static Function> ofList() + public static Function ofLocalTime() { - return new Function>() - { - @Override - public List apply( Value value ) - { - return value.asList(); - } - }; + return Value::asLocalTime; } /** - * Converts values to {@link List} of T. - * @param innerMap converter for the values inside the list - * @param the type of values inside the list - * @return a function that returns {@link Value#asList(Function)} of a {@link Value} + * Converts values to {@link LocalDateTime}. + * + * @return a function that returns {@link Value#asLocalDateTime()} of a {@link Value} */ - public static Function> ofList( final Function innerMap ) + public static Function ofLocalDateTime() { - return new Function>() - { - @Override - public List apply( Value value ) - { - return value.asList( innerMap ); - } - }; + return Value::asLocalDateTime; } - private static final Function OBJECT = new Function() - { - public Object apply( Value val ) - { - return val.asObject(); - } - }; - private static final Function VALUE = new Function() - { - public Value apply( Value val ) - { - return val; - } - }; - private static final Function NUMBER = new Function() - { - public Number apply( Value val ) - { - return val.asNumber(); - } - }; - private static final Function STRING = new Function() + /** + * Converts values to {@link ZonedDateTime}. + * + * @return a function that returns {@link Value#asZonedDateTime()} of a {@link Value} + */ + public static Function ofZonedDateTime() { - public String apply( Value val ) - { - return val.asString(); - } - }; + return Value::asZonedDateTime; + } - private static final Function TO_STRING = new Function() - { - public String apply( Value val ) - { - return val.toString(); - } - }; - private static final Function INTEGER = new Function() - { - public Integer apply( Value val ) - { - return val.asInt(); - } - }; - private static final Function LONG = new Function() - { - public Long apply( Value val ) - { - return val.asLong(); - } - }; - private static final Function FLOAT = new Function() - { - public Float apply( Value val ) - { - return val.asFloat(); - } - }; - private static final Function DOUBLE = new Function() - { - public Double apply( Value val ) - { - return val.asDouble(); - } - }; - private static final Function BOOLEAN = new Function() - { - public Boolean apply( Value val ) - { - return val.asBoolean(); - } - }; - private static final Function> MAP = new Function>() - { - public Map apply( Value val ) - { - return val.asMap(); - } - }; - private static final Function ENTITY_ID = new Function() - { - public Long apply( Value val ) - { - return val.asEntity().id(); - } - }; - private static final Function ENTITY = new Function() - { - public Entity apply( Value val ) - { - return val.asEntity(); - } - }; - private static final Function NODE = new Function() - { - public Node apply( Value val ) - { - return val.asNode(); - } - }; - private static final Function RELATIONSHIP = new Function() + /** + * Converts values to {@link IsoDuration}. + * + * @return a function that returns {@link Value#asIsoDuration()} of a {@link Value} + */ + public static Function ofIsoDuration() { - public Relationship apply( Value val ) - { - return val.asRelationship(); - } - }; - private static final Function PATH = new Function() + return Value::asIsoDuration; + } + + /** + * Converts values to {@link Point}. + * + * @return a function that returns {@link Value#asPoint()} of a {@link Value} + */ + public static Function ofPoint() { - public Path apply( Value val ) - { - return val.asPath(); - } - }; - private static final Function POINT_2D = new Function() + return Value::asPoint; + } + + /** + * Converts values to {@link List} of {@link Object}. + * + * @return a function that returns {@link Value#asList()} of a {@link Value} + */ + public static Function> ofList() { - public Point2D apply( Value val ) - { - return val.asPoint2D(); - } - }; - private static final Function POINT_3D = new Function() + return Value::asList; + } + + /** + * Converts values to {@link List} of T. + * + * @param innerMap converter for the values inside the list + * @param the type of values inside the list + * @return a function that returns {@link Value#asList(Function)} of a {@link Value} + */ + public static Function> ofList( final Function innerMap ) { - public Point3D apply( Value val ) - { - return val.asPoint3D(); - } - }; + return value -> value.asList( innerMap ); + } private static void assertParameter( Object value ) { diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java new file mode 100644 index 0000000000..7521d44e09 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import java.time.temporal.TemporalAmount; + +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.util.Immutable; + +/** + * Represents temporal amount containing months, days, seconds and nanoseconds of the second. A duration can be negative. + *

+ * Value that represents a duration can be created using {@link Values#isoDuration(long, long, long, long)} method. + */ +@Immutable +public interface IsoDuration extends TemporalAmount +{ + /** + * Retrieve amount of months in this duration. + * + * @return number of months. + */ + long months(); + + /** + * Retrieve amount of days in this duration. + * + * @return number of days. + */ + long days(); + + /** + * Retrieve amount of seconds in this duration. + * + * @return number of seconds. + */ + long seconds(); + + /** + * Retrieve amount of nanoseconds of the second in this duration. + * + * @return number of nanoseconds. + */ + long nanoseconds(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point.java new file mode 100644 index 0000000000..9ff87f27d9 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.util.Immutable; + +/** + * Represents a single point in a particular coordinate reference system. + *

+ * Value that represents a point can be created using {@link Values#point(int, double, double)} + * or {@link Values#point(int, double, double, double)} method. + */ +@Immutable + +public interface Point +{ + /** + * Retrieve identifier of the coordinate reference system for this point. + * + * @return coordinate reference system identifier. + */ + int srid(); + + /** + * Retrieve {@code x} coordinate of this point. + * + * @return the {@code x} coordinate value. + */ + double x(); + + /** + * Retrieve {@code y} coordinate of this point. + * + * @return the {@code y} coordinate value. + */ + double y(); + + /** + * Retrieve {@code z} coordinate of this point. + * + * @return the {@code z} coordinate value or {@link Double#NaN} if not applicable + */ + double z(); + +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java index 3db423d053..6a49854ed4 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java @@ -53,9 +53,19 @@ public interface TypeSystem Type PATH(); - Type POINT_2D(); + Type POINT(); - Type POINT_3D(); + Type DATE(); + + Type TIME(); + + Type LOCAL_TIME(); + + Type LOCAL_DATE_TIME(); + + Type DATE_TIME(); + + Type DURATION(); Type NULL(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java new file mode 100644 index 0000000000..438f9d09dc --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import org.junit.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.temporal.Temporal; +import java.time.temporal.UnsupportedTemporalTypeException; + +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.YEARS; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class InternalIsoDurationTest +{ + @Test + public void shouldExposeMonths() + { + IsoDuration duration = newDuration( 42, 1, 2, 3 ); + assertEquals( 42, duration.months() ); + assertEquals( 42, duration.get( MONTHS ) ); + } + + @Test + public void shouldExposeDays() + { + IsoDuration duration = newDuration( 1, 42, 2, 3 ); + assertEquals( 42, duration.days() ); + assertEquals( 42, duration.get( DAYS ) ); + } + + @Test + public void shouldExposeSeconds() + { + IsoDuration duration = newDuration( 1, 2, 42, 3 ); + assertEquals( 42, duration.seconds() ); + assertEquals( 42, duration.get( SECONDS ) ); + } + + @Test + public void shouldExposeNanoseconds() + { + IsoDuration duration = newDuration( 1, 2, 3, 42 ); + assertEquals( 42, duration.nanoseconds() ); + assertEquals( 42, duration.get( NANOS ) ); + } + + @Test + public void shouldFailToGetUnsupportedTemporalUnit() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + + try + { + duration.get( YEARS ); + fail( "Exception expected" ); + } + catch ( UnsupportedTemporalTypeException ignore ) + { + } + } + + @Test + public void shouldExposeSupportedTemporalUnits() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + assertEquals( asList( MONTHS, DAYS, SECONDS, NANOS ), duration.getUnits() ); + } + + @Test + public void shouldAddTo() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + LocalDateTime dateTime = LocalDateTime.of( 1990, 1, 1, 0, 0, 0, 0 ); + + Temporal result = duration.addTo( dateTime ); + + assertEquals( LocalDateTime.of( 1990, 2, 3, 0, 0, 3, 4 ), result ); + } + + @Test + public void shouldSubtractFrom() + { + IsoDuration duration = newDuration( 4, 3, 2, 1 ); + LocalDateTime dateTime = LocalDateTime.of( 1990, 7, 19, 0, 0, 59, 999 ); + + Temporal result = duration.subtractFrom( dateTime ); + + assertEquals( LocalDateTime.of( 1990, 3, 16, 0, 0, 57, 998 ), result ); + } + + @Test + public void shouldImplementEqualsAndHashCode() + { + IsoDuration duration1 = newDuration( 1, 2, 3, 4 ); + IsoDuration duration2 = newDuration( 1, 2, 3, 4 ); + + assertEquals( duration1, duration2 ); + assertEquals( duration1.hashCode(), duration2.hashCode() ); + } + + @Test + public void shouldCreateFromPeriod() + { + Period period = Period.of( 3, 5, 12 ); + + InternalIsoDuration duration = new InternalIsoDuration( period ); + + assertEquals( period.toTotalMonths(), duration.months() ); + assertEquals( period.getDays(), duration.days() ); + assertEquals( 0, duration.seconds() ); + assertEquals( 0, duration.nanoseconds() ); + } + + @Test + public void shouldCreateFromDuration() + { + Duration duration = Duration.ofSeconds( 391784, 4879173 ); + + InternalIsoDuration isoDuration = new InternalIsoDuration( duration ); + + assertEquals( 0, isoDuration.months() ); + assertEquals( 0, isoDuration.days() ); + assertEquals( duration.getSeconds(), isoDuration.seconds() ); + assertEquals( duration.getNano(), isoDuration.nanoseconds() ); + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return new InternalIsoDuration( months, days, seconds, nanoseconds ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java b/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java index de6bc67f0b..29ff8368b5 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java @@ -23,6 +23,13 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.ArrayDeque; import java.util.Collection; import java.util.HashMap; @@ -32,18 +39,28 @@ import java.util.Map; import java.util.Set; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.DateValue; +import org.neo4j.driver.internal.value.DurationValue; import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; +import org.neo4j.driver.internal.value.LocalTimeValue; import org.neo4j.driver.internal.value.MapValue; +import org.neo4j.driver.internal.value.TimeValue; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; import org.neo4j.driver.v1.exceptions.ClientException; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Point; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.v1.Values.isoDuration; import static org.neo4j.driver.v1.Values.ofDouble; import static org.neo4j.driver.v1.Values.ofFloat; import static org.neo4j.driver.v1.Values.ofInteger; @@ -54,6 +71,7 @@ import static org.neo4j.driver.v1.Values.ofObject; import static org.neo4j.driver.v1.Values.ofString; import static org.neo4j.driver.v1.Values.ofToString; +import static org.neo4j.driver.v1.Values.point; import static org.neo4j.driver.v1.Values.value; import static org.neo4j.driver.v1.Values.values; @@ -287,4 +305,182 @@ public void shouldHandleIterator() throws Throwable // When/Then assertThat( val.asList(), Matchers.containsInAnyOrder( "hello", "world" )); } + + @Test + public void shouldCreateDateValueFromLocalDate() + { + LocalDate localDate = LocalDate.now(); + Value value = value( localDate ); + + assertThat( value, instanceOf( DateValue.class ) ); + assertEquals( localDate, value.asLocalDate() ); + } + + @Test + public void shouldCreateDateValue() + { + Object localDate = LocalDate.now(); + Value value = value( localDate ); + + assertThat( value, instanceOf( DateValue.class ) ); + assertEquals( localDate, value.asObject() ); + } + + @Test + public void shouldCreateTimeValueFromOffsetTime() + { + OffsetTime offsetTime = OffsetTime.now(); + Value value = value( offsetTime ); + + assertThat( value, instanceOf( TimeValue.class ) ); + assertEquals( offsetTime, value.asOffsetTime() ); + } + + @Test + public void shouldCreateTimeValue() + { + OffsetTime offsetTime = OffsetTime.now(); + Value value = value( offsetTime ); + + assertThat( value, instanceOf( TimeValue.class ) ); + assertEquals( offsetTime, value.asObject() ); + } + + @Test + public void shouldCreateLocalTimeValueFromLocalTime() + { + LocalTime localTime = LocalTime.now(); + Value value = value( localTime ); + + assertThat( value, instanceOf( LocalTimeValue.class ) ); + assertEquals( localTime, value.asLocalTime() ); + } + + @Test + public void shouldCreateLocalTimeValue() + { + LocalTime localTime = LocalTime.now(); + Value value = value( localTime ); + + assertThat( value, instanceOf( LocalTimeValue.class ) ); + assertEquals( localTime, value.asObject() ); + } + + @Test + public void shouldCreateLocalDateTimeValueFromLocalDateTime() + { + LocalDateTime localDateTime = LocalDateTime.now(); + Value value = value( localDateTime ); + + assertThat( value, instanceOf( LocalDateTimeValue.class ) ); + assertEquals( localDateTime, value.asLocalDateTime() ); + } + + @Test + public void shouldCreateLocalDateTimeValue() + { + LocalDateTime localDateTime = LocalDateTime.now(); + Value value = value( localDateTime ); + + assertThat( value, instanceOf( LocalDateTimeValue.class ) ); + assertEquals( localDateTime, value.asObject() ); + } + + @Test + public void shouldCreateDateTimeValueFromZonedDateTime() + { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + Value value = value( zonedDateTime ); + + assertThat( value, instanceOf( DateTimeValue.class ) ); + assertEquals( zonedDateTime, value.asZonedDateTime() ); + } + + @Test + public void shouldCreateDateTimeValue() + { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + Value value = value( zonedDateTime ); + + assertThat( value, instanceOf( DateTimeValue.class ) ); + assertEquals( zonedDateTime, value.asObject() ); + } + + @Test + public void shouldCreateIsoDurationValue() + { + Value value = isoDuration( 42_1, 42_2, 42_3, 42_4 ); + + assertThat( value, instanceOf( DurationValue.class ) ); + IsoDuration duration = value.asIsoDuration(); + + assertEquals( 42_1, duration.months() ); + assertEquals( 42_2, duration.days() ); + assertEquals( 42_3, duration.seconds() ); + assertEquals( 42_4, duration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromIsoDuration() + { + Value durationValue1 = isoDuration( 1, 2, 3, 4 ); + IsoDuration duration = durationValue1.asIsoDuration(); + Value durationValue2 = value( duration ); + + assertEquals( duration, durationValue1.asIsoDuration() ); + assertEquals( duration, durationValue2.asIsoDuration() ); + assertEquals( durationValue1, durationValue2 ); + } + + @Test + public void shouldCreateValueFromPeriod() + { + Period period = Period.of( 5, 11, 190 ); + + Value value = value( period ); + IsoDuration isoDuration = value.asIsoDuration(); + + assertEquals( period.toTotalMonths(), isoDuration.months() ); + assertEquals( period.getDays(), isoDuration.days() ); + assertEquals( 0, isoDuration.seconds() ); + assertEquals( 0, isoDuration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromDuration() + { + Duration duration = Duration.ofSeconds( 183951, 4384718937L ); + + Value value = value( duration ); + IsoDuration isoDuration = value.asIsoDuration(); + + assertEquals( 0, isoDuration.months() ); + assertEquals( 0, isoDuration.days() ); + assertEquals( duration.getSeconds(), isoDuration.seconds() ); + assertEquals( duration.getNano(), isoDuration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromPoint2D() + { + Value point2DValue1 = point( 1, 2, 3 ); + Point point2D = point2DValue1.asPoint(); + Value point2DValue2 = value( point2D ); + + assertEquals( point2D, point2DValue1.asPoint() ); + assertEquals( point2D, point2DValue2.asPoint() ); + assertEquals( point2DValue1, point2DValue2 ); + } + + @Test + public void shouldCreateValueFromPoint3D() + { + Value point3DValue1 = point( 1, 2, 3, 4 ); + Point point3D = point3DValue1.asPoint(); + Value point3DValue2 = value( point3D ); + + assertEquals( point3D, point3DValue1.asPoint() ); + assertEquals( point3D, point3DValue2.asPoint() ); + assertEquals( point3DValue1, point3DValue2 ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java index 80e4d9b6bf..23bdf9f6ee 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java @@ -23,6 +23,13 @@ import org.junit.Test; import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,7 +41,15 @@ import org.neo4j.driver.internal.util.ByteBufOutput; import org.neo4j.driver.internal.util.ThrowingConsumer; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Point; +import static java.time.Month.APRIL; +import static java.time.Month.AUGUST; +import static java.time.Month.DECEMBER; +import static java.time.ZoneOffset.UTC; +import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -43,9 +58,13 @@ import static org.mockito.Mockito.mock; import static org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1.MSG_RECORD; import static org.neo4j.driver.internal.packstream.PackStream.FLOAT_64; +import static org.neo4j.driver.internal.packstream.PackStream.INT_16; +import static org.neo4j.driver.internal.packstream.PackStream.INT_32; +import static org.neo4j.driver.internal.packstream.PackStream.INT_64; import static org.neo4j.driver.internal.packstream.PackStream.Packer; -import static org.neo4j.driver.v1.Values.point2D; -import static org.neo4j.driver.v1.Values.point3D; +import static org.neo4j.driver.internal.packstream.PackStream.STRING_8; +import static org.neo4j.driver.v1.Values.point; +import static org.neo4j.driver.v1.Values.value; import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; public class PackStreamMessageFormatV2Test @@ -71,9 +90,9 @@ public void shouldFailToCreateWriterWithoutByteArraySupport() public void shouldWritePoint2D() throws Exception { ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + MessageFormat.Writer writer = newWriter( buf ); - writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point2D( 42, 12.99, -180.0 ) ) ) ); + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ); // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y) int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; @@ -86,9 +105,9 @@ public void shouldWritePoint2D() throws Exception public void shouldWritePoint3D() throws Exception { ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + MessageFormat.Writer writer = newWriter( buf ); - writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point3D( 42, 0.51, 2.99, 100.123 ) ) ) ); + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ); // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y, FLOAT_64 header byte + double Z) int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; @@ -100,33 +119,269 @@ public void shouldWritePoint3D() throws Exception @Test public void shouldReadPoint2D() throws Exception { - InternalPoint2D point = new InternalPoint2D( 42, 120.65, -99.2 ); + Point point = new InternalPoint2D( 42, 120.65, -99.2 ); - testReadingOfPoint( packer -> + Object unpacked = packAndUnpackValue( packer -> { packer.packStructHeader( 3, (byte) 'X' ); packer.pack( point.srid() ); packer.pack( point.x() ); packer.pack( point.y() ); - }, point ); + } ); + + assertEquals( point, unpacked ); } @Test public void shouldReadPoint3D() throws Exception { - InternalPoint3D point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); + Point point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); - testReadingOfPoint( packer -> + Object unpacked = packAndUnpackValue( packer -> { packer.packStructHeader( 4, (byte) 'Y' ); packer.pack( point.srid() ); packer.pack( point.x() ); packer.pack( point.y() ); packer.pack( point.z() ); - }, point ); + } ); + + assertEquals( point, unpacked ); + } + + @Test + public void shouldWriteDate() throws Exception + { + LocalDate date = LocalDate.ofEpochDay( 2147483650L ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $date", singletonMap( "date", value( date ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, date.toEpochDay() ); + } + + @Test + public void shouldReadDate() throws Exception + { + LocalDate date = LocalDate.of( 2012, AUGUST, 3 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 1, (byte) 'D' ); + packer.pack( date.toEpochDay() ); + } ); + + assertEquals( date, unpacked ); + } + + @Test + public void shouldWriteTime() throws Exception + { + OffsetTime time = OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, time.withOffsetSameInstant( UTC ).toLocalTime().toNanoOfDay(), INT_32, time.getOffset().getTotalSeconds() ); + } + + @Test + public void shouldReadTime() throws Exception + { + OffsetTime time = OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 2, (byte) 'T' ); + packer.pack( time.withOffsetSameInstant( UTC ).toLocalTime().toNanoOfDay() ); + packer.pack( time.getOffset().getTotalSeconds() ); + } ); + + assertEquals( time, unpacked ); } - private void testReadingOfPoint( ThrowingConsumer packAction, Object expected ) throws Exception + @Test + public void shouldWriteLocalTime() throws Exception + { + LocalTime time = LocalTime.of( 12, 9, 18, 999_888 ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, time.toNanoOfDay() ); + } + + @Test + public void shouldReadLocalTime() throws Exception + { + LocalTime time = LocalTime.of( 12, 25 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 1, (byte) 't' ); + packer.pack( time.toNanoOfDay() ); + } ); + + assertEquals( time, unpacked ); + } + + @Test + public void shouldWriteLocalDateTime() throws Exception + { + LocalDateTime dateTime = LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, dateTime.toEpochSecond( UTC ), INT_16, (short) dateTime.getNano() ); + } + + @Test + public void shouldReadLocalDateTime() throws Exception + { + LocalDateTime dateTime = LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 2, (byte) 'd' ); + packer.pack( dateTime.toEpochSecond( UTC ) ); + packer.pack( dateTime.getNano() ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteZonedDateTimeWithOffset() throws Exception + { + ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( 9, 30 ); + ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, zoneOffset ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_32, (int) dateTime.toEpochSecond(), INT_16, (short) dateTime.getNano(), INT_32, zoneOffset.getTotalSeconds() ); + } + + @Test + public void shouldReadZonedDateTimeWithOffset() throws Exception + { + ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( -7, -15 ); + ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, zoneOffset ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 3, (byte) 'F' ); + packer.pack( dateTime.toInstant().getEpochSecond() ); + packer.pack( dateTime.toInstant().getNano() ); + packer.pack( zoneOffset.getTotalSeconds() ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteZonedDateTimeWithZoneId() throws Exception + { + String zoneName = "Europe/Stockholm"; + byte[] zoneNameBytes = zoneName.getBytes(); + ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( zoneName ) ); + + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - zoneNameBytes.length - Byte.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + List expectedBuf = new ArrayList<>( asList( + INT_32, (int) dateTime.toInstant().getEpochSecond(), + INT_16, (short) dateTime.toInstant().getNano(), + STRING_8, (byte) zoneNameBytes.length ) ); + + for ( byte b : zoneNameBytes ) + { + expectedBuf.add( b ); + } + + assertByteBufContains( tailSlice, expectedBuf.toArray( new Number[0] ) ); + } + + @Test + public void shouldReadZonedDateTimeWithZoneId() throws Exception + { + String zoneName = "Europe/Stockholm"; + ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( zoneName ) ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 3, (byte) 'f' ); + packer.pack( dateTime.toInstant().getEpochSecond() ); + packer.pack( dateTime.toInstant().getNano() ); + packer.pack( zoneName ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteDuration() throws Exception + { + Value durationValue = Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ); + IsoDuration duration = durationValue.asIsoDuration(); + + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $duration", singletonMap( "duration", durationValue ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, + INT_64, duration.months(), INT_32, (int) duration.days(), INT_16, (short) duration.seconds(), (byte) duration.nanoseconds() ); + } + + @Test + public void shouldReadDuration() throws Exception + { + Value durationValue = Values.isoDuration( 17, 22, 99, 15 ); + IsoDuration duration = durationValue.asIsoDuration(); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 4, (byte) 'E' ); + packer.pack( duration.months() ); + packer.pack( duration.days() ); + packer.pack( duration.seconds() ); + packer.pack( duration.nanoseconds() ); + } ); + + assertEquals( duration, unpacked ); + } + + private Object packAndUnpackValue( ThrowingConsumer packAction ) throws Exception { ByteBuf buf = Unpooled.buffer(); try @@ -146,7 +401,7 @@ private void testReadingOfPoint( ThrowingConsumer packAction, Object exp input.stop(); assertEquals( 1, values.size() ); - assertEquals( expected, values.get( 0 ).asObject() ); + return values.get( 0 ).asObject(); } finally { @@ -165,4 +420,9 @@ private static MessageHandler recordMemorizingHandler( List values ) thro } ).when( messageHandler ).handleRecordMessage( any() ); return messageHandler; } + + private MessageFormat.Writer newWriter( ByteBuf buf ) + { + return messageFormat.newWriter( new ByteBufOutput( buf ), true ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java index a0739d3b40..11a74d8a55 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java @@ -29,7 +29,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; - import static org.neo4j.driver.internal.value.BooleanValue.FALSE; import static org.neo4j.driver.internal.value.BooleanValue.TRUE; @@ -102,8 +101,8 @@ public void shouldNotBeNull() @Test public void shouldTypeAsBoolean() { - assertThat( TRUE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN_TyCon ) ); - assertThat( BooleanValue.FALSE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN_TyCon ) ); + assertThat( TRUE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN ) ); + assertThat( BooleanValue.FALSE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java index 898fcba6ea..0d4d441064 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java @@ -19,6 +19,7 @@ package org.neo4j.driver.internal.value; import org.junit.Test; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.v1.Value; @@ -87,7 +88,7 @@ public void shouldNotBeNull() public void shouldTypeAsString() { InternalValue value = new BytesValue( TEST_BYTES ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.BYTES_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.BYTES ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java new file mode 100644 index 0000000000..c5db59c4a9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DateTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + ZonedDateTime dateTime = ZonedDateTime.of( 1991, 2, 24, 12, 0, 0, 999_000, ZoneOffset.ofHours( -5 ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DATE_TIME(), dateTimeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + ZonedDateTime dateTime = ZonedDateTime.of( 2015, 8, 2, 23, 59, 59, 999_999, ZoneId.of( "Europe/Stockholm" ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asObject() ); + } + + @Test + public void shouldSupportAsZonedDateTime() + { + ZonedDateTime dateTime = ZonedDateTime.of( 1822, 9, 24, 9, 23, 57, 123, ZoneOffset.ofHoursMinutes( 12, 15 ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asZonedDateTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + ZonedDateTime dateTime = ZonedDateTime.now(); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + + try + { + dateTimeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java new file mode 100644 index 0000000000..a078e3d083 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalDate; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DateValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DATE(), dateValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( localDate, dateValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalDate() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( localDate, dateValue.asLocalDate() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + + try + { + dateValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java new file mode 100644 index 0000000000..4e812be596 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import org.neo4j.driver.internal.InternalIsoDuration; +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; +import org.neo4j.driver.v1.types.IsoDuration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DurationValueTest +{ + @Test + public void shouldHaveCorrectType() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DURATION(), durationValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + IsoDuration duration = newDuration( 11, 22, 33, 44 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( duration, durationValue.asObject() ); + } + + @Test + public void shouldSupportAsOffsetTime() + { + IsoDuration duration = newDuration( 111, 222, 333, 444 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( duration, durationValue.asIsoDuration() ); + } + + @Test + public void shouldNotSupportAsLong() + { + IsoDuration duration = newDuration( 1111, 2222, 3333, 4444 ); + DurationValue durationValue = new DurationValue( duration ); + + try + { + durationValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return new InternalIsoDuration( months, days, seconds, nanoseconds ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java index 55a66839a5..46f520972d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java @@ -24,9 +24,9 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -105,7 +105,7 @@ public void shouldNotBeNull() public void shouldTypeAsFloat() { InternalValue value = new FloatValue( 6.28 ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.FLOAT_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.FLOAT ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java index 7f34e03df6..3b96d93603 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java @@ -24,9 +24,9 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -110,7 +110,7 @@ public void shouldNotBeNull() public void shouldTypeAsInteger() { InternalValue value = new IntegerValue( 1L ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.INTEGER_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.INTEGER ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java new file mode 100644 index 0000000000..7dcc4d82b9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static java.time.Month.AUGUST; +import static java.time.Month.FEBRUARY; +import static java.time.Month.JANUARY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class LocalDateTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalDateTime dateTime = LocalDateTime.of( 1991, AUGUST, 24, 12, 0, 0 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.LOCAL_DATE_TIME(), dateTimeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalDateTime dateTime = LocalDateTime.of( 2015, FEBRUARY, 2, 23, 59, 59, 999_999 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalDateTime() + { + LocalDateTime dateTime = LocalDateTime.of( 1822, JANUARY, 24, 9, 23, 57, 123 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asLocalDateTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalDateTime dateTime = LocalDateTime.now(); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + + try + { + dateTimeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java new file mode 100644 index 0000000000..433a6287c5 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class LocalTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalTime time = LocalTime.of( 23, 59, 59 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.LOCAL_TIME(), timeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalTime time = LocalTime.of( 1, 17, 59, 999 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( time, timeValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalTime() + { + LocalTime time = LocalTime.of( 12, 59, 12, 999_999_999 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( time, timeValue.asLocalTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalTime time = LocalTime.now(); + LocalTimeValue timeValue = new LocalTimeValue( time ); + + try + { + timeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java index 42a3750825..f01c252267 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java @@ -67,7 +67,7 @@ public void shouldHaveCorrectType() throws Throwable public void shouldTypeAsNode() { InternalValue value = emptyNodeValue(); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.NODE_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.NODE ) ); } private NodeValue emptyNodeValue() 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 5c56bb4046..c2db722511 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 @@ -43,6 +43,6 @@ public void shouldBeNull() @Test public void shouldTypeAsNull() { - assertThat( ( (InternalValue) NullValue.NULL ).typeConstructor(), equalTo( TypeConstructor.NULL_TyCon ) ); + assertThat( ((InternalValue) NullValue.NULL).typeConstructor(), equalTo( TypeConstructor.NULL ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java index 0215d208c7..724a244887 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java @@ -25,12 +25,10 @@ import org.neo4j.driver.v1.Value; import static java.util.Collections.singletonMap; - import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; - import static org.neo4j.driver.v1.Values.value; public class RelationshipValueTest @@ -60,7 +58,7 @@ public void shouldNotBeNull() public void shouldTypeAsRelationship() { InternalValue value = emptyRelationshipValue(); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.RELATIONSHIP_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.RELATIONSHIP ) ); } private RelationshipValue emptyRelationshipValue() diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java index 3cdd84f958..dc2b4ccc28 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java @@ -22,8 +22,8 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -86,7 +86,7 @@ public void shouldNotBeNull() public void shouldTypeAsString() { InternalValue value = new StringValue( "Spongebob" ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.STRING_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.STRING ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java new file mode 100644 index 0000000000..315a843775 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.OffsetTime; +import java.time.ZoneOffset; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + OffsetTime time = OffsetTime.now().withOffsetSameInstant( ZoneOffset.ofHoursMinutes( 5, 30 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.TIME(), timeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + OffsetTime time = OffsetTime.of( 19, 0, 10, 1, ZoneOffset.ofHours( -3 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( time, timeValue.asObject() ); + } + + @Test + public void shouldSupportAsOffsetTime() + { + OffsetTime time = OffsetTime.of( 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( 2, 15 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( time, timeValue.asOffsetTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + OffsetTime time = OffsetTime.now().withOffsetSameInstant( ZoneOffset.ofHours( -5 ) ); + TimeValue timeValue = new TimeValue( time ); + + try + { + timeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java similarity index 68% rename from driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java rename to driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java index c6e92a52a8..2b7d059139 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java @@ -29,7 +29,7 @@ import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.util.TestNeo4jSession; import static java.util.Collections.singletonMap; @@ -37,13 +37,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; -import static org.neo4j.driver.v1.Values.ofPoint2D; -import static org.neo4j.driver.v1.Values.point2D; +import static org.neo4j.driver.v1.Values.ofPoint; +import static org.neo4j.driver.v1.Values.point; -public class PointsIT +public class SpatialTypesIT { - private static final long WGS_84_CRS_CODE = 4326; - private static final long CARTESIAN_CRS_CODE = 7203; + private static final int WGS_84_CRS_CODE = 4326; + private static final int CARTESIAN_CRS_CODE = 7203; private static final double DELTA = 0.00001; @Rule @@ -56,11 +56,11 @@ public void setUp() } @Test - public void shouldReceivePoint2D() + public void shouldReceivePoint() { Record record = session.run( "RETURN point({x: 39.111748, y:-76.775635})" ).single(); - Point2D point = record.get( 0 ).asPoint2D(); + Point point = record.get( 0 ).asPoint(); assertEquals( CARTESIAN_CRS_CODE, point.srid() ); assertEquals( 39.111748, point.x(), DELTA ); @@ -68,15 +68,15 @@ public void shouldReceivePoint2D() } @Test - public void shouldSendPoint2D() + public void shouldSendPoint() { - Value pointValue = point2D( WGS_84_CRS_CODE, 38.8719, 77.0563 ); + Value pointValue = point( WGS_84_CRS_CODE, 38.8719, 77.0563 ); Record record1 = session.run( "CREATE (n:Node {location: $point}) RETURN 42", singletonMap( "point", pointValue ) ).single(); assertEquals( 42, record1.get( 0 ).asInt() ); Record record2 = session.run( "MATCH (n:Node) RETURN n.location" ).single(); - Point2D point = record2.get( 0 ).asPoint2D(); + Point point = record2.get( 0 ).asPoint(); assertEquals( WGS_84_CRS_CODE, point.srid() ); assertEquals( 38.8719, point.x(), DELTA ); @@ -84,9 +84,9 @@ public void shouldSendPoint2D() } @Test - public void shouldSendAndReceivePoint2D() + public void shouldSendAndReceivePoint() { - testPoint2DSendAndReceive( point2D( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); + testPointSendAndReceive( point( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); } @Test @@ -95,10 +95,10 @@ public void shouldSendAndReceiveRandom2DPoints() Stream randomPoints = ThreadLocalRandom.current() .ints( 1_000, 0, 2 ) .mapToObj( idx -> idx % 2 == 0 - ? point2D( WGS_84_CRS_CODE, randomDouble(), randomDouble() ) - : point2D( CARTESIAN_CRS_CODE, randomDouble(), randomDouble() ) ); + ? point( WGS_84_CRS_CODE, randomDouble(), randomDouble() ) + : point( CARTESIAN_CRS_CODE, randomDouble(), randomDouble() ) ); - randomPoints.forEach( this::testPoint2DSendAndReceive ); + randomPoints.forEach( this::testPointSendAndReceive ); } @Test @@ -106,39 +106,39 @@ public void shouldSendAndReceiveRandom2DPointArrays() { Stream> randomPointLists = ThreadLocalRandom.current() .ints( 1_000, 0, 2 ) - .mapToObj( PointsIT::randomPoint2DList ); + .mapToObj( SpatialTypesIT::randomPointList ); - randomPointLists.forEach( this::testPoint2DListSendAndReceive ); + randomPointLists.forEach( this::testPointListSendAndReceive ); } - private void testPoint2DSendAndReceive( Value pointValue ) + private void testPointSendAndReceive( Value pointValue ) { - Point2D originalPoint = pointValue.asPoint2D(); + Point originalPoint = pointValue.asPoint(); Record record = session.run( "CREATE (n {point: $point}) return n.point", singletonMap( "point", pointValue ) ).single(); - Point2D receivedPoint = record.get( 0 ).asPoint2D(); + Point receivedPoint = record.get( 0 ).asPoint(); assertPoints2DEqual( originalPoint, receivedPoint ); } - private void testPoint2DListSendAndReceive( List points ) + private void testPointListSendAndReceive( List points ) { Record record = session.run( "CREATE (n {points: $points}) return n.points", singletonMap( "points", points ) ).single(); - List receivedPoints = record.get( 0 ).asList( ofPoint2D() ); + List receivedPoints = record.get( 0 ).asList( ofPoint() ); assertEquals( points.size(), receivedPoints.size() ); for ( int i = 0; i < points.size(); i++ ) { - assertPoints2DEqual( points.get( i ).asPoint2D(), receivedPoints.get( i ) ); + assertPoints2DEqual( points.get( i ).asPoint(), receivedPoints.get( i ) ); } } - private static List randomPoint2DList( int index ) + private static List randomPointList( int index ) { int size = ThreadLocalRandom.current().nextInt( 1, 100 ); - long srid = index % 2 == 0 ? CARTESIAN_CRS_CODE : WGS_84_CRS_CODE; + int srid = index % 2 == 0 ? CARTESIAN_CRS_CODE : WGS_84_CRS_CODE; return IntStream.range( 0, size ) - .mapToObj( i -> point2D( srid, randomDouble(), randomDouble() ) ) + .mapToObj( i -> point( srid, randomDouble(), randomDouble() ) ) .collect( toList() ); } @@ -147,7 +147,7 @@ private static double randomDouble() return ThreadLocalRandom.current().nextDouble( -180.0, 180 ); } - private static void assertPoints2DEqual( Point2D expected, Point2D actual ) + private static void assertPoints2DEqual( Point expected, Point actual ) { String message = "Expected: " + expected + " but was: " + actual; assertEquals( message, expected.srid(), actual.srid() ); diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java new file mode 100644 index 0000000000..67a2b14900 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.integration; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.function.Supplier; + +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.util.Function; +import org.neo4j.driver.v1.util.TemporalUtil; +import org.neo4j.driver.v1.util.TestNeo4jSession; + +import static java.time.Month.MARCH; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; +import static org.neo4j.driver.v1.Values.isoDuration; + +public class TemporalTypesIT +{ + private static final int RANDOM_VALUES_TO_TEST = 1_000; + + @Rule + public final TestNeo4jSession session = new TestNeo4jSession(); + + @Before + public void setUp() + { + assumeTrue( session.version().greaterThanOrEqual( v3_4_0 ) ); + } + + @Test + public void shouldSendDate() + { + testSendValue( LocalDate.now(), Value::asLocalDate ); + } + + @Test + public void shouldReceiveDate() + { + testReceiveValue( "RETURN date({year: 1995, month: 12, day: 4})", + LocalDate.of( 1995, 12, 4 ), + Value::asLocalDate ); + } + + @Test + public void shouldSendAndReceiveDate() + { + testSendAndReceiveValue( LocalDate.now(), Value::asLocalDate ); + } + + @Test + public void shouldSendAndReceiveRandomDate() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalDate, Value::asLocalDate ); + } + + @Test + public void shouldSendTime() + { + testSendValue( OffsetTime.now(), Value::asOffsetTime ); + } + + @Test + public void shouldReceiveTime() + { + testReceiveValue( "RETURN time({hour: 23, minute: 19, second: 55, timezone:'-07:00'})", + OffsetTime.of( 23, 19, 55, 0, ZoneOffset.ofHours( -7 ) ), + Value::asOffsetTime ); + } + + @Test + public void shouldSendAndReceiveTime() + { + testSendAndReceiveValue( OffsetTime.now(), Value::asOffsetTime ); + } + + @Test + public void shouldSendAndReceiveRandomTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomOffsetTime, Value::asOffsetTime ); + } + + @Test + public void shouldSendLocalTime() + { + testSendValue( LocalTime.now(), Value::asLocalTime ); + } + + @Test + public void shouldReceiveLocalTime() + { + testReceiveValue( "RETURN localtime({hour: 22, minute: 59, second: 10, nanosecond: 999999})", + LocalTime.of( 22, 59, 10, 999_999 ), + Value::asLocalTime ); + } + + @Test + public void shouldSendAndReceiveLocalTime() + { + testSendAndReceiveValue( LocalTime.now(), Value::asLocalTime ); + } + + @Test + public void shouldSendAndReceiveRandomLocalTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalTime, Value::asLocalTime ); + } + + @Test + public void shouldSendLocalDateTime() + { + testSendValue( LocalDateTime.now(), Value::asLocalDateTime ); + } + + @Test + public void shouldReceiveLocalDateTime() + { + testReceiveValue( "RETURN localdatetime({year: 1899, month: 3, day: 20, hour: 12, minute: 17, second: 13, nanosecond: 999})", + LocalDateTime.of( 1899, MARCH, 20, 12, 17, 13, 999 ), + Value::asLocalDateTime ); + } + + @Test + public void shouldSendAndReceiveLocalDateTime() + { + testSendAndReceiveValue( LocalDateTime.now(), Value::asLocalDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomLocalDateTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalDateTime, Value::asLocalDateTime ); + } + + @Test + public void shouldSendDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( -4, -15 ); + testSendValue( ZonedDateTime.of( 1845, 3, 25, 19, 15, 45, 22, offset ), Value::asZonedDateTime ); + } + + @Test + public void shouldReceiveDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( 3, 30 ); + testReceiveValue( "RETURN datetime({year:1984, month:10, day:11, hour:21, minute:30, second:34, timezone:'+03:30'})", + ZonedDateTime.of( 1984, 10, 11, 21, 30, 34, 0, offset ), + Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( -7, -15 ); + testSendAndReceiveValue( ZonedDateTime.of( 2017, 3, 9, 11, 12, 13, 14, offset ), Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomDateTimeWithZoneOffset() + { + testSendAndReceiveRandomValues( TemporalUtil::randomZonedDateTimeWithOffset, Value::asZonedDateTime ); + } + + @Test + public void shouldSendDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/Stockholm" ); + testSendValue( ZonedDateTime.of( 2049, 9, 11, 19, 10, 40, 20, zoneId ), Value::asZonedDateTime ); + } + + @Test + public void shouldReceiveDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/London" ); + testReceiveValue( "RETURN datetime({year:2000, month:1, day:1, hour:9, minute:5, second:1, timezone:'Europe/London'})", + ZonedDateTime.of( 2000, 1, 1, 9, 5, 1, 0, zoneId ), + Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/Stockholm" ); + testSendAndReceiveValue( ZonedDateTime.of( 2099, 12, 29, 12, 59, 59, 59, zoneId ), Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomDateTimeWithZoneId() + { + testSendAndReceiveRandomValues( TemporalUtil::randomZonedDateTimeWithZoneId, Value::asZonedDateTime ); + } + + @Test + public void shouldSendDuration() + { + testSendValue( newDuration( 8, 12, 90, 8 ), Value::asIsoDuration ); + } + + @Test + public void shouldReceiveDuration() + { + testReceiveValue( "RETURN duration({months: 13, days: 40, seconds: 12, nanoseconds: 999})", + newDuration( 13, 40, 12, 999 ), + Value::asIsoDuration ); + } + + @Test + public void shouldSendAndReceiveDuration() + { + testSendAndReceiveValue( newDuration( 7, 7, 88, 999_999 ), Value::asIsoDuration ); + } + + @Test + public void shouldSendAndReceiveRandomDuration() + { + testSendAndReceiveRandomValues( TemporalUtil::randomDuration, Value::asIsoDuration ); + } + + private void testSendAndReceiveRandomValues( Supplier supplier, Function converter ) + { + for ( int i = 0; i < RANDOM_VALUES_TO_TEST; i++ ) + { + testSendAndReceiveValue( supplier.get(), converter ); + } + } + + private void testSendValue( T value, Function converter ) + { + Record record1 = session.run( "CREATE (n:Node {value: $value}) RETURN 42", singletonMap( "value", value ) ).single(); + assertEquals( 42, record1.get( 0 ).asInt() ); + + Record record2 = session.run( "MATCH (n:Node) RETURN n.value" ).single(); + assertEquals( value, converter.apply( record2.get( 0 ) ) ); + } + + private void testReceiveValue( String query, T expectedValue, Function converter ) + { + Record record = session.run( query ).single(); + assertEquals( expectedValue, converter.apply( record.get( 0 ) ) ); + } + + private void testSendAndReceiveValue( T value, Function converter ) + { + Record record = session.run( "CREATE (n:Node {value: $value}) RETURN n.value", singletonMap( "value", value ) ).single(); + assertEquals( value, converter.apply( record.get( 0 ) ) ); + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return isoDuration( months, days, seconds, nanoseconds ).asIsoDuration(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java b/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java index 2029161a59..5cfe5edff7 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java +++ b/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java @@ -22,9 +22,9 @@ import org.neo4j.driver.internal.InternalRelationship; import org.neo4j.driver.internal.value.RelationshipValue; +import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Relationship; -import org.neo4j.driver.v1.Value; public class TestRelationshipValue extends RelationshipValue implements Entity { diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java new file mode 100644 index 0000000000..7096780caa --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.util; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.ValueRange; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.neo4j.driver.internal.InternalIsoDuration; +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +public final class TemporalUtil +{ + private TemporalUtil() + { + } + + public static LocalDate randomLocalDate() + { + return LocalDate.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ) ); + } + + public static OffsetTime randomOffsetTime() + { + ZoneOffset offset = randomZoneOffset(); + return OffsetTime.of( random( HOUR_OF_DAY ), random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ), offset ); + } + + public static LocalTime randomLocalTime() + { + return LocalTime.of( random( HOUR_OF_DAY ), random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ) ); + } + + public static LocalDateTime randomLocalDateTime() + { + return LocalDateTime.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ), random( HOUR_OF_DAY ), + random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ) ); + } + + public static ZonedDateTime randomZonedDateTimeWithOffset() + { + return randomZonedDateTime( randomZoneOffset() ); + } + + public static ZonedDateTime randomZonedDateTimeWithZoneId() + { + return randomZonedDateTime( randomZoneId() ); + } + + public static IsoDuration randomDuration() + { + int sign = random().nextBoolean() ? 1 : -1; // duration can be negative + return new InternalIsoDuration( sign * randomInt(), sign * randomInt(), sign * randomInt(), sign * Math.abs( random( NANO_OF_SECOND ) ) ); + } + + private static ZonedDateTime randomZonedDateTime( ZoneId zoneId ) + { + return ZonedDateTime.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ), random( HOUR_OF_DAY ), + random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ), zoneId ); + } + + private static ZoneOffset randomZoneOffset() + { + int min = ZoneOffset.MIN.getTotalSeconds(); + int max = ZoneOffset.MAX.getTotalSeconds(); + return ZoneOffset.ofTotalSeconds( random().nextInt( min, max ) ); + } + + private static ZoneId randomZoneId() + { + Set availableZoneIds = ZoneId.getAvailableZoneIds(); + int randomIndex = random().nextInt( availableZoneIds.size() ); + int index = 0; + for ( String id : availableZoneIds ) + { + if ( index == randomIndex ) + { + return ZoneId.of( id ); + } + else + { + index++; + } + } + throw new AssertionError( "Unable to pick random ZoneId from the set of available ids: " + availableZoneIds ); + } + + private static int random( ChronoField field ) + { + ValueRange range = field.range(); + long min = range.getMinimum(); + long max = range.getSmallestMaximum(); + long value = random().nextLong( min, max ); + return Math.toIntExact( value ); + } + + private static int randomInt() + { + return random().nextInt( 0, Integer.MAX_VALUE ); + } + + private static ThreadLocalRandom random() + { + return ThreadLocalRandom.current(); + } +}