Skip to content

Commit e4daf28

Browse files
authored
Enhance constructor invocation handling with improved matching logic (#262)
2 parents 834cc99 + 4e3d4ca commit e4daf28

2 files changed

Lines changed: 150 additions & 11 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
77
javaVersion=25
88
mcVersion=1.21.11
99
group=dev.slne.surf
10-
version=1.21.11-2.70.2
10+
version=1.21.11-2.70.3
1111
relocationPrefix=dev.slne.surf.surfapi.libs
1212
snapshot=false

surf-api-core/surf-api-core-server/src/main/java/dev/slne/surf/surfapi/core/server/impl/reflection/SurfInvocationHandlerJava.java

Lines changed: 149 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)