Added documentation to ObfuscationReflectionHelper adn deprecated index based functions. (#5893)

This commit is contained in:
Cadiboo 2019-07-09 12:44:21 +10:00 committed by LexManos
parent 61bea2bfda
commit 3fe08f149d
1 changed files with 160 additions and 38 deletions

View File

@ -21,11 +21,10 @@ package net.minecraftforge.fml.common;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner; import java.util.StringJoiner;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import cpw.mods.modlauncher.api.INameMappingService; import cpw.mods.modlauncher.api.INameMappingService;
import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.FMLLoader;
@ -44,31 +43,50 @@ import com.google.common.base.Preconditions;
* *
* In other cases, AccessTransformers may be used. * 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 public class ObfuscationReflectionHelper
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final Logger LOGGER = LogManager.getLogger();
private static final Marker REFLECTION = MarkerManager.getMarker("REFLECTION"); private static final Marker REFLECTION = MarkerManager.getMarker("REFLECTION");
private static final Map<String, String> 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) public static String remapName(INameMappingService.Domain domain, String name)
{ {
return FMLLoader.getNameFunction("srg").map(f->f.apply(domain, name)).orElse(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.
* <p>
* 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 <T> The type of the value.
* @param <E> 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, E> T getPrivateValue(Class<? super E> classToAccess, E instance, int fieldIndex) public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, int fieldIndex)
{ {
try try
{ {
Field f = classToAccess.getDeclaredFields()[fieldIndex]; return (T) findField(classToAccess, fieldIndex).get(instance);
f.setAccessible(true);
return (T)f.get(instance);
} }
catch (Exception e) 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.
* <p>
* 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 <T> The type of the value.
* @param <E> 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, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String fieldName) public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String fieldName)
{ {
try try
{ {
return (T)findField(classToAccess, remapName(INameMappingService.Domain.FIELD, fieldName)).get(instance); return (T) findField(classToAccess, fieldName).get(instance);
} }
catch (UnableToFindFieldException e) catch (UnableToFindFieldException e)
{ {
@ -95,13 +129,28 @@ public class ObfuscationReflectionHelper
} }
} }
public static <T, E> void setPrivateValue(Class<? super T> 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.
* <p>
* 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 <T> The type of the value.
* @param <E> 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 <T, E> void setPrivateValue(@Nonnull final Class<? super T> classToAccess, @Nonnull final T instance, @Nullable final E value, int fieldIndex)
{ {
try try
{ {
Field f = classToAccess.getDeclaredFields()[fieldIndex]; findField(classToAccess, fieldIndex).set(instance, value);
f.setAccessible(true);
f.set(instance, value);
} }
catch (IllegalAccessException e) catch (IllegalAccessException e)
{ {
@ -110,11 +159,26 @@ public class ObfuscationReflectionHelper
} }
} }
public static <T, E> void setPrivateValue(Class<? super T> 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.
* <p>
* 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 <T> The type of the value.
* @param <E> 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 <T, E> void setPrivateValue(@Nonnull final Class<? super T> classToAccess, @Nonnull final T instance, @Nullable final E value, @Nonnull final String fieldName)
{ {
try try
{ {
findField(classToAccess, remapName(INameMappingService.Domain.FIELD, fieldName)).set(instance, value); findField(classToAccess, fieldName).set(instance, value);
} }
catch (UnableToFindFieldException e) catch (UnableToFindFieldException e)
{ {
@ -135,16 +199,22 @@ public class ObfuscationReflectionHelper
* Throws an exception if the method is not found. * Throws an exception if the method is not found.
* *
* @param clazz The class to find the method on. * @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. * @param parameterTypes The parameter types of the method to find.
* @return The method with the specified name and parameters in the given class. * @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 @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(clazz, "Class to find method on cannot be null.");
Preconditions.checkNotNull(methodName); Preconditions.checkNotNull(methodName, "Name of method to find cannot be null.");
Preconditions.checkArgument(!methodName.isEmpty(), "Method name cannot be empty"); 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 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.
* <p>
* 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 parameterTypes The parameter types of the constructor.
* @param <T> The type * @param <T> The type.
* @return The constructor * @return The constructor with the specified parameters in the given class.
* @throws NullPointerException if {@code klass} is null * @throws NullPointerException If {@code clazz} is null.
* @throws NullPointerException if {@code parameterTypes} is null * @throws NullPointerException If {@code parameterTypes} is null.
* @throws UnknownConstructorException if the constructor could not be found * @throws UnknownConstructorException If the constructor could not be found.
*/ */
@Nonnull @Nonnull
public static <T> Constructor<T> findConstructor(@Nonnull final Class<T> klass, @Nonnull final Class<?>... parameterTypes) public static <T> Constructor<T> findConstructor(@Nonnull final Class<T> clazz, @Nonnull final Class<?>... parameterTypes)
{ {
Preconditions.checkNotNull(klass, "class"); Preconditions.checkNotNull(clazz, "Class to find constructor on cannot be null.");
Preconditions.checkNotNull(parameterTypes, "parameter types"); Preconditions.checkNotNull(parameterTypes, "Parameter types of constructor to find cannot be null.");
try try
{ {
Constructor<T> constructor = klass.getDeclaredConstructor(parameterTypes); Constructor<T> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true); constructor.setAccessible(true);
return constructor; return constructor;
} }
catch (final NoSuchMethodException e) catch (final NoSuchMethodException e)
{ {
final StringBuilder desc = new StringBuilder(); final StringBuilder desc = new StringBuilder();
desc.append(klass.getSimpleName()); desc.append(clazz.getSimpleName());
StringJoiner joiner = new StringJoiner(", ", "(", ")"); StringJoiner joiner = new StringJoiner(", ", "(", ")");
for (Class<?> type : parameterTypes) for (Class<?> type : parameterTypes)
@ -193,15 +266,35 @@ public class ObfuscationReflectionHelper
} }
desc.append(joiner); 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.
* <p>
* 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 <T> 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 <T> Field findField(@Nonnull final Class<? super T> 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 try
{ {
Field f = clazz.getDeclaredField(name); Field f = clazz.getDeclaredField(remapName(INameMappingService.Domain.FIELD, fieldName));
f.setAccessible(true); f.setAccessible(true);
return f; 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.
* <p>
* 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 <T> 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 <T> Field findField(final Class<? super T> 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 public static class UnableToAccessFieldException extends RuntimeException
{ {
private UnableToAccessFieldException(Exception e) private UnableToAccessFieldException(Exception e)