All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.deecaad.core.utils.ReflectionUtil Maven / Gradle / Ivy

Go to download

Library plugin for WeaponMechanics containing Brigadier Commands, Mechanics, and more

There is a newer version: 3.4.1
Show newest version
package me.deecaad.core.utils;

import me.deecaad.core.MechanicsCore;
import me.deecaad.core.compatibility.CompatibilityAPI;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

/**
 * This final utility class outlines static methods that operate on or return members of the
 * {@link java.lang.reflect} package. This class also deals with version compatibility.
 *
 * 

* The methods of this class are threadsafe. For most of the methods, if an error occurs inside of * the method, it will return null. */ @SuppressWarnings("unused") public final class ReflectionUtil { private static final String versionString; private static final String nmsVersion; private static final String cbVersion; private static final Field modifiersField; private static final int javaVersion; private static final String ERR = "This is probably caused by your minecraft server version. Contact a DEV for more help."; static { int javaVersion1; // Occurs when run without a server (e.x. In intellij) // noinspection ConstantConditions if (Bukkit.getServer() == null) { versionString = "TESTING"; } else { versionString = MinecraftVersions.getCURRENT().toProtocolString(); } nmsVersion = "net.minecraft.server." + versionString + '.'; // In 1.20.6+ paper servers, the CraftBukkit package has been remapped if (CompatibilityAPI.isPaper() && MinecraftVersions.TRAILS_AND_TAILS.get(5).isAtLeast()) { cbVersion = "org.bukkit.craftbukkit."; } else { cbVersion = "org.bukkit.craftbukkit." + versionString + '.'; } try { String version = System.getProperty("java.version"); if (version.startsWith("1.")) { version = version.substring(2, 3); } else { int dot = version.indexOf("."); if (dot != -1) version = version.substring(0, dot); } // IF version is something like 18-ea -> version = version.split("-")[0]; javaVersion1 = Integer.parseInt(version); } catch (Throwable throwable) { javaVersion1 = -1; MechanicsCore.debug.error("Could not get Java version for '" + System.getProperty("java.version") + "'"); MechanicsCore.debug.log(LogLevel.ERROR, throwable); } javaVersion = javaVersion1; if (javaVersion < 12) { modifiersField = getField(Field.class, "modifiers"); } else { // todo modify final fields post java 12 modifiersField = null; } } // Don't let anyone instantiate this class private ReflectionUtil() { } /** * Returns the major java version. For java versions that start with a one (E.x. 1.8), * this method will return 8. For newer java versions, this method will return the major version. * * @return The non-negative java version. */ public static int getJavaVersion() { return javaVersion; } /** * Returns the NMS class with the given name. In mc versions 1.17 and higher, pack is * used for the package the class is in. Previous versions ignore pack. * *

On paper servers in MC 1.20.5 and higher, packages have been remapped. We have to use the * remapping tool to get the correct package name and class name. * * @param pack The non-null package name that contains the class defined by name. Make * sure the string ends with a dot. * @param name The non-null name of the class to find. * @return The NMS class with that name. */ public static Class getNMSClass(@NotNull String pack, @NotNull String name) { String className; if (MinecraftVersions.CAVES_AND_CLIFFS_2.isAtLeast()) { className = "net.minecraft." + pack + '.' + name; if (CompatibilityAPI.isPaper() && MinecraftVersions.TRAILS_AND_TAILS.get(5).isAtLeast()) { ReflectionRemapper remapper = ReflectionRemapper.forReobfMappingsInPaperJar(); className = remapper.remapClassOrArrayName(className); } } else { className = nmsVersion + name; } try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new InternalError("Failed to get NMS class " + className + ". " + ERR, e); } } /** * Returns the net.minecraft.network.protocol.game packet for the given class name in 1.17+, or the * NMS packet for older versions. * * @param className The non-null name of the class to get. * @return The NMS class with that name, or null. */ @NotNull public static Class getPacketClass(@NotNull String className) { return getNMSClass("network.protocol.game", className); } /** * Returns the CraftBukkit class with the given package and name. * * @param className The non-null name of the class to get. * @return The CB class with that name, or null. */ @NotNull public static Class getCBClass(@NotNull String className) { try { return Class.forName(cbVersion + className); } catch (ClassNotFoundException e) { throw new InternalError("Failed to get CB class " + className + ". " + ERR, e); } } /** * Returns the {@link Constructor} of the given classObject that matches the given * parameters. * * @param clazz The class to get the constructor from. * @param parameters The types of parameters that the constructor takes. * @return The found constructor, or null. */ @NotNull public static Constructor getConstructor(@NotNull Class clazz, Class... parameters) { try { return clazz.getConstructor(parameters); } catch (NoSuchMethodException | SecurityException e) { throw new InternalError("Failed to get constructor. " + ERR, e); } } /** * Instantiates a new {@link Object} of the generic class time defined by * constructorSupplier. * * @param constructorSupplier The class to instantiate. * @param parameters The parameters of the constructor to use. * @param The generic class type to return. * @return A new object of the given class. * @see #newInstance(Constructor, Object...) */ @NotNull public static T newInstance(@NotNull Class constructorSupplier, Object... parameters) { Class[] classes = new Class[parameters.length]; for (int i = 0; i < parameters.length; i++) { classes[i] = parameters[i].getClass(); classes[i] = switch (parameters[i].getClass().getSimpleName()) { case "Double" -> double.class; case "Integer" -> int.class; case "Float" -> float.class; case "Boolean" -> boolean.class; case "Byte" -> byte.class; case "Short" -> short.class; case "Long" -> long.class; default -> classes[i]; }; } try { return newInstance(constructorSupplier.getConstructor(classes), parameters); } catch (NoSuchMethodException e) { throw new InternalError("Failed to instantiate class " + constructorSupplier + ". " + ERR, e); } } /** * Instantiates a new {@link Object} using the given constructor and * parameters. If the constructor is a default constructor, * parameters.length should equal 0. * * @param constructor The constructor to use to instantiate the object. * @param parameters The parameters that the constructor takes. * @return The new object, or null. */ @NotNull public static T newInstance(@NotNull Constructor constructor, Object... parameters) { try { return constructor.newInstance(parameters); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new InternalError("Failed to instantiate class " + constructor + ". " + ERR, e); } } @SuppressWarnings("unchecked") @NotNull public static Class getClass(@NotNull String className) { try { return (Class) Class.forName(className); } catch (ClassNotFoundException e) { throw new InternalError("Failed to find class with name " + className + ". " + ERR, e); } } /** * Instantiates a new {@link Object} of the generic class type using the default constructor. If the * class does not have a default constructor, this method will return null. * * @param clazz The class to instantiate a new instance from. * @param The generic type of the class. * @return The new instance, or null. */ @NotNull public static T newInstance(@NotNull Class clazz) { try { Constructor constructor = clazz.getConstructor(); return constructor.newInstance(); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new InternalError("Failed to instantiate class " + clazz + ". " + ERR, e); } } /** * Returns the {@link Field} belonging to the classObject with the given * fieldName. If no such field exists, this method will return null. * *

* After calling this method, you should cache the returned field to avoid the overhead of searching * for a field every time you want to use it. If the field you are getting is obfuscated, it is a * good practice to use {@link #getField(Class, Class, int)} instead, which will likely be more * accurate across server versions. * * @param clazz The non-null class to pull the field from. * @param fieldName The non-null name of the field to pull. * @return The found field. */ @NotNull public static Field getField(@NotNull Class clazz, @NotNull String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); // noinspection deprecation if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException | SecurityException e) { throw new InternalError("Failed to get field " + fieldName + ". " + ERR, e); } } /** * Returns a {@link Field} by it's datatype. This method should be used for getting obfuscated * fields, or if the name of the {@link Field} is otherwise not guaranteed to stay the same. * * If the target does not declare a matching field, this method will search in the * parent class. * * @param target The class to get the field from. * @param type The non-null datatype of the field. * @return The non-null found field. * @throws IllegalArgumentException If no such field exists. */ public static Field getField(@NotNull Class target, Class type) { return getField(target, type, 0, false); } /** * Returns a {@link Field} by it's datatype. This method should be used for getting obfuscated * fields, or if the name of the {@link Field} is otherwise not guaranteed to stay the same. * * If the target does not declare a matching field, this method will search in the * parent class. * * @param target The class to get the field from. * @param type The non-null datatype of the field. * @param index The index of the field. Sometimes this method will match multiple fields. For these * fields, index is required. * @return The non-null found field. * @throws IllegalArgumentException If no such field exists. */ public static Field getField(@NotNull Class target, Class type, int index) { return getField(target, type, index, false); } public static Field getField(@NotNull Class target, Class type, int index, boolean skipStatic) { for (final Field field : target.getDeclaredFields()) { // Type check. Make sure the field's datatype // matches the data type we are trying to find if (!type.isAssignableFrom(field.getType())) continue; if (skipStatic && Modifier.isStatic(field.getModifiers())) continue; if (index-- > 0) continue; // noinspection deprecation if (!field.isAccessible()) field.setAccessible(true); return field; } // if the class has a superclass, then recursively check // the super class for the field Class superClass = target.getSuperclass(); if (superClass != null) return getField(superClass, type, index); throw new IllegalArgumentException("Cannot find field with type " + type); } /** * Returns the value of the field. * * @param field The non-null field that holds the value. * @param instance The instance that holds the field, or null for static fields. * @return The value of the field, or null. */ public static Object invokeField(@NotNull Field field, @Nullable Object instance) { try { return field.get(instance); } catch (IllegalArgumentException | IllegalAccessException e) { throw new InternalError("Failed to invoke field " + field + ". " + ERR, e); } } /** * Sets the value of the field for a given instance. For static fields, * instance should be null. This method does not work for static final * fields. * * @param field The non-null field to set the value of. * @param instance The object to set the field value to. For static fields, this should be * null. * @param value The value to set to the field. */ public static void setField(@NotNull Field field, @Nullable Object instance, Object value) { try { // TODO This does not work yet. static final fields are tough if (Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) { if (javaVersion < 12) { // Not sure why, but this does not allow modifying static final fields modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); } } field.set(instance, value); } catch (IllegalArgumentException | IllegalAccessException e) { throw new InternalError("Failed to set field " + field + ". " + ERR, e); } } /** * Returns the {@link Method} declared in the given clazz with a signature that matches * methodName and parameters. * * @param clazz The non-null class that declares the method. * @param methodName The non-null name of the method. * @param parameters The non-null parameters of the method. * @return The method that matches the given signature, or null. */ public static Method getMethod(@NotNull Class clazz, @NotNull String methodName, Class... parameters) { try { Method method = clazz.getDeclaredMethod(methodName, parameters); // noinspection deprecation if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException | SecurityException e) { throw new InternalError("Failed to find method " + methodName + ". " + ERR, e); } } /** * Returns a {@link Method} by it's returned datatype and parameters. This method should be used for * getting obfuscated methods, or if the name of the method is otherwise not guaranteed to stay the * same. * * If the target does not declare a matching method, this method will search in the * parent class recursively. * * @param target The non-null target class that declares the method. * @param returnType The nullable returned type of the method. * @param params The non-null parameters of of the method. * @return The non-null method that matches the given signature. * @throws IllegalArgumentException If no such method exists. */ public static Method getMethod(@NotNull Class target, @Nullable Class returnType, Class... params) { return getMethod(target, returnType, 0, params); } /** * Returns a {@link Method} by it's returned datatype and parameters. This method should be used for * getting obfuscated methods, or if the name of the method is otherwise not guaranteed to stay the * same. * * If the target does not declare a matching method, this method will search in the * parent class recursively. * * @param target The non-null target class that declares the method. * @param returnType The nullable returned type of the method. * @param index The index of the method. Sometimes this method will match multiple methods. For * these methods, index is required. * @param params The non-null parameters of of the method. * @return The non-null method that matches the given signature. * @throws IllegalArgumentException If no such method exists. */ public static Method getMethod(@NotNull Class target, @Nullable Class returnType, int index, Class... params) { for (final Method method : target.getDeclaredMethods()) { if (returnType != null && !returnType.isAssignableFrom(method.getReturnType())) continue; else if (!Arrays.equals(method.getParameterTypes(), params)) continue; else if (index-- > 0) continue; // noinspection deprecation if (!method.isAccessible()) method.setAccessible(true); return method; } // Recursively check superclasses for the method if (target.getSuperclass() != null) return getMethod(target.getSuperclass(), returnType, index, params); throw new IllegalArgumentException("Cannot find field with return=" + returnType + ", params=" + Arrays.toString(params)); } /** * Invokes the given method, running it then returning the method's returned value. The * method is run as a member of instance, with parameters. * * @param method The non-null method to run. * @param instance The instance to run the method as a part of. For static methods, this should be * null. * @param parameters The parameters of the method. * @return The returned value from the method, or null. */ public static Object invokeMethod(@NotNull Method method, Object instance, Object... parameters) { try { return method.invoke(instance, parameters); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new InternalError("Failed to invoke method " + method + ". " + ERR, e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy