From 3fe08f149d0a84e0593c80f3a028ac3d76d1603f Mon Sep 17 00:00:00 2001 From: Cadiboo <37298044+Cadiboo@users.noreply.github.com> Date: Tue, 9 Jul 2019 12:44:21 +1000 Subject: [PATCH] Added documentation to ObfuscationReflectionHelper adn deprecated index based functions. (#5893) --- .../common/ObfuscationReflectionHelper.java | 198 ++++++++++++++---- 1 file changed, 160 insertions(+), 38 deletions(-) diff --git a/src/main/java/net/minecraftforge/fml/common/ObfuscationReflectionHelper.java b/src/main/java/net/minecraftforge/fml/common/ObfuscationReflectionHelper.java index ad75cef14..daf2388f6 100644 --- a/src/main/java/net/minecraftforge/fml/common/ObfuscationReflectionHelper.java +++ b/src/main/java/net/minecraftforge/fml/common/ObfuscationReflectionHelper.java @@ -21,11 +21,10 @@ package net.minecraftforge.fml.common; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import java.util.StringJoiner; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import cpw.mods.modlauncher.api.INameMappingService; import net.minecraftforge.fml.loading.FMLLoader; @@ -44,31 +43,50 @@ import com.google.common.base.Preconditions; * * In other cases, AccessTransformers may be used. * - * All field names should be passed in as SRG names, and this will automatically resolve if MCP mappings are detected. + * All field and method names should be passed in as SRG names, and this will automatically resolve if MCP mappings are detected. * */ -@SuppressWarnings({"serial", "unchecked"}) +@SuppressWarnings({"serial", "unchecked", "unused", "WeakerAccess"}) public class ObfuscationReflectionHelper { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker REFLECTION = MarkerManager.getMarker("REFLECTION"); - private static final Map map = new HashMap<>(); - private static boolean loaded = false; - - + /** + * Remaps a name using the SRG naming function + * @param domain The {@link INameMappingService.Domain} to use to remap the name. + * @param name The name to try and remap. + * @return The remapped name, or the original name if it couldn't be remapped. + */ + @Nonnull public static String remapName(INameMappingService.Domain domain, String name) { return FMLLoader.getNameFunction("srg").map(f->f.apply(domain, name)).orElse(name); } + /** + * Gets the value a field with the specified index in the given class. + * Note: For performance, use {@link #findField(Class, int)} if you are getting the value more than once. + *

+ * Throws an exception if the field is not found or the value of the field cannot be gotten. + * + * @param classToAccess The class to find the field on. + * @param instance The instance of the {@code classToAccess}. + * @param fieldIndex The index of the field in the {@code classToAccess}. + * @param The type of the value. + * @param The type of the {@code classToAccess}. + * @return The value of the field with the specified index in the {@code classToAccess}. + * @throws UnableToAccessFieldException If there was a problem getting the field or the value. + * @deprecated Use {@link #getPrivateValue(Class, Object, String)} because field indices change a lot more often than field names do. + * TODO: Remove in 1.15 + */ + @Deprecated + @Nullable public static T getPrivateValue(Class classToAccess, E instance, int fieldIndex) { try { - Field f = classToAccess.getDeclaredFields()[fieldIndex]; - f.setAccessible(true); - return (T)f.get(instance); + return (T) findField(classToAccess, fieldIndex).get(instance); } catch (Exception e) { @@ -77,11 +95,27 @@ public class ObfuscationReflectionHelper } } + /** + * Gets the value a field with the specified name in the given class. + * Note: For performance, use {@link #findField(Class, String)} if you are getting the value more than once. + *

+ * Throws an exception if the field is not found or the value of the field cannot be gotten. + * + * @param classToAccess The class to find the field on. + * @param instance The instance of the {@code classToAccess}. + * @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a"). + * @param The type of the value. + * @param The type of the {@code classToAccess}. + * @return The value of the field with the specified name in the {@code classToAccess}. + * @throws UnableToAccessFieldException If there was a problem getting the field. + * @throws UnableToAccessFieldException If there was a problem getting the value. + */ + @Nullable public static T getPrivateValue(Class classToAccess, E instance, String fieldName) { try { - return (T)findField(classToAccess, remapName(INameMappingService.Domain.FIELD, fieldName)).get(instance); + return (T) findField(classToAccess, fieldName).get(instance); } catch (UnableToFindFieldException e) { @@ -95,13 +129,28 @@ public class ObfuscationReflectionHelper } } - public static void setPrivateValue(Class classToAccess, T instance, E value, int fieldIndex) + /** + * Sets the value a field with the specified index in the given class. + * Note: For performance, use {@link #findField(Class, int)} if you are setting the value more than once. + *

+ * Throws an exception if the field is not found or the value of the field cannot be set. + * + * @param classToAccess The class to find the field on. + * @param instance The instance of the {@code classToAccess}. + * @param value The new value for the field + * @param fieldIndex The index of the field in the {@code classToAccess}. + * @param The type of the value. + * @param The type of the {@code classToAccess}. + * @throws UnableToAccessFieldException If there was a problem setting the value of the field. + * @deprecated Use {@link #setPrivateValue(Class, Object, Object, String)} because field indices change a lot more often than field names do. + * TODO: Remove in 1.15 + */ + @Deprecated + public static void setPrivateValue(@Nonnull final Class classToAccess, @Nonnull final T instance, @Nullable final E value, int fieldIndex) { try { - Field f = classToAccess.getDeclaredFields()[fieldIndex]; - f.setAccessible(true); - f.set(instance, value); + findField(classToAccess, fieldIndex).set(instance, value); } catch (IllegalAccessException e) { @@ -110,11 +159,26 @@ public class ObfuscationReflectionHelper } } - public static void setPrivateValue(Class classToAccess, T instance, E value, String fieldName) + /** + * Sets the value a field with the specified name in the given class. + * Note: For performance, use {@link #findField(Class, String)} if you are setting the value more than once. + *

+ * Throws an exception if the field is not found or the value of the field cannot be set. + * + * @param classToAccess The class to find the field on. + * @param instance The instance of the {@code classToAccess}. + * @param value The new value for the field + * @param fieldName The name of the field in the {@code classToAccess}. + * @param The type of the value. + * @param The type of the {@code classToAccess}. + * @throws UnableToFindFieldException If there was a problem getting the field. + * @throws UnableToAccessFieldException If there was a problem setting the value of the field. + */ + public static void setPrivateValue(@Nonnull final Class classToAccess, @Nonnull final T instance, @Nullable final E value, @Nonnull final String fieldName) { try { - findField(classToAccess, remapName(INameMappingService.Domain.FIELD, fieldName)).set(instance, value); + findField(classToAccess, fieldName).set(instance, value); } catch (UnableToFindFieldException e) { @@ -135,16 +199,22 @@ public class ObfuscationReflectionHelper * Throws an exception if the method is not found. * * @param clazz The class to find the method on. - * @param methodName The SRG (obfuscated) name of the method to find(e.g. "func_12820_D"). + * @param methodName The SRG (unmapped) name of the method to find (e.g. "func_12820_D"). * @param parameterTypes The parameter types of the method to find. * @return The method with the specified name and parameters in the given class. + * @throws NullPointerException If {@code clazz} is null. + * @throws NullPointerException If {@code methodName} is null. + * @throws IllegalArgumentException If {@code methodName} is empty. + * @throws NullPointerException If {@code parameterTypes} is null. + * @throws UnableToFindMethodException If the method could not be found. */ @Nonnull - public static Method findMethod(@Nonnull Class clazz, @Nonnull String methodName, Class... parameterTypes) + public static Method findMethod(@Nonnull final Class clazz, @Nonnull final String methodName, @Nonnull final Class... parameterTypes) { - Preconditions.checkNotNull(clazz); - Preconditions.checkNotNull(methodName); - Preconditions.checkArgument(!methodName.isEmpty(), "Method name cannot be empty"); + Preconditions.checkNotNull(clazz, "Class to find method on cannot be null."); + Preconditions.checkNotNull(methodName, "Name of method to find cannot be null."); + Preconditions.checkArgument(!methodName.isEmpty(), "Name of method to find cannot be empty."); + Preconditions.checkNotNull(parameterTypes, "Parameter types of method to find cannot be null."); try { @@ -159,32 +229,35 @@ public class ObfuscationReflectionHelper } /** - * Finds a constructor in the specified class that has matching parameter types. + * Finds a constructor with the specified parameter types in the given class and makes it accessible. + * Note: For performance, store the returned value and avoid calling this repeatedly. + *

+ * Throws an exception if the constructor is not found. * - * @param klass The class to find the constructor in + * @param clazz The class to find the constructor in. * @param parameterTypes The parameter types of the constructor. - * @param The type - * @return The constructor - * @throws NullPointerException if {@code klass} is null - * @throws NullPointerException if {@code parameterTypes} is null - * @throws UnknownConstructorException if the constructor could not be found + * @param The type. + * @return The constructor with the specified parameters in the given class. + * @throws NullPointerException If {@code clazz} is null. + * @throws NullPointerException If {@code parameterTypes} is null. + * @throws UnknownConstructorException If the constructor could not be found. */ @Nonnull - public static Constructor findConstructor(@Nonnull final Class klass, @Nonnull final Class... parameterTypes) + public static Constructor findConstructor(@Nonnull final Class clazz, @Nonnull final Class... parameterTypes) { - Preconditions.checkNotNull(klass, "class"); - Preconditions.checkNotNull(parameterTypes, "parameter types"); + Preconditions.checkNotNull(clazz, "Class to find constructor on cannot be null."); + Preconditions.checkNotNull(parameterTypes, "Parameter types of constructor to find cannot be null."); try { - Constructor constructor = klass.getDeclaredConstructor(parameterTypes); + Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); return constructor; } catch (final NoSuchMethodException e) { final StringBuilder desc = new StringBuilder(); - desc.append(klass.getSimpleName()); + desc.append(clazz.getSimpleName()); StringJoiner joiner = new StringJoiner(", ", "(", ")"); for (Class type : parameterTypes) @@ -193,15 +266,35 @@ public class ObfuscationReflectionHelper } desc.append(joiner); - throw new UnknownConstructorException("Could not find constructor '" + desc.toString() + "' in " + klass); + throw new UnknownConstructorException("Could not find constructor '" + desc.toString() + "' in " + clazz); } } - private static Field findField(Class clazz, String name) + /** + * Finds a field with the specified name in the given class and makes it accessible. + * Note: For performance, store the returned value and avoid calling this repeatedly. + *

+ * Throws an exception if the field is not found. + * + * @param clazz The class to find the field on. + * @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a"). + * @param The type. + * @return The constructor with the specified parameters in the given class. + * @throws NullPointerException If {@code clazz} is null. + * @throws NullPointerException If {@code fieldName} is null. + * @throws IllegalArgumentException If {@code fieldName} is empty. + * @throws UnableToFindFieldException If the field could not be found. + */ + @Nonnull + public static Field findField(@Nonnull final Class clazz, @Nonnull final String fieldName) { + Preconditions.checkNotNull(clazz, "Class to find field on cannot be null."); + Preconditions.checkNotNull(fieldName, "Name of field to find cannot be null."); + Preconditions.checkArgument(!fieldName.isEmpty(), "Name of field to find cannot be empty."); + try { - Field f = clazz.getDeclaredField(name); + Field f = clazz.getDeclaredField(remapName(INameMappingService.Domain.FIELD, fieldName)); f.setAccessible(true); return f; } @@ -211,6 +304,35 @@ public class ObfuscationReflectionHelper } } + /** + * Finds a field with the specified index in the given class and makes it accessible. + * Note: For performance, store the returned value and avoid calling this repeatedly. + *

+ * Throws an exception if the field is not found. + * + * @param clazz The class to find the field on. + * @param fieldIndex The index of the field on the class + * @param The type. + * @return The constructor with the specified parameters in the given class. + * @throws NullPointerException If {@code clazz} is null. + * @throws UnableToFindFieldException If the field could not be found. + * @deprecated Use {@link #findField(Class, String)} because field indices change a lot more often than field names do. + * TODO: Remove in 1.15 + */ + @Deprecated + @Nonnull + public static Field findField(final Class clazz, final int fieldIndex) { + Preconditions.checkNotNull(clazz, "Class to find field on cannot be null."); + + try { + final Field f = clazz.getDeclaredFields()[fieldIndex]; + f.setAccessible(true); + return f; + } catch (Exception e) { + throw new UnableToFindFieldException(e); + } + } + public static class UnableToAccessFieldException extends RuntimeException { private UnableToAccessFieldException(Exception e)