chore: improve readability of method retrieval

This commit is contained in:
Pierre Maurice Schwang
2025-10-24 22:35:18 +02:00
parent a388629d62
commit d1a6c021fc
3 changed files with 166 additions and 14 deletions

View File

@@ -18,6 +18,7 @@
*/
package com.plotsquared.bukkit.schematic;
import com.plotsquared.core.util.ReflectionHelper;
import com.plotsquared.core.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
@@ -236,13 +237,11 @@ final class StateWrapperSpigot implements StateWrapper {
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
NoSuchMethodException, IllegalAccessException {
for (final Method method : craftBlockEntityStateClass.getMethods()) {
if (method.getReturnType().equals(Boolean.TYPE) && method.getParameterCount() == 2 &&
method.getParameterTypes()[0] == Boolean.TYPE && method.getParameterTypes()[1] == Boolean.TYPE) {
return LOOKUP.unreflect(method);
}
}
throw new NoSuchMethodException("Couldn't find method for #update(boolean, boolean) in " + craftBlockEntityStateClass.getName());
return LOOKUP.unreflect(ReflectionHelper.findMethod(
craftBlockEntityStateClass,
MethodType.methodType(Boolean.TYPE, Boolean.TYPE, Boolean.TYPE),
Modifier.PUBLIC
).orElseThrow(() -> new NoSuchMethodException("Couldn't lookup CraftBlockEntityState#update(boolean, boolean) boolean")));
}
private static MethodHandle findCraftBlockEntityStateSnapshotMethodHandle(Class<?> craftBlockEntityStateClass) throws
@@ -254,13 +253,11 @@ final class StateWrapperSpigot implements StateWrapper {
private static MethodHandle findSignBlockEntitySetTextMethodHandle(Class<?> signBlockEntity, Class<?> signText) throws
NoSuchMethodException, IllegalAccessException {
for (final Method method : signBlockEntity.getMethods()) {
if (method.getReturnType() == Boolean.TYPE && method.getParameterCount() == 2
&& method.getParameterTypes()[0] == signText && method.getParameterTypes()[1] == Boolean.TYPE) {
return LOOKUP.unreflect(method);
}
}
throw new NoSuchMethodException("Couldn't lookup SignBlockEntity#setText(SignText, boolean) boolean");
return LOOKUP.unreflect(ReflectionHelper.findMethod(
signBlockEntity,
MethodType.methodType(Boolean.TYPE, signText, Boolean.TYPE),
Modifier.PUBLIC
).orElseThrow(() -> new NoSuchMethodException("Couldn't lookup SignBlockEntity#setText(SignText, boolean) boolean")));
}
}

View File

@@ -0,0 +1,76 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.util;
import org.jetbrains.annotations.ApiStatus;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Optional;
@ApiStatus.Internal
public final class ReflectionHelper {
/**
* Find a (declared) method with an unknown or potentially obfuscated name by its signature and optional modifiers.
* <br>
* The method - if private - is not made accessible. Either call {@link Method#setAccessible(boolean)} or
* use a {@link java.lang.invoke.MethodHandles.Lookup#privateLookupIn(Class, MethodHandles.Lookup) private lookup}.
*
* @param holder The class providing the method.
* @param signature The signature of the method, identified by parameter types and the return type.
* @param modifiers All possible modifiers of the method that should be validated.
* @return The method, if one has been found. Otherwise, an empty Optional.
* @throws RuntimeException if multiple matching methods have been found.
* @see java.lang.reflect.Modifier
*/
public static Optional<Method> findMethod(Class<?> holder, MethodType signature, int... modifiers) {
Method found = null;
outer:
for (final Method method : holder.getDeclaredMethods()) {
if (method.getParameterCount() != signature.parameterCount()) {
continue;
}
if (!signature.returnType().isAssignableFrom(method.getReturnType())) {
continue;
}
for (final int modifier : modifiers) {
if ((method.getModifiers() & modifier) == 0) {
continue outer;
}
}
Class<?>[] parameterTypes = signature.parameterArray();
for (int i = 0; i < parameterTypes.length; i++) {
// validate expected parameter is either the same type or subtype of actual parameter
if (!parameterTypes[i].isAssignableFrom(method.getParameterTypes()[i])) {
continue outer;
}
}
if (found != null) {
throw new RuntimeException("Found ambiguous method by selector: " + method + " vs " + found);
}
found = method;
}
return Optional.ofNullable(found);
}
}

View File

@@ -0,0 +1,79 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.util;
import org.junit.jupiter.api.Test;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ReflectionHelperTest {
@Test
void findMethod() throws NoSuchMethodException {
assertThrows(
RuntimeException.class, () ->
ReflectionHelper.findMethod(MethodTesterClass.class, MethodType.methodType(String.class))
);
assertEquals(
MethodTesterClass.class.getMethod("methodThree"),
ReflectionHelper.findMethod(MethodTesterClass.class, MethodType.methodType(String.class), Modifier.PUBLIC)
.orElse(null)
);
assertEquals(
MethodTesterClass.class.getDeclaredMethod("methodFour", String.class, Collection.class),
ReflectionHelper.findMethod(MethodTesterClass.class, MethodType.methodType(
String.class, String.class, Collection.class
)).orElse(null)
);
// check that helper allows super classes of parameters when searching
assertEquals(
MethodTesterClass.class.getDeclaredMethod("methodFour", String.class, Collection.class),
ReflectionHelper.findMethod(MethodTesterClass.class, MethodType.methodType(
String.class, String.class, Object.class
)).orElse(null)
);
}
@SuppressWarnings("unused")
private static class MethodTesterClass {
private static String methodOne() {
return "";
}
private static String methodTwo() {
return "";
}
public static String methodThree() {
return "";
}
protected static String methodFour(String param, Collection<String> paramList) {
return "";
}
}
}