Skip to content

Commit 289be53

Browse files
committed
Handle nested context-only constructors
1 parent 15faf58 commit 289be53

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,15 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
602602
parameters[i] = injectParameter(parameterInjections[i], parameterTypes[i]);
603603
continue;
604604
}
605-
if (parameters[i] == null && parameterDefaults[i] != null) {
605+
if (parameters[i] != null) {
606+
continue;
607+
}
608+
if (parameterDefaults[i] != null) {
606609
parameters[i] = parameterDefaults[i];
610+
continue;
611+
}
612+
if (shouldInstantiateFromContext(parameterTypes[i])) {
613+
parameters[i] = instantiateWithLookupContext(parameterTypes[i]);
607614
}
608615
}
609616

@@ -629,6 +636,77 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
629636
}
630637
}
631638

639+
private boolean shouldInstantiateFromContext(Class<?> parameterType) {
640+
if (parameterType == null
641+
|| parameterType.isPrimitive()
642+
|| isSimpleType(parameterType)
643+
|| Map.class.isAssignableFrom(parameterType)
644+
|| List.class.isAssignableFrom(parameterType)) {
645+
return false;
646+
}
647+
return requiresLookupContext(parameterType);
648+
}
649+
650+
private Object instantiateWithLookupContext(Class<?> parameterType) {
651+
var metadata = loadConstructorMetadata(parameterType);
652+
if (metadata == null || !metadata.requiresLookupContext()) {
653+
return null;
654+
}
655+
656+
var ctor = metadata.constructor();
657+
var types = metadata.parameterTypes();
658+
var defaults = metadata.parameterDefaults();
659+
var injections = metadata.parameterInjections();
660+
var args = new Object[types.length];
661+
662+
for (int i = 0; i < args.length; i++) {
663+
if (injections[i] != ParameterInjection.NONE) {
664+
args[i] = injectParameter(injections[i], types[i]);
665+
} else if (defaults[i] != null) {
666+
args[i] = defaults[i];
667+
} else if (types[i].isPrimitive()) {
668+
args[i] = primitiveDefault(types[i]);
669+
} else {
670+
args[i] = null;
671+
}
672+
}
673+
674+
try {
675+
return ctor.newInstance(args);
676+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
677+
throw new DeserializationException(
678+
"Error creating object of type: " + parameterType.getName(), e);
679+
}
680+
}
681+
682+
private static Object primitiveDefault(Class<?> type) {
683+
if (type.equals(Boolean.TYPE)) {
684+
return false;
685+
}
686+
if (type.equals(Byte.TYPE)) {
687+
return (byte) 0;
688+
}
689+
if (type.equals(Short.TYPE)) {
690+
return (short) 0;
691+
}
692+
if (type.equals(Integer.TYPE)) {
693+
return 0;
694+
}
695+
if (type.equals(Long.TYPE)) {
696+
return 0L;
697+
}
698+
if (type.equals(Float.TYPE)) {
699+
return 0.0f;
700+
}
701+
if (type.equals(Double.TYPE)) {
702+
return 0.0d;
703+
}
704+
if (type.equals(Character.TYPE)) {
705+
return '\0';
706+
}
707+
return null;
708+
}
709+
632710
private Object injectParameter(ParameterInjection injection, Class<?> parameterType) {
633711
return switch (injection) {
634712
case IP_ADDRESS -> getLookupIpValue(parameterType);

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,27 @@ public void testContextAnnotations(int chunkSize) throws IOException {
552552
}
553553
}
554554

555+
@ParameterizedTest
556+
@MethodSource("chunkSizes")
557+
public void testNestedContextAnnotations(int chunkSize) throws IOException {
558+
try (var reader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize)) {
559+
var firstIp = InetAddress.getByName("1.1.1.1");
560+
var secondIp = InetAddress.getByName("1.1.1.3");
561+
var expectedNetwork = reader.getRecord(firstIp, Map.class).network().toString();
562+
563+
var first = reader.get(firstIp, WrapperContextOnlyModel.class);
564+
var second = reader.get(secondIp, WrapperContextOnlyModel.class);
565+
566+
assertNotNull(first.context);
567+
assertEquals(firstIp, first.context.lookupIp);
568+
assertEquals(expectedNetwork, first.context.lookupNetwork.toString());
569+
570+
assertNotNull(second.context);
571+
assertEquals(secondIp, second.context.lookupIp);
572+
assertEquals(expectedNetwork, second.context.lookupNetwork.toString());
573+
}
574+
}
575+
555576
@ParameterizedTest
556577
@MethodSource("chunkSizes")
557578
public void testDecodingTypesPointerDecoderFile(int chunkSize) throws IOException {
@@ -842,6 +863,32 @@ public TestModelBoxed(
842863
}
843864
}
844865

866+
static class ContextOnlyModel {
867+
InetAddress lookupIp;
868+
Network lookupNetwork;
869+
870+
@MaxMindDbConstructor
871+
public ContextOnlyModel(
872+
@MaxMindDbIpAddress InetAddress lookupIp,
873+
@MaxMindDbNetwork Network lookupNetwork
874+
) {
875+
this.lookupIp = lookupIp;
876+
this.lookupNetwork = lookupNetwork;
877+
}
878+
}
879+
880+
static class WrapperContextOnlyModel {
881+
ContextOnlyModel context;
882+
883+
@MaxMindDbConstructor
884+
public WrapperContextOnlyModel(
885+
@MaxMindDbParameter(name = "missing_context")
886+
ContextOnlyModel context
887+
) {
888+
this.context = context;
889+
}
890+
}
891+
845892
static class MapModelBoxed {
846893
MapXModelBoxed mapXField;
847894

0 commit comments

Comments
 (0)