Skip to content

Commit 59c7501

Browse files
committed
Decode int primitive constructor params
Add coercion for INT32/UINT16/UINT32 to target primitives with range checks.
1 parent 4b36416 commit 59c7501

File tree

2 files changed

+157
-4
lines changed

2 files changed

+157
-4
lines changed

src/main/java/com/maxmind/db/Decoder.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,11 @@ private <T> Object decodeByType(
169169
case BYTES:
170170
return this.getByteArray(size);
171171
case UINT16:
172-
return this.decodeUint16(size);
172+
return coerceFromInt(this.decodeUint16(size), cls);
173173
case UINT32:
174-
return this.decodeUint32(size);
174+
return coerceFromLong(this.decodeUint32(size), cls);
175175
case INT32:
176-
return this.decodeInt32(size);
176+
return coerceFromInt(this.decodeInt32(size), cls);
177177
case UINT64:
178178
case UINT128:
179179
return this.decodeBigInteger(size);
@@ -183,6 +183,74 @@ private <T> Object decodeByType(
183183
}
184184
}
185185

186+
private static Object coerceFromInt(int value, Class<?> target) {
187+
if (target.equals(Object.class)
188+
|| target.equals(Integer.TYPE)
189+
|| target.equals(Integer.class)) {
190+
return value;
191+
}
192+
if (target.equals(Long.TYPE) || target.equals(Long.class)) {
193+
return (long) value;
194+
}
195+
if (target.equals(Short.TYPE) || target.equals(Short.class)) {
196+
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
197+
throw new DeserializationException("Value " + value + " out of range for short");
198+
}
199+
return (short) value;
200+
}
201+
if (target.equals(Byte.TYPE) || target.equals(Byte.class)) {
202+
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
203+
throw new DeserializationException("Value " + value + " out of range for byte");
204+
}
205+
return (byte) value;
206+
}
207+
if (target.equals(Double.TYPE) || target.equals(Double.class)) {
208+
return (double) value;
209+
}
210+
if (target.equals(Float.TYPE) || target.equals(Float.class)) {
211+
return (float) value;
212+
}
213+
if (target.equals(BigInteger.class)) {
214+
return BigInteger.valueOf(value);
215+
}
216+
// Fallback: return as Integer; caller may attempt to cast/assign
217+
return value;
218+
}
219+
220+
private static Object coerceFromLong(long value, Class<?> target) {
221+
if (target.equals(Object.class) || target.equals(Long.TYPE) || target.equals(Long.class)) {
222+
return value;
223+
}
224+
if (target.equals(Integer.TYPE) || target.equals(Integer.class)) {
225+
if (value < 0 || value > Integer.MAX_VALUE) {
226+
throw new DeserializationException("Value " + value + " out of range for int");
227+
}
228+
return (int) value;
229+
}
230+
if (target.equals(Short.TYPE) || target.equals(Short.class)) {
231+
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
232+
throw new DeserializationException("Value " + value + " out of range for short");
233+
}
234+
return (short) value;
235+
}
236+
if (target.equals(Byte.TYPE) || target.equals(Byte.class)) {
237+
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
238+
throw new DeserializationException("Value " + value + " out of range for byte");
239+
}
240+
return (byte) value;
241+
}
242+
if (target.equals(Double.TYPE) || target.equals(Double.class)) {
243+
return (double) value;
244+
}
245+
if (target.equals(Float.TYPE) || target.equals(Float.class)) {
246+
return (float) value;
247+
}
248+
if (target.equals(BigInteger.class)) {
249+
return BigInteger.valueOf(value);
250+
}
251+
return value;
252+
}
253+
186254
private String decodeString(long size) throws CharacterCodingException {
187255
var oldLimit = buffer.limit();
188256
buffer.limit(buffer.position() + size);

src/test/java/com/maxmind/db/ReaderTest.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,10 +1148,95 @@ public TestModelVector(
11481148
@MaxMindDbParameter(name = "array")
11491149
Vector<Long> arrayField
11501150
) {
1151-
this.arrayField = arrayField;
1151+
this.arrayField = arrayField;
1152+
}
1153+
}
1154+
1155+
// Positive tests for primitive constructor parameters
1156+
static class TestModelPrimitivesBasic {
1157+
boolean booleanField;
1158+
double doubleField;
1159+
float floatField;
1160+
int int32Field;
1161+
long uint32Field;
1162+
1163+
@MaxMindDbConstructor
1164+
public TestModelPrimitivesBasic(
1165+
@MaxMindDbParameter(name = "boolean") boolean booleanField,
1166+
@MaxMindDbParameter(name = "double") double doubleField,
1167+
@MaxMindDbParameter(name = "float") float floatField,
1168+
@MaxMindDbParameter(name = "int32") int int32Field,
1169+
@MaxMindDbParameter(name = "uint32") long uint32Field
1170+
) {
1171+
this.booleanField = booleanField;
1172+
this.doubleField = doubleField;
1173+
this.floatField = floatField;
1174+
this.int32Field = int32Field;
1175+
this.uint32Field = uint32Field;
1176+
}
1177+
}
1178+
1179+
@ParameterizedTest
1180+
@MethodSource("chunkSizes")
1181+
public void testPrimitiveConstructorParamsBasicWorks(int chunkSize) throws IOException {
1182+
this.testReader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize);
1183+
1184+
var model = this.testReader.get(
1185+
InetAddress.getByName("::1.1.1.0"),
1186+
TestModelPrimitivesBasic.class
1187+
);
1188+
1189+
assertTrue(model.booleanField);
1190+
assertEquals(42.123456, model.doubleField, 0.000000001);
1191+
assertEquals(1.1, model.floatField, 0.000001);
1192+
assertEquals(-268435456, model.int32Field);
1193+
assertEquals(268435456L, model.uint32Field);
1194+
}
1195+
1196+
static class TestModelShortPrimitive {
1197+
short uint16Field;
1198+
1199+
@MaxMindDbConstructor
1200+
public TestModelShortPrimitive(
1201+
@MaxMindDbParameter(name = "uint16") short uint16Field
1202+
) {
1203+
this.uint16Field = uint16Field;
1204+
}
1205+
}
1206+
1207+
@ParameterizedTest
1208+
@MethodSource("chunkSizes")
1209+
public void testPrimitiveConstructorParamShortWorks(int chunkSize) throws IOException {
1210+
this.testReader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize);
1211+
var model = this.testReader.get(
1212+
InetAddress.getByName("::1.1.1.0"),
1213+
TestModelShortPrimitive.class
1214+
);
1215+
assertEquals((short) 100, model.uint16Field);
1216+
}
1217+
1218+
static class TestModelBytePrimitive {
1219+
byte uint16Field;
1220+
1221+
@MaxMindDbConstructor
1222+
public TestModelBytePrimitive(
1223+
@MaxMindDbParameter(name = "uint16") byte uint16Field
1224+
) {
1225+
this.uint16Field = uint16Field;
11521226
}
11531227
}
11541228

1229+
@ParameterizedTest
1230+
@MethodSource("chunkSizes")
1231+
public void testPrimitiveConstructorParamByteWorks(int chunkSize) throws IOException {
1232+
this.testReader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize);
1233+
var model = this.testReader.get(
1234+
InetAddress.getByName("::1.1.1.0"),
1235+
TestModelBytePrimitive.class
1236+
);
1237+
assertEquals((byte) 100, model.uint16Field);
1238+
}
1239+
11551240
// Test that we cache differently depending on more than the offset.
11561241
@ParameterizedTest
11571242
@MethodSource("chunkSizes")

0 commit comments

Comments
 (0)