package com.plotsquared.bukkit.chat; import com.intellectualcrafters.plot.PS; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * A class containing static utility methods and caches which are intended as reflective conveniences. * Unless otherwise noted, upon failure methods will return {@code null}. */ public final class Reflection { /** * Stores loaded classes from the {@code net.minecraft.server} package. */ private static final Map> _loadedNMSClasses = new HashMap<>(); /** * Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages). */ private static final Map> _loadedOBCClasses = new HashMap<>(); private static final Map, Map> _loadedFields = new HashMap<>(); /** * Contains loaded methods in a cache. * The map maps [types to maps of [method names to maps of [parameter types to method instances]]]. */ private static final Map, Map>, Method>>> _loadedMethods = new HashMap<>(); /** * Gets the version string from the package name of the CraftBukkit server implementation. * This is needed to bypass the JAR package name changing on each update. * @return The version string of the OBC and NMS packages, including the trailing dot. */ public static synchronized String getVersion() { return PS.get().IMP.getNMSPackage(); } /** * Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package. * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this * method simultaneously). * @param className The name of the class, excluding the package, within NMS. * @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded. */ public static synchronized Class getNMSClass(String className) { if (_loadedNMSClasses.containsKey(className)) { return _loadedNMSClasses.get(className); } String fullName = "net.minecraft.server." + getVersion() + '.' + className; Class clazz; try { clazz = Class.forName(fullName); } catch (ClassNotFoundException e) { e.printStackTrace(); _loadedNMSClasses.put(className, null); return null; } _loadedNMSClasses.put(className, clazz); return clazz; } /** * Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package. * The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this * method simultaneously). * @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory * .CraftItemStack}. * @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded. */ public static synchronized Class getOBCClass(String className) { if (_loadedOBCClasses.containsKey(className)) { return _loadedOBCClasses.get(className); } String fullName = "org.bukkit.craftbukkit." + getVersion() + '.' + className; Class clazz; try { clazz = Class.forName(fullName); } catch (ClassNotFoundException e) { e.printStackTrace(); _loadedOBCClasses.put(className, null); return null; } _loadedOBCClasses.put(className, clazz); return clazz; } /** * Attempts to get the NMS handle of a CraftBukkit object. *

* The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the * runtime type of the specified object. *

* @param obj The object for which to retrieve an NMS handle. * @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}. */ public static synchronized Object getHandle(Object obj) { try { return getMethod(obj.getClass(), "getHandle").invoke(obj); } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { e.printStackTrace(); return null; } } /** * Retrieves a {@link Field} instance declared by the specified class with the specified name. * Java access modifiers are ignored during this retrieval. * No guarantee is made as to whether the field returned will be an * instance or static field. *

* A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that * no field will be reflectively looked up twice. *

*

* If a field is deemed suitable for return, * {@link Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned. * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance. *

* @param clazz The class which contains the field to retrieve. * @param name The declared name of the field in the class. * @return A field object with the specified name declared by the specified class. * @see Class#getDeclaredField(String) */ public static synchronized Field getField(Class clazz, String name) { Map loaded; if (!_loadedFields.containsKey(clazz)) { loaded = new HashMap<>(); _loadedFields.put(clazz, loaded); } else { loaded = _loadedFields.get(clazz); } if (loaded.containsKey(name)) { // If the field is loaded (or cached as not existing), return the relevant value, which might be null return loaded.get(name); } try { Field field = clazz.getDeclaredField(name); field.setAccessible(true); loaded.put(name, field); return field; } catch (NoSuchFieldException | SecurityException e) { // Error loading e.printStackTrace(); // Cache field as not existing loaded.put(name, null); return null; } } /** * Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types. * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field * returned will be an instance or static field. *

* A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that * no method will be reflectively looked up twice. *

*

* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code * true} before it is returned. * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance. *

* *

* This method does not search superclasses of the specified type for methods with the specified signature. * Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}. * @param clazz The class which contains the method to retrieve. * @param name The declared name of the method in the class. * @param args The formal argument types of the method. * @return A method object with the specified name declared by the specified class. */ public static synchronized Method getMethod(Class clazz, String name, Class... args) { if (!_loadedMethods.containsKey(clazz)) { _loadedMethods.put(clazz, new HashMap>, Method>>()); } Map>, Method>> loadedMethodNames = _loadedMethods.get(clazz); if (!loadedMethodNames.containsKey(name)) { loadedMethodNames.put(name, new HashMap>, Method>()); } Map>, Method> loadedSignatures = loadedMethodNames.get(name); ArrayWrapper> wrappedArg = new ArrayWrapper<>(args); if (loadedSignatures.containsKey(wrappedArg)) { return loadedSignatures.get(wrappedArg); } for (Method m : clazz.getMethods()) { if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) { m.setAccessible(true); loadedSignatures.put(wrappedArg, m); return m; } } loadedSignatures.put(wrappedArg, null); return null; } }