44import java .lang .annotation .Annotation ;
55import java .lang .reflect .Constructor ;
66import java .lang .reflect .InvocationTargetException ;
7+ import java .lang .reflect .Method ;
78import java .lang .reflect .ParameterizedType ;
89import java .math .BigInteger ;
910import java .nio .charset .CharacterCodingException ;
@@ -161,13 +162,54 @@ private <T> Object decodeByType(
161162 case BOOLEAN :
162163 return Decoder .decodeBoolean (size );
163164 case UTF8_STRING :
164- return this .decodeString (size );
165+ var s = this .decodeString (size );
166+ var created = tryCreateFromScalar (cls , s );
167+ if (created != null ) {
168+ return created ;
169+ }
170+ if (cls .isEnum ()) {
171+ @ SuppressWarnings ({"rawtypes" , "unchecked" })
172+ var enumClass = (Class <? extends Enum >) cls ;
173+ try {
174+ // Attempt a forgiving mapping commonly needed for ConnectionType
175+ var candidate = s .trim ()
176+ .replace (' ' , '_' )
177+ .replace ('-' , '_' )
178+ .replace ('/' , '_' )
179+ .toUpperCase ();
180+ return Enum .valueOf (enumClass , candidate );
181+ } catch (IllegalArgumentException ignored ) {
182+ // fall through to return the raw string
183+ }
184+ }
185+ return s ;
165186 case DOUBLE :
166- return this .decodeDouble (size );
187+ var d = this .decodeDouble (size );
188+ {
189+ var created2 = tryCreateFromScalar (cls , d );
190+ if (created2 != null ) {
191+ return created2 ;
192+ }
193+ }
194+ return d ;
167195 case FLOAT :
168- return this .decodeFloat (size );
196+ var f = this .decodeFloat (size );
197+ {
198+ var created3 = tryCreateFromScalar (cls , f );
199+ if (created3 != null ) {
200+ return created3 ;
201+ }
202+ }
203+ return f ;
169204 case BYTES :
170- return this .getByteArray (size );
205+ var bytes = this .getByteArray (size );
206+ {
207+ var created4 = tryCreateFromScalar (cls , bytes );
208+ if (created4 != null ) {
209+ return created4 ;
210+ }
211+ }
212+ return bytes ;
171213 case UINT16 :
172214 return coerceFromInt (this .decodeUint16 (size ), cls );
173215 case UINT32 :
@@ -176,14 +218,26 @@ private <T> Object decodeByType(
176218 return coerceFromInt (this .decodeInt32 (size ), cls );
177219 case UINT64 :
178220 case UINT128 :
179- return this .decodeBigInteger (size );
221+ var bi = this .decodeBigInteger (size );
222+ {
223+ var created5 = tryCreateFromScalar (cls , bi );
224+ if (created5 != null ) {
225+ return created5 ;
226+ }
227+ }
228+ return bi ;
180229 default :
181230 throw new InvalidDatabaseException (
182231 "Unknown or unexpected type: " + type .name ());
183232 }
184233 }
185234
186235 private static Object coerceFromInt (int value , Class <?> target ) {
236+ // If a creator exists that accepts an Integer-compatible value, use it
237+ var created = tryCreateFromScalar (target , Integer .valueOf (value ));
238+ if (created != null ) {
239+ return created ;
240+ }
187241 if (target .equals (Object .class )
188242 || target .equals (Integer .TYPE )
189243 || target .equals (Integer .class )) {
@@ -218,6 +272,10 @@ private static Object coerceFromInt(int value, Class<?> target) {
218272 }
219273
220274 private static Object coerceFromLong (long value , Class <?> target ) {
275+ var created = tryCreateFromScalar (target , Long .valueOf (value ));
276+ if (created != null ) {
277+ return created ;
278+ }
221279 if (target .equals (Object .class ) || target .equals (Long .TYPE ) || target .equals (Long .class )) {
222280 return value ;
223281 }
@@ -251,6 +309,58 @@ private static Object coerceFromLong(long value, Class<?> target) {
251309 return value ;
252310 }
253311
312+ private static Object tryCreateFromScalar (Class <?> target , Object value ) {
313+ if (target .equals (Object .class )) {
314+ return null ;
315+ }
316+ if (value != null && target .isAssignableFrom (value .getClass ())) {
317+ return null ;
318+ }
319+ Method creator = findSingleArgCreator (target );
320+ if (creator == null ) {
321+ return null ;
322+ }
323+ var paramType = creator .getParameterTypes ()[0 ];
324+ Object argument = value ;
325+ if (value != null && !paramType .isAssignableFrom (value .getClass ())) {
326+ // Minimal adaptation: allow converting to String for String parameters
327+ if (paramType .equals (String .class )) {
328+ argument = String .valueOf (value );
329+ } else {
330+ return null ;
331+ }
332+ }
333+ try {
334+ return creator .invoke (null , argument );
335+ } catch (IllegalAccessException | InvocationTargetException e ) {
336+ throw new DeserializationException ("Error invoking @MaxMindDbCreator on "
337+ + target .getName () + ": " + e .getMessage (), e );
338+ }
339+ }
340+
341+ private static Method findSingleArgCreator (Class <?> target ) {
342+ for (var m : target .getDeclaredMethods ()) {
343+ if (m .getAnnotation (MaxMindDbCreator .class ) == null ) {
344+ continue ;
345+ }
346+ if (!java .lang .reflect .Modifier .isStatic (m .getModifiers ())) {
347+ continue ;
348+ }
349+ if (!java .lang .reflect .Modifier .isPublic (m .getModifiers ())) {
350+ // To avoid module access issues, only consider public methods
351+ continue ;
352+ }
353+ if (!target .isAssignableFrom (m .getReturnType ())) {
354+ continue ;
355+ }
356+ if (m .getParameterCount () != 1 ) {
357+ continue ;
358+ }
359+ return m ;
360+ }
361+ return null ;
362+ }
363+
254364 private String decodeString (long size ) throws CharacterCodingException {
255365 var oldLimit = buffer .limit ();
256366 buffer .limit (buffer .position () + size );
0 commit comments