@@ -115,10 +115,7 @@ private Invokable createInvokable(final Method method) {
115115 }
116116
117117 if (constructorAnnotation != null ) {
118- final var handle = sneaky (
119- () -> privateLookup .unreflectConstructor (findConstructor (proxiedClass , method )));
120- final boolean hasParams = handle .type ().parameterCount () > 0 ;
121- return new HandleInvokable (normalizeMethodHandleType (handle ), hasParams );
118+ return new ConstructorInvokable (proxiedClass , method , privateLookup );
122119 }
123120
124121 if (staticAnnotation == null && method .getParameterCount () == 0 ) {
@@ -228,11 +225,132 @@ private static Field findField(final Class<?> clazz, final String name)
228225 return field ;
229226 }
230227
231- private static Constructor <?> findConstructor (final Class <?> clazz , final Method method )
232- throws NoSuchMethodException {
233- final Constructor <?> constructor = clazz .getDeclaredConstructor (method .getParameterTypes ());
234- constructor .setAccessible (true );
235- return constructor ;
228+ private static Constructor <?> findConstructor (
229+ final Class <?> clazz ,
230+ final Method method ,
231+ final Object [] args
232+ ) throws NoSuchMethodException {
233+
234+ final Class <?>[] declaredTypes = method .getParameterTypes ();
235+
236+ try {
237+ final Constructor <?> exact = clazz .getDeclaredConstructor (declaredTypes );
238+ exact .trySetAccessible ();
239+ return exact ;
240+ } catch (final NoSuchMethodException ignored ) {
241+ }
242+
243+ final Constructor <?>[] constructors = clazz .getDeclaredConstructors ();
244+ Constructor <?> best = null ;
245+ int bestScore = Integer .MIN_VALUE ;
246+
247+ for (final Constructor <?> constructor : constructors ) {
248+ final Class <?>[] ctorTypes = constructor .getParameterTypes ();
249+
250+ if (ctorTypes .length != declaredTypes .length ) {
251+ continue ;
252+ }
253+
254+ final int score = scoreExecutableMatch (ctorTypes , declaredTypes , args );
255+ if (score > bestScore ) {
256+ best = constructor ;
257+ bestScore = score ;
258+ }
259+ }
260+
261+ if (best == null ) {
262+ throw new NoSuchMethodException (
263+ "No compatible constructor found for " + clazz .getName ()
264+ + " with declared parameters " + Arrays .toString (declaredTypes )
265+ + " and runtime arguments " + runtimeTypesToString (args )
266+ );
267+ }
268+
269+ best .setAccessible (true );
270+ return best ;
271+ }
272+
273+ private static int scoreExecutableMatch (
274+ final Class <?>[] targetTypes ,
275+ final Class <?>[] declaredTypes ,
276+ final Object @ Nullable [] args
277+ ) {
278+ int score = 0 ;
279+
280+ for (int i = 0 ; i < targetTypes .length ; i ++) {
281+ final Class <?> target = wrap (targetTypes [i ]);
282+ final Class <?> declared = wrap (declaredTypes [i ]);
283+ final Object arg = args != null && i < args .length ? args [i ] : null ;
284+
285+ if (arg == null ) {
286+ if (targetTypes [i ].isPrimitive ()) {
287+ return Integer .MIN_VALUE ;
288+ }
289+
290+ if (declared .equals (target )) {
291+ score += 20 ;
292+ } else if (target .isAssignableFrom (declared )) {
293+ score += 10 ;
294+ } else {
295+ score += 1 ;
296+ }
297+ continue ;
298+ }
299+
300+ final Class <?> runtime = wrap (arg .getClass ());
301+
302+ if (!target .isAssignableFrom (runtime )) {
303+ return Integer .MIN_VALUE ;
304+ }
305+
306+ if (target .equals (runtime )) {
307+ score += 100 ;
308+ } else if (target .equals (declared )) {
309+ score += 50 ;
310+ } else if (target .isAssignableFrom (declared )) {
311+ score += 25 ;
312+ } else {
313+ score += 10 ;
314+ }
315+ }
316+
317+ return score ;
318+ }
319+
320+ private static Class <?> wrap (final Class <?> type ) {
321+ if (!type .isPrimitive ()) {
322+ return type ;
323+ }
324+
325+ if (type == boolean .class ) return Boolean .class ;
326+ if (type == byte .class ) return Byte .class ;
327+ if (type == short .class ) return Short .class ;
328+ if (type == char .class ) return Character .class ;
329+ if (type == int .class ) return Integer .class ;
330+ if (type == long .class ) return Long .class ;
331+ if (type == float .class ) return Float .class ;
332+ if (type == double .class ) return Double .class ;
333+ if (type == void .class ) return Void .class ;
334+
335+ return type ;
336+ }
337+
338+ private static String runtimeTypesToString (final @ Nullable Object @ Nullable [] args ) {
339+ if (args == null ) {
340+ return "[]" ;
341+ }
342+
343+ final StringBuilder builder = new StringBuilder ("[" );
344+ for (int i = 0 ; i < args .length ; i ++) {
345+ if (i > 0 ) {
346+ builder .append (", " );
347+ }
348+
349+ final Object arg = args [i ];
350+ builder .append (arg == null ? "null" : arg .getClass ().getName ());
351+ }
352+ builder .append (']' );
353+ return builder .toString ();
236354 }
237355
238356 private static Method findMethod (
@@ -348,7 +466,7 @@ private static boolean isEqualsHashOrToStringMethod(final Method method) {
348466 return isEqualsMethod (method ) || isHashCodeMethod (method ) || isToStringMethod (method );
349467 }
350468
351- private sealed interface Invokable permits HandleInvokable , ReflectionSetterInvokable , VarHandleSetterInvokable {
469+ private sealed interface Invokable permits ConstructorInvokable , HandleInvokable , ReflectionSetterInvokable , VarHandleSetterInvokable {
352470
353471 @ Nullable
354472 Object invoke (final Object [] args ) throws Throwable ;
@@ -367,6 +485,27 @@ private record HandleInvokable(MethodHandle handle, boolean hasParams) implement
367485 }
368486 }
369487
488+ private record ConstructorInvokable (
489+ Class <?> proxiedClass ,
490+ Method method ,
491+ MethodHandles .Lookup privateLookup
492+ ) implements Invokable {
493+
494+ @ Override
495+ public @ Nullable Object invoke (final Object [] args ) throws Throwable {
496+ final Constructor <?> constructor = findConstructor (proxiedClass , method , args );
497+ final MethodHandle handle = normalizeMethodHandleType (
498+ privateLookup .unreflectConstructor (constructor )
499+ );
500+
501+ if (handle .type ().parameterCount () > 0 ) {
502+ return handle .invokeExact (args );
503+ } else {
504+ return handle .invokeExact ();
505+ }
506+ }
507+ }
508+
370509 private record ReflectionSetterInvokable (Field field , boolean isStatic ) implements Invokable {
371510
372511 @ Override
0 commit comments