Skip to content

Commit e4b2b6f

Browse files
authored
Enhance KotlinSerializer with value codecs for widening primitive conversion. (#1301) (#1471)
JAVA-5303
1 parent 75259ee commit e4b2b6f

File tree

14 files changed

+170
-60
lines changed

14 files changed

+170
-60
lines changed

bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt

+13-8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import org.bson.BsonType
3636
import org.bson.BsonValue
3737
import org.bson.codecs.BsonValueCodec
3838
import org.bson.codecs.DecoderContext
39+
import org.bson.internal.NumberCodecHelper
40+
import org.bson.internal.StringCodecHelper
3941
import org.bson.types.ObjectId
4042

4143
/**
@@ -154,14 +156,17 @@ internal open class DefaultBsonDecoder(
154156
}
155157
}
156158

157-
override fun decodeByte(): Byte = decodeInt().toByte()
158-
override fun decodeChar(): Char = decodeString().single()
159-
override fun decodeFloat(): Float = decodeDouble().toFloat()
160-
override fun decodeShort(): Short = decodeInt().toShort()
161-
override fun decodeBoolean(): Boolean = readOrThrow({ reader.readBoolean() }, BsonType.BOOLEAN)
162-
override fun decodeDouble(): Double = readOrThrow({ reader.readDouble() }, BsonType.DOUBLE)
163-
override fun decodeInt(): Int = readOrThrow({ reader.readInt32() }, BsonType.INT32)
164-
override fun decodeLong(): Long = readOrThrow({ reader.readInt64() }, BsonType.INT64)
159+
override fun decodeByte(): Byte = NumberCodecHelper.decodeByte(reader)
160+
161+
override fun decodeChar(): Char = StringCodecHelper.decodeChar(reader)
162+
override fun decodeFloat(): Float = NumberCodecHelper.decodeFloat(reader)
163+
164+
override fun decodeShort(): Short = NumberCodecHelper.decodeShort(reader)
165+
override fun decodeBoolean(): Boolean = reader.readBoolean()
166+
167+
override fun decodeDouble(): Double = NumberCodecHelper.decodeDouble(reader)
168+
override fun decodeInt(): Int = NumberCodecHelper.decodeInt(reader)
169+
override fun decodeLong(): Long = NumberCodecHelper.decodeLong(reader)
165170
override fun decodeString(): String = readOrThrow({ reader.readString() }, BsonType.STRING)
166171

167172
override fun decodeNull(): Nothing? {

bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt

+60-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.bson.codecs.kotlinx
1717

18+
import java.util.stream.Stream
1819
import kotlin.test.assertEquals
1920
import kotlinx.serialization.ExperimentalSerializationApi
2021
import kotlinx.serialization.MissingFieldException
@@ -23,12 +24,17 @@ import kotlinx.serialization.modules.SerializersModule
2324
import kotlinx.serialization.modules.plus
2425
import kotlinx.serialization.modules.polymorphic
2526
import kotlinx.serialization.modules.subclass
27+
import org.bson.BsonBoolean
2628
import org.bson.BsonDocument
2729
import org.bson.BsonDocumentReader
2830
import org.bson.BsonDocumentWriter
31+
import org.bson.BsonDouble
32+
import org.bson.BsonInt32
33+
import org.bson.BsonInt64
2934
import org.bson.BsonInvalidOperationException
3035
import org.bson.BsonMaxKey
3136
import org.bson.BsonMinKey
37+
import org.bson.BsonString
3238
import org.bson.BsonUndefined
3339
import org.bson.codecs.DecoderContext
3440
import org.bson.codecs.EncoderContext
@@ -90,11 +96,12 @@ import org.bson.codecs.kotlinx.samples.SealedInterface
9096
import org.bson.codecs.kotlinx.samples.ValueClass
9197
import org.junit.jupiter.api.Test
9298
import org.junit.jupiter.api.assertThrows
99+
import org.junit.jupiter.params.ParameterizedTest
100+
import org.junit.jupiter.params.provider.MethodSource
93101

94102
@OptIn(ExperimentalSerializationApi::class)
95103
@Suppress("LargeClass")
96104
class KotlinSerializerCodecTest {
97-
private val numberLong = "\$numberLong"
98105
private val oid = "\$oid"
99106
private val emptyDocument = "{}"
100107
private val altConfiguration =
@@ -134,15 +141,59 @@ class KotlinSerializerCodecTest {
134141

135142
private val allBsonTypesDocument = BsonDocument.parse(allBsonTypesJson)
136143

137-
@Test
138-
fun testDataClassWithSimpleValues() {
139-
val expected =
140-
"""{"char": "c", "byte": 0, "short": 1, "int": 22, "long": {"$numberLong": "42"}, "float": 4.0,
141-
| "double": 4.2, "boolean": true, "string": "String"}"""
142-
.trimMargin()
143-
val dataClass = DataClassWithSimpleValues('c', 0, 1, 22, 42L, 4.0f, 4.2, true, "String")
144+
companion object {
145+
@JvmStatic
146+
fun testTypesCastingDataClassWithSimpleValues(): Stream<BsonDocument> {
147+
return Stream.of(
148+
BsonDocument()
149+
.append("char", BsonString("c"))
150+
.append("byte", BsonInt32(1))
151+
.append("short", BsonInt32(2))
152+
.append("int", BsonInt32(10))
153+
.append("long", BsonInt32(10))
154+
.append("float", BsonInt32(2))
155+
.append("double", BsonInt32(3))
156+
.append("boolean", BsonBoolean.TRUE)
157+
.append("string", BsonString("String")),
158+
BsonDocument()
159+
.append("char", BsonString("c"))
160+
.append("byte", BsonDouble(1.0))
161+
.append("short", BsonDouble(2.0))
162+
.append("int", BsonDouble(9.9999999999999992))
163+
.append("long", BsonDouble(9.9999999999999992))
164+
.append("float", BsonDouble(2.0))
165+
.append("double", BsonDouble(3.0))
166+
.append("boolean", BsonBoolean.TRUE)
167+
.append("string", BsonString("String")),
168+
BsonDocument()
169+
.append("char", BsonString("c"))
170+
.append("byte", BsonDouble(1.0))
171+
.append("short", BsonDouble(2.0))
172+
.append("int", BsonDouble(10.0))
173+
.append("long", BsonDouble(10.0))
174+
.append("float", BsonDouble(2.0))
175+
.append("double", BsonDouble(3.0))
176+
.append("boolean", BsonBoolean.TRUE)
177+
.append("string", BsonString("String")),
178+
BsonDocument()
179+
.append("char", BsonString("c"))
180+
.append("byte", BsonInt64(1))
181+
.append("short", BsonInt64(2))
182+
.append("int", BsonInt64(10))
183+
.append("long", BsonInt64(10))
184+
.append("float", BsonInt64(2))
185+
.append("double", BsonInt64(3))
186+
.append("boolean", BsonBoolean.TRUE)
187+
.append("string", BsonString("String")))
188+
}
189+
}
144190

145-
assertRoundTrips(expected, dataClass)
191+
@ParameterizedTest
192+
@MethodSource("testTypesCastingDataClassWithSimpleValues")
193+
fun testTypesCastingDataClassWithSimpleValues(data: BsonDocument) {
194+
val expectedDataClass = DataClassWithSimpleValues('c', 1, 2, 10, 10L, 2.0f, 3.0, true, "String")
195+
196+
assertDecodesTo(data, expectedDataClass)
146197
}
147198

148199
@Test

bson/src/main/org/bson/codecs/AtomicIntegerCodec.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import java.util.concurrent.atomic.AtomicInteger;
2323

24-
import static org.bson.codecs.NumberCodecHelper.decodeInt;
24+
import static org.bson.internal.NumberCodecHelper.decodeInt;
2525

2626
/**
2727
* Encodes and decodes {@code AtomicInteger} objects.

bson/src/main/org/bson/codecs/AtomicLongCodec.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import java.util.concurrent.atomic.AtomicLong;
2323

24-
import static org.bson.codecs.NumberCodecHelper.decodeLong;
24+
import static org.bson.internal.NumberCodecHelper.decodeLong;
2525

2626
/**
2727
* Encodes and decodes {@code AtomicLong} objects.

bson/src/main/org/bson/codecs/ByteCodec.java

+2-8
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@
1616

1717
package org.bson.codecs;
1818

19-
import org.bson.BsonInvalidOperationException;
2019
import org.bson.BsonReader;
2120
import org.bson.BsonWriter;
2221

23-
import static java.lang.String.format;
24-
import static org.bson.codecs.NumberCodecHelper.decodeInt;
22+
import static org.bson.internal.NumberCodecHelper.decodeByte;
2523

2624
/**
2725
* Encodes and decodes {@code Byte} objects.
@@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Byte value, final EncoderConte
3735

3836
@Override
3937
public Byte decode(final BsonReader reader, final DecoderContext decoderContext) {
40-
int value = decodeInt(reader);
41-
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
42-
throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value));
43-
}
44-
return (byte) value;
38+
return decodeByte(reader);
4539
}
4640

4741
@Override

bson/src/main/org/bson/codecs/CharacterCodec.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
package org.bson.codecs;
1818

19-
import org.bson.BsonInvalidOperationException;
2019
import org.bson.BsonReader;
2120
import org.bson.BsonWriter;
21+
import org.bson.internal.StringCodecHelper;
2222

23-
import static java.lang.String.format;
2423
import static org.bson.assertions.Assertions.notNull;
2524

2625
/**
@@ -38,13 +37,7 @@ public void encode(final BsonWriter writer, final Character value, final Encoder
3837

3938
@Override
4039
public Character decode(final BsonReader reader, final DecoderContext decoderContext) {
41-
String string = reader.readString();
42-
if (string.length() != 1) {
43-
throw new BsonInvalidOperationException(format("Attempting to decode the string '%s' to a character, but its length is not "
44-
+ "equal to one", string));
45-
}
46-
47-
return string.charAt(0);
40+
return StringCodecHelper.decodeChar(reader);
4841
}
4942

5043
@Override

bson/src/main/org/bson/codecs/DoubleCodec.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22-
import static org.bson.codecs.NumberCodecHelper.decodeDouble;
22+
import static org.bson.internal.NumberCodecHelper.decodeDouble;
2323

2424
/**
2525
* Encodes and decodes {@code Double} objects.

bson/src/main/org/bson/codecs/FloatCodec.java

+2-8
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@
1616

1717
package org.bson.codecs;
1818

19-
import org.bson.BsonInvalidOperationException;
2019
import org.bson.BsonReader;
2120
import org.bson.BsonWriter;
2221

23-
import static java.lang.String.format;
24-
import static org.bson.codecs.NumberCodecHelper.decodeDouble;
22+
import static org.bson.internal.NumberCodecHelper.decodeFloat;
2523

2624
/**
2725
* Encodes and decodes {@code Float} objects.
@@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Float value, final EncoderCont
3735

3836
@Override
3937
public Float decode(final BsonReader reader, final DecoderContext decoderContext) {
40-
double value = decodeDouble(reader);
41-
if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) {
42-
throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value));
43-
}
44-
return (float) value;
38+
return decodeFloat(reader);
4539
}
4640

4741
@Override

bson/src/main/org/bson/codecs/IntegerCodec.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22-
import static org.bson.codecs.NumberCodecHelper.decodeInt;
22+
import static org.bson.internal.NumberCodecHelper.decodeInt;
2323

2424
/**
2525
* Encodes and decodes {@code Integer} objects.

bson/src/main/org/bson/codecs/LongCodec.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22-
import static org.bson.codecs.NumberCodecHelper.decodeLong;
22+
import static org.bson.internal.NumberCodecHelper.decodeLong;
2323

2424
/**
2525
* Encodes and decodes {@code Long} objects.

bson/src/main/org/bson/codecs/ShortCodec.java

+2-8
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@
1616

1717
package org.bson.codecs;
1818

19-
import org.bson.BsonInvalidOperationException;
2019
import org.bson.BsonReader;
2120
import org.bson.BsonWriter;
2221

23-
import static java.lang.String.format;
24-
import static org.bson.codecs.NumberCodecHelper.decodeInt;
22+
import static org.bson.internal.NumberCodecHelper.decodeShort;
2523

2624
/**
2725
* Encodes and decodes {@code Short} objects.
@@ -37,11 +35,7 @@ public void encode(final BsonWriter writer, final Short value, final EncoderCont
3735

3836
@Override
3937
public Short decode(final BsonReader reader, final DecoderContext decoderContext) {
40-
int value = decodeInt(reader);
41-
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
42-
throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value));
43-
}
44-
return (short) value;
38+
return decodeShort(reader);
4539
}
4640

4741
@Override

bson/src/main/org/bson/codecs/NumberCodecHelper.java renamed to bson/src/main/org/bson/internal/NumberCodecHelper.java

+32-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.bson.codecs;
17+
package org.bson.internal;
1818

1919
import org.bson.BsonInvalidOperationException;
2020
import org.bson.BsonReader;
@@ -25,9 +25,28 @@
2525

2626
import static java.lang.String.format;
2727

28-
final class NumberCodecHelper {
28+
/**
29+
* This class is not part of the public API. It may be removed or changed at any time.
30+
*/
31+
public final class NumberCodecHelper {
32+
33+
public static byte decodeByte(final BsonReader reader) {
34+
int value = decodeInt(reader);
35+
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
36+
throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value));
37+
}
38+
return (byte) value;
39+
}
40+
41+
public static short decodeShort(final BsonReader reader) {
42+
int value = decodeInt(reader);
43+
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
44+
throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value));
45+
}
46+
return (short) value;
47+
}
2948

30-
static int decodeInt(final BsonReader reader) {
49+
public static int decodeInt(final BsonReader reader) {
3150
int intValue;
3251
BsonType bsonType = reader.getCurrentBsonType();
3352
switch (bsonType) {
@@ -61,7 +80,7 @@ static int decodeInt(final BsonReader reader) {
6180
return intValue;
6281
}
6382

64-
static long decodeLong(final BsonReader reader) {
83+
public static long decodeLong(final BsonReader reader) {
6584
long longValue;
6685
BsonType bsonType = reader.getCurrentBsonType();
6786
switch (bsonType) {
@@ -91,7 +110,15 @@ static long decodeLong(final BsonReader reader) {
91110
return longValue;
92111
}
93112

94-
static double decodeDouble(final BsonReader reader) {
113+
public static float decodeFloat(final BsonReader reader) {
114+
double value = decodeDouble(reader);
115+
if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) {
116+
throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value));
117+
}
118+
return (float) value;
119+
}
120+
121+
public static double decodeDouble(final BsonReader reader) {
95122
double doubleValue;
96123
BsonType bsonType = reader.getCurrentBsonType();
97124
switch (bsonType) {

0 commit comments

Comments
 (0)