Improve auto wrapper handling (#1518)

This commit is contained in:
Pasqual Koschmieder 2022-02-25 06:02:11 +01:00 committed by GitHub
parent baecaf4ca4
commit e44d1e6051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 180 additions and 51 deletions

View File

@ -16,6 +16,12 @@
*/
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.google.common.base.Defaults;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@ -41,9 +47,18 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author dmulloy2
*/
public class AutoWrapper<T> implements EquivalentConverter<T> {
private static final Object[] NO_ARGS = new Object[0];
private Map<Integer, Function<Object, Object>> wrappers = new HashMap<>();
private Map<Integer, Function<Object, Object>> unwrappers = new HashMap<>();
// lazy
private FieldAccessor[] nmsAccessors;
private FieldAccessor[] wrapperAccessors;
private Object[] nmsDefaultArgs;
private ConstructorAccessor nmsInstanceCreator;
private Class<T> wrapperClass;
private Class<?> nmsClass;
@ -79,75 +94,77 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex);
}
Field[] wrapperFields = wrapperClass.getDeclaredFields();
Field[] nmsFields = Arrays
.stream(nmsClass.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.toArray(Field[]::new);
// ensures that all accessors are present
computeFieldAccessors();
for (int i = 0; i < wrapperFields.length; i++) {
try {
Field wrapperField = wrapperFields[i];
for (int i = 0; i < wrapperAccessors.length; i++) {
FieldAccessor source = nmsAccessors[i];
FieldAccessor target = wrapperAccessors[i];
Field nmsField = nmsFields[i];
if (!nmsField.isAccessible())
nmsField.setAccessible(true);
Object value = source.get(nmsObject);
if (wrappers.containsKey(i))
value = wrappers.get(i).apply(value);
Object value = nmsField.get(nmsObject);
if (wrappers.containsKey(i))
value = wrappers.get(i).apply(value);
wrapperField.set(instance, value);
} catch (Exception ex) {
throw new InvalidWrapperException("Failed to wrap field at index " + i, ex);
}
target.set(instance, value);
}
return instance;
}
public Object unwrap(Object wrapper) {
Object instance;
// ensures that all accessors are present
computeFieldAccessors();
computeNmsConstructorAccess();
try {
instance = nmsClass.newInstance();
} catch (ReflectiveOperationException ex) {
throw new InvalidWrapperException("Failed to construct new " + nmsClass.getSimpleName(), ex);
}
Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs);
Field[] wrapperFields = wrapperClass.getDeclaredFields();
Field[] nmsFields = Arrays
.stream(nmsClass.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.toArray(Field[]::new);
for (int i = 0; i < wrapperAccessors.length; i++) {
FieldAccessor source = wrapperAccessors[i];
FieldAccessor target = nmsAccessors[i];
for (int i = 0; i < wrapperFields.length; i++) {
try {
Field wrapperField = wrapperFields[i];
Object value = source.get(wrapper);
if (unwrappers.containsKey(i))
value = unwrappers.get(i).apply(value);
Field nmsField = nmsFields[i];
if (!nmsField.isAccessible())
nmsField.setAccessible(true);
if (Modifier.isFinal(nmsField.getModifiers()))
unsetFinal(nmsField);
Object value = wrapperField.get(wrapper);
if (unwrappers.containsKey(i))
value = unwrappers.get(i).apply(value);
nmsField.set(instance, value);
} catch (ReflectiveOperationException ex) {
throw new InvalidWrapperException("Failed to unwrap field", ex);
}
target.set(instance, value);
}
return instance;
}
private void unsetFinal(Field field) throws ReflectiveOperationException {
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
private void computeFieldAccessors() {
if (nmsAccessors == null) {
nmsAccessors = Arrays
.stream(nmsClass.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(field -> Accessors.getFieldAccessor(field, true))
.toArray(FieldAccessor[]::new);
}
if (wrapperAccessors == null) {
wrapperAccessors = Arrays
.stream(wrapperClass.getDeclaredFields())
.map(field -> Accessors.getFieldAccessor(field, true))
.toArray(FieldAccessor[]::new);
}
}
private void computeNmsConstructorAccess() {
if (nmsInstanceCreator == null) {
ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass);
if (noArgs != null) {
// no args constructor is available - use it
nmsInstanceCreator = noArgs;
nmsDefaultArgs = NO_ARGS;
} else {
// use the first constructor of the class
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
nmsDefaultArgs = Arrays
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
.toArray(Object[]::new);
}
}
}
// ---- Equivalent conversion

View File

@ -0,0 +1,112 @@
package com.comphenix.protocol.wrappers;
import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.advancements.AdvancementDisplay;
import net.minecraft.advancements.AdvancementFrameType;
import net.minecraft.network.chat.ChatComponentText;
import net.minecraft.world.item.Items;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.BeforeClass;
import org.junit.Test;
public class AutoWrapperTest {
@BeforeClass
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
}
@Test
public void testToNms() {
WrappedAdvancementDisplay display = new WrappedAdvancementDisplay();
display.title = WrappedChatComponent.fromText("Test123");
display.description = WrappedChatComponent.fromText("Test567");
display.item = new ItemStack(Material.SAND);
display.background = new MinecraftKey("test");
display.frameType = WrappedFrameType.CHALLENGE;
display.announceChat = false;
display.showToast = true;
display.hidden = true;
display.x = 5f;
display.y = 67f;
AdvancementDisplay nms = (AdvancementDisplay) displayWrapper().unwrap(display);
assertTrue(nms.h());
assertTrue(nms.j());
assertFalse(nms.i());
assertEquals("test", nms.d().a());
assertEquals("Test123", nms.a().a());
assertEquals("Test567", nms.b().a());
assertSame(AdvancementFrameType.b, nms.e());
assertSame(Items.L, nms.c().c());
assertEquals(5f, nms.f(), 0f);
assertEquals(67f, nms.g(), 0f);
}
@Test
public void testFromNms() {
AdvancementDisplay display = new AdvancementDisplay(
new net.minecraft.world.item.ItemStack(Items.L),
new ChatComponentText("Test123"),
new ChatComponentText("Test567"),
new net.minecraft.resources.MinecraftKey("minecraft", "test"),
AdvancementFrameType.b,
true,
false,
true
);
display.a(5f, 67f);
WrappedAdvancementDisplay wrapped = displayWrapper().wrap(display);
assertTrue(wrapped.showToast);
assertTrue(wrapped.hidden);
assertFalse(wrapped.announceChat);
assertEquals("test", wrapped.background.getKey());
assertEquals("{\"text\":\"Test123\"}", wrapped.title.getJson());
assertEquals("{\"text\":\"Test567\"}", wrapped.description.getJson());
assertSame(WrappedFrameType.CHALLENGE, wrapped.frameType);
assertSame(Material.SAND, wrapped.item.getType());
assertEquals(5f, wrapped.x, 0f);
assertEquals(67f, wrapped.y, 0f);
}
private AutoWrapper<WrappedAdvancementDisplay> displayWrapper() {
return AutoWrapper
.wrap(WrappedAdvancementDisplay.class, "advancements.AdvancementDisplay")
.field(0, BukkitConverters.getWrappedChatComponentConverter())
.field(1, BukkitConverters.getWrappedChatComponentConverter())
.field(2, BukkitConverters.getItemStackConverter())
.field(3, MinecraftKey.getConverter())
.field(4, EnumWrappers.getGenericConverter(getMinecraftClass("advancements.AdvancementFrameType"),
WrappedFrameType.class));
}
public enum WrappedFrameType {
TASK,
CHALLENGE,
GOAL
}
public static final class WrappedAdvancementDisplay {
public WrappedChatComponent title;
public WrappedChatComponent description;
public ItemStack item;
public MinecraftKey background;
public WrappedFrameType frameType;
public boolean showToast;
public boolean announceChat;
public boolean hidden;
public float x;
public float y;
}
}