Skip to content

Commit c55a3a4

Browse files
committed
Made numeric codec implementations more lenient
JAVA-2529
1 parent 12c3894 commit c55a3a4

19 files changed

+576
-41
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@
2121

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

24+
import static org.bson.codecs.NumberCodecHelper.decodeInt;
25+
2426
/**
2527
* Encodes and decodes {@code AtomicInteger} objects.
2628
*
2729
* @since 3.0
2830
*/
2931

3032
public class AtomicIntegerCodec implements Codec<AtomicInteger> {
33+
3134
@Override
3235
public void encode(final BsonWriter writer, final AtomicInteger value, final EncoderContext encoderContext) {
3336
writer.writeInt32(value.intValue());
3437
}
3538

3639
@Override
3740
public AtomicInteger decode(final BsonReader reader, final DecoderContext decoderContext) {
38-
return new AtomicInteger(reader.readInt32());
41+
return new AtomicInteger(decodeInt(reader));
3942
}
4043

4144
@Override

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@
2121

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

24+
import static org.bson.codecs.NumberCodecHelper.decodeLong;
25+
2426
/**
2527
* Encodes and decodes {@code AtomicLong} objects.
2628
*
2729
* @since 3.0
2830
*/
2931

3032
public class AtomicLongCodec implements Codec<AtomicLong> {
33+
3134
@Override
3235
public void encode(final BsonWriter writer, final AtomicLong value, final EncoderContext encoderContext) {
3336
writer.writeInt64(value.longValue());
3437
}
3538

3639
@Override
3740
public AtomicLong decode(final BsonReader reader, final DecoderContext decoderContext) {
38-
return new AtomicLong(reader.readInt64());
41+
return new AtomicLong(decodeLong(reader));
3942
}
4043

4144
@Override

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@
2121
import org.bson.BsonWriter;
2222

2323
import static java.lang.String.format;
24+
import static org.bson.codecs.NumberCodecHelper.decodeInt;
2425

2526
/**
2627
* Encodes and decodes {@code Byte} objects.
2728
*
2829
* @since 3.0
2930
*/
3031
public class ByteCodec implements Codec<Byte> {
32+
3133
@Override
3234
public void encode(final BsonWriter writer, final Byte value, final EncoderContext encoderContext) {
3335
writer.writeInt32(value);
3436
}
3537

3638
@Override
3739
public Byte decode(final BsonReader reader, final DecoderContext decoderContext) {
38-
int value = reader.readInt32();
40+
int value = decodeInt(reader);
3941
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
4042
throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value));
4143
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22+
import static org.bson.codecs.NumberCodecHelper.decodeDouble;
23+
2224
/**
2325
* Encodes and decodes {@code Double} objects.
2426
*
@@ -32,7 +34,7 @@ public void encode(final BsonWriter writer, final Double value, final EncoderCon
3234

3335
@Override
3436
public Double decode(final BsonReader reader, final DecoderContext decoderContext) {
35-
return reader.readDouble();
37+
return decodeDouble(reader);
3638
}
3739

3840
@Override

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@
2121
import org.bson.BsonWriter;
2222

2323
import static java.lang.String.format;
24+
import static org.bson.codecs.NumberCodecHelper.decodeDouble;
2425

2526
/**
2627
* Encodes and decodes {@code Float} objects.
2728
*
2829
* @since 3.0
2930
*/
3031
public class FloatCodec implements Codec<Float> {
32+
3133
@Override
3234
public void encode(final BsonWriter writer, final Float value, final EncoderContext encoderContext) {
3335
writer.writeDouble(value);
3436
}
3537

3638
@Override
3739
public Float decode(final BsonReader reader, final DecoderContext decoderContext) {
38-
double value = reader.readDouble();
40+
double value = decodeDouble(reader);
3941
if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) {
4042
throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value));
4143
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22+
import static org.bson.codecs.NumberCodecHelper.decodeInt;
23+
2224
/**
2325
* Encodes and decodes {@code Integer} objects.
2426
*
2527
* @since 3.0
2628
*/
2729
public class IntegerCodec implements Codec<Integer> {
30+
2831
@Override
2932
public void encode(final BsonWriter writer, final Integer value, final EncoderContext encoderContext) {
3033
writer.writeInt32(value);
3134
}
3235

3336
@Override
3437
public Integer decode(final BsonReader reader, final DecoderContext decoderContext) {
35-
return reader.readInt32();
38+
return decodeInt(reader);
3639
}
3740

3841
@Override

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,24 @@
1919
import org.bson.BsonReader;
2020
import org.bson.BsonWriter;
2121

22+
import static org.bson.codecs.NumberCodecHelper.decodeLong;
23+
2224
/**
2325
* Encodes and decodes {@code Long} objects.
2426
*
2527
* @since 3.0
2628
*/
2729

2830
public class LongCodec implements Codec<Long> {
31+
2932
@Override
3033
public void encode(final BsonWriter writer, final Long value, final EncoderContext encoderContext) {
3134
writer.writeInt64(value);
3235
}
3336

3437
@Override
3538
public Long decode(final BsonReader reader, final DecoderContext decoderContext) {
36-
return reader.readInt64();
39+
return decodeLong(reader);
3740
}
3841

3942
@Override
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.bson.codecs;
18+
19+
import org.bson.BsonInvalidOperationException;
20+
import org.bson.BsonReader;
21+
import org.bson.BsonType;
22+
23+
import static java.lang.String.format;
24+
25+
final class NumberCodecHelper {
26+
27+
static int decodeInt(final BsonReader reader) {
28+
int intValue;
29+
BsonType bsonType = reader.getCurrentBsonType();
30+
switch (bsonType) {
31+
case INT32:
32+
intValue = reader.readInt32();
33+
break;
34+
case INT64:
35+
long longValue = reader.readInt64();
36+
intValue = (int) longValue;
37+
if (longValue != (long) intValue) {
38+
throw invalidConversion(Integer.class, longValue);
39+
}
40+
break;
41+
case DOUBLE:
42+
double doubleValue = reader.readDouble();
43+
intValue = (int) doubleValue;
44+
if (doubleValue != (double) intValue) {
45+
throw invalidConversion(Integer.class, doubleValue);
46+
}
47+
break;
48+
default:
49+
throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType));
50+
}
51+
return intValue;
52+
}
53+
54+
static long decodeLong(final BsonReader reader) {
55+
long longValue;
56+
BsonType bsonType = reader.getCurrentBsonType();
57+
switch (bsonType) {
58+
case INT32:
59+
longValue = reader.readInt32();
60+
break;
61+
case INT64:
62+
longValue = reader.readInt64();
63+
break;
64+
case DOUBLE:
65+
double doubleValue = reader.readDouble();
66+
longValue = (long) doubleValue;
67+
if (doubleValue != (double) longValue) {
68+
throw invalidConversion(Long.class, doubleValue);
69+
}
70+
break;
71+
default:
72+
throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType));
73+
}
74+
return longValue;
75+
}
76+
77+
static double decodeDouble(final BsonReader reader) {
78+
double doubleValue;
79+
BsonType bsonType = reader.getCurrentBsonType();
80+
switch (bsonType) {
81+
case INT32:
82+
doubleValue = reader.readInt32();
83+
break;
84+
case INT64:
85+
long longValue = reader.readInt64();
86+
doubleValue = longValue;
87+
if (longValue != (long) doubleValue) {
88+
throw invalidConversion(Double.class, longValue);
89+
}
90+
break;
91+
case DOUBLE:
92+
doubleValue = reader.readDouble();
93+
break;
94+
default:
95+
throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType));
96+
}
97+
return doubleValue;
98+
}
99+
100+
private static <T extends Number> BsonInvalidOperationException invalidConversion(final Class<T> clazz, final Number value) {
101+
return new BsonInvalidOperationException(format("Could not convert `%s` to a %s without losing precision", value, clazz));
102+
}
103+
104+
private NumberCodecHelper() {
105+
}
106+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@
2121
import org.bson.BsonWriter;
2222

2323
import static java.lang.String.format;
24+
import static org.bson.codecs.NumberCodecHelper.decodeInt;
2425

2526
/**
2627
* Encodes and decodes {@code Short} objects.
2728
*
2829
* @since 3.0
2930
*/
3031
public class ShortCodec implements Codec<Short> {
32+
3133
@Override
3234
public void encode(final BsonWriter writer, final Short value, final EncoderContext encoderContext) {
3335
writer.writeInt32(value);
3436
}
3537

3638
@Override
3739
public Short decode(final BsonReader reader, final DecoderContext decoderContext) {
38-
int value = reader.readInt32();
40+
int value = decodeInt(reader);
3941
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
4042
throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value));
4143
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.bson.codecs;
18+
19+
import org.bson.BsonInvalidOperationException;
20+
import org.bson.Document;
21+
import org.junit.Test;
22+
23+
import java.util.concurrent.atomic.AtomicInteger;
24+
25+
import static org.junit.Assert.assertEquals;
26+
27+
public final class AtomicIntegerCodecTest extends CodecTestCase {
28+
29+
@Test
30+
public void shouldRoundTripAtomicIntegerValues() {
31+
Document original = new Document("a", new AtomicInteger(Integer.MAX_VALUE));
32+
roundTrip(original, new AtomicIntegerComparator(original));
33+
34+
original = new Document("a", new AtomicInteger(Integer.MIN_VALUE));
35+
roundTrip(original, new AtomicIntegerComparator(original));
36+
}
37+
38+
@Test
39+
public void shouldHandleAlternativeNumberValues() {
40+
Document expected = new Document("a", new AtomicInteger(10));
41+
roundTrip(new Document("a", 10), new AtomicIntegerComparator(expected));
42+
roundTrip(new Document("a", 10L), new AtomicIntegerComparator(expected));
43+
roundTrip(new Document("a", 10.00), new AtomicIntegerComparator(expected));
44+
roundTrip(new Document("a", 9.9999999999999992), new AtomicIntegerComparator(expected));
45+
}
46+
47+
@Test(expected = BsonInvalidOperationException.class)
48+
public void shouldThrowWhenHandlingLossyDoubleValues() {
49+
Document original = new Document("a", 9.9999999999999991);
50+
roundTrip(original, new AtomicIntegerComparator(original));
51+
}
52+
53+
@Test(expected = BsonInvalidOperationException.class)
54+
public void shouldErrorDecodingOutsideMinRange() {
55+
roundTrip(new Document("a", Long.MIN_VALUE));
56+
}
57+
58+
@Test(expected = BsonInvalidOperationException.class)
59+
public void shouldErrorDecodingOutsideMaxRange() {
60+
roundTrip(new Document("a", Long.MAX_VALUE));
61+
}
62+
63+
@Override
64+
DocumentCodecProvider getDocumentCodecProvider() {
65+
return getSpecificNumberDocumentCodecProvider(AtomicInteger.class);
66+
}
67+
68+
private class AtomicIntegerComparator implements Comparator<Document> {
69+
private final Document expected;
70+
71+
AtomicIntegerComparator(final Document expected) {
72+
this.expected = expected;
73+
}
74+
75+
@Override
76+
public void apply(final Document result) {
77+
assertEquals("Codec Round Trip",
78+
expected.get("a", AtomicInteger.class).get(),
79+
result.get("a", AtomicInteger.class).get());
80+
}
81+
}
82+
83+
}

0 commit comments

Comments
 (0)