diff --git a/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java b/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java index aa820aaf..c1331544 100644 --- a/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java +++ b/src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java @@ -97,6 +97,7 @@ public DefaultCodecs(ByteBufAllocator byteBufAllocator) { //Geometry new CircleCodec(byteBufAllocator), + new LineSegmentCodec(byteBufAllocator), new PointCodec(byteBufAllocator) )); } diff --git a/src/main/java/io/r2dbc/postgresql/codec/LineSegment.java b/src/main/java/io/r2dbc/postgresql/codec/LineSegment.java new file mode 100644 index 00000000..cab435de --- /dev/null +++ b/src/main/java/io/r2dbc/postgresql/codec/LineSegment.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 the original author or authors. + * + * 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 + * + * https://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 io.r2dbc.postgresql.codec; + +import io.r2dbc.postgresql.util.Assert; + +public final class LineSegment { + + private final Point point1; + + private final Point point2; + + public LineSegment(Point point1, Point point2) { + this.point1 = Assert.requireNonNull(point1, "First Point must not be null"); + this.point2 = Assert.requireNonNull(point2, "Second Point must not be null"); + } + + public LineSegment(double x1, double y1, double x2, double y2) { + this.point1 = Point.of(x1, y1); + this.point2 = Point.of(x2, y2); + } + + public Point getPoint1() { + return point1; + } + + public Point getPoint2() { + return point2; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof LineSegment) { + LineSegment lineSegment = (LineSegment) obj; + return (lineSegment.point1.equals(this.point1) && lineSegment.point2.equals(this.point2)) + || (lineSegment.point1.equals(this.point2) && lineSegment.point2.equals(this.point1)); + } + return false; + } + + @Override + public int hashCode() { + return point1.hashCode() ^ point2.hashCode(); + } + + @Override + public String toString() { + return "[" + this.point1.toString() + "," + this.point2.toString() + "]"; + } + +} diff --git a/src/main/java/io/r2dbc/postgresql/codec/LineSegmentCodec.java b/src/main/java/io/r2dbc/postgresql/codec/LineSegmentCodec.java new file mode 100644 index 00000000..eddffe91 --- /dev/null +++ b/src/main/java/io/r2dbc/postgresql/codec/LineSegmentCodec.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 the original author or authors. + * + * 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 + * + * https://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 io.r2dbc.postgresql.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.r2dbc.postgresql.client.Parameter; +import io.r2dbc.postgresql.message.Format; +import io.r2dbc.postgresql.type.PostgresqlObjectId; +import io.r2dbc.postgresql.util.Assert; +import io.r2dbc.postgresql.util.ByteBufUtils; +import reactor.util.annotation.Nullable; + +import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY; +import static io.r2dbc.postgresql.type.PostgresqlObjectId.LSEG; + +final class LineSegmentCodec extends AbstractCodec { + + private final ByteBufAllocator byteBufAllocator; + + LineSegmentCodec(ByteBufAllocator byteBufAllocator) { + super(LineSegment.class); + this.byteBufAllocator = Assert.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null"); + } + + @Override + boolean doCanDecode(PostgresqlObjectId type, @Nullable Format format) { + Assert.requireNonNull(type, "type must not be null"); + + return LSEG == type; + } + + @Override + LineSegment doDecode(ByteBuf buffer, PostgresqlObjectId dataType, Format format, Class type) { + Assert.requireNonNull(buffer, "byteBuf must not be null"); + Assert.requireNonNull(type, "type must not be null"); + Assert.requireNonNull(format, "format must not be null"); + + if (format == FORMAT_BINARY) { + double x1 = buffer.readDouble(); + double y1 = buffer.readDouble(); + double x2 = buffer.readDouble(); + double y2 = buffer.readDouble(); + return new LineSegment(Point.of(x1, y1), Point.of(x2, y2)); + } + + String decodedAsString = ByteBufUtils.decode(buffer); + String parenRemovedVal = decodedAsString.replaceAll("[()]", "") + .replace("[", "") + .replace("]", "") + .trim(); + String[] coordinatesAsString = parenRemovedVal.split(","); + double x1 = Double.parseDouble(coordinatesAsString[0]); + double y1 = Double.parseDouble(coordinatesAsString[1]); + double x2 = Double.parseDouble(coordinatesAsString[2]); + double y2 = Double.parseDouble(coordinatesAsString[3]); + return new LineSegment(Point.of(x1, y1), Point.of(x2, y2)); + } + + @Override + Parameter doEncode(LineSegment value) { + Assert.requireNonNull(value, "value must not be null"); + Point p1 = value.getPoint1(); + Point p2 = value.getPoint2(); + return create(LSEG, FORMAT_BINARY, () -> this.byteBufAllocator + .buffer(lengthInBytes()) + .writeDouble(p1.getX()) + .writeDouble(p1.getY()) + .writeDouble(p2.getX()) + .writeDouble(p2.getY())); + } + + @Override + public Parameter encodeNull() { + return createNull(LSEG, FORMAT_BINARY); + } + + int lengthInBytes() { + return 32; + } + +} diff --git a/src/main/java/io/r2dbc/postgresql/type/PostgresqlObjectId.java b/src/main/java/io/r2dbc/postgresql/type/PostgresqlObjectId.java index 190eca73..a5c465eb 100644 --- a/src/main/java/io/r2dbc/postgresql/type/PostgresqlObjectId.java +++ b/src/main/java/io/r2dbc/postgresql/type/PostgresqlObjectId.java @@ -188,9 +188,9 @@ public enum PostgresqlObjectId { LINE(628), /** - * The line segment object id + * The line segment(lseg) object id */ - LINE_SEGMENT(601), + LSEG(601), /** * The money object id. diff --git a/src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java b/src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java index 5dc55b24..0c61fd49 100644 --- a/src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java +++ b/src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java @@ -24,6 +24,7 @@ import io.r2dbc.postgresql.codec.Circle; import io.r2dbc.postgresql.codec.EnumCodec; import io.r2dbc.postgresql.codec.Json; +import io.r2dbc.postgresql.codec.LineSegment; import io.r2dbc.postgresql.codec.Point; import io.r2dbc.spi.Blob; import io.r2dbc.spi.Clob; @@ -334,6 +335,12 @@ void jsonb() { }, "JSONB"); } + @Test + void lineSegment() { + testCodec(LineSegment.class, new LineSegment(Point.of(1.12, 2.12), Point.of(3.12, 4.12)), "LSEG"); + testCodec(LineSegment.class, new LineSegment(Point.of(Double.MIN_VALUE, Double.MIN_VALUE), Point.of(Double.MAX_VALUE, Double.MAX_VALUE)), "LSEG"); + } + @Test void localDate() { testCodec(LocalDate.class, LocalDate.now(), "DATE"); diff --git a/src/test/java/io/r2dbc/postgresql/codec/LineSegmentCodecUnitTests.java b/src/test/java/io/r2dbc/postgresql/codec/LineSegmentCodecUnitTests.java new file mode 100644 index 00000000..7906c834 --- /dev/null +++ b/src/test/java/io/r2dbc/postgresql/codec/LineSegmentCodecUnitTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 the original author or authors. + * + * 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 + * + * https://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 io.r2dbc.postgresql.codec; + +import io.netty.buffer.ByteBuf; +import io.r2dbc.postgresql.client.Parameter; +import io.r2dbc.postgresql.client.ParameterAssert; +import org.junit.jupiter.api.Test; + +import static io.r2dbc.postgresql.client.Parameter.NULL_VALUE; +import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY; +import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT; +import static io.r2dbc.postgresql.type.PostgresqlObjectId.LSEG; +import static io.r2dbc.postgresql.type.PostgresqlObjectId.VARCHAR; +import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Unit tests for {@link LineSegmentCodec}. + */ +final class LineSegmentCodecUnitTests { + + @Test + void constructorNoByteBufAllocator() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(null)) + .withMessage("byteBufAllocator must not be null"); + } + + @Test + void doCanDecodeNoType() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(TEST).doCanDecode(null, FORMAT_BINARY)) + .withMessage("type must not be null"); + } + + @Test + void doCanDecode() { + LineSegmentCodec codec = new LineSegmentCodec(TEST); + + assertThat(codec.doCanDecode(VARCHAR, FORMAT_BINARY)).isFalse(); + assertThat(codec.doCanDecode(LSEG, FORMAT_TEXT)).isTrue(); + assertThat(codec.doCanDecode(LSEG, FORMAT_BINARY)).isTrue(); + } + + @Test + void doDecodeNoByteBuf() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(TEST).doDecode(null, LSEG, FORMAT_BINARY, LineSegment.class)) + .withMessage("byteBuf must not be null"); + } + + @Test + void doDecodeNoType() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(TEST).doDecode(TEST.buffer(), LSEG, FORMAT_BINARY, null)) + .withMessage("type must not be null"); + } + + @Test + void doDecodeNoFormat() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(TEST).doDecode(TEST.buffer(), LSEG, null, LineSegment.class)) + .withMessage("format must not be null"); + } + + @Test + void doDecode() { + LineSegmentCodec codec = new LineSegmentCodec(TEST); + Point point1 = Point.of(1.12, 2.12); + Point point2 = Point.of(3.12, 4.12); + LineSegment lineSegment = new LineSegment(point1, point2); + ByteBuf lineSegmentAsBinary = TEST.buffer(codec.lengthInBytes()) + .writeDouble(1.12) + .writeDouble(2.12) + .writeDouble(3.12) + .writeDouble(4.12); + assertThat(codec.doDecode(lineSegmentAsBinary, LSEG, FORMAT_BINARY, LineSegment.class)).isEqualTo(lineSegment); + } + + @Test + void doEncodeNoValue() { + assertThatIllegalArgumentException().isThrownBy(() -> new LineSegmentCodec(TEST).doEncode(null)) + .withMessage("value must not be null"); + } + + @Test + void doEncode() { + LineSegmentCodec codec = new LineSegmentCodec(TEST); + ByteBuf lineSegmentAsBinary = TEST.buffer(codec.lengthInBytes()) + .writeDouble(1.12) + .writeDouble(2.12) + .writeDouble(3.12) + .writeDouble(4.12); + + ParameterAssert.assertThat(codec.doEncode(new LineSegment(Point.of(1.12, 2.12), Point.of(3.12, 4.12)))) + .hasFormat(FORMAT_BINARY) + .hasType(LSEG.getObjectId()) + .hasValue(lineSegmentAsBinary); + } + + @Test + void encodeNull() { + ParameterAssert.assertThat(new LineSegmentCodec(TEST).encodeNull()) + .isEqualTo(new Parameter(FORMAT_BINARY, LSEG.getObjectId(), NULL_VALUE)); + } + +}