diff --git a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java index a0d87b61..02e295f7 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java +++ b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java @@ -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 implements EquivalentConverter { + private static final Object[] NO_ARGS = new Object[0]; + private Map> wrappers = new HashMap<>(); private Map> unwrappers = new HashMap<>(); + // lazy + private FieldAccessor[] nmsAccessors; + private FieldAccessor[] wrapperAccessors; + + private Object[] nmsDefaultArgs; + private ConstructorAccessor nmsInstanceCreator; + private Class wrapperClass; private Class nmsClass; @@ -79,75 +94,77 @@ public class AutoWrapper implements EquivalentConverter { 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 diff --git a/src/test/java/com/comphenix/protocol/wrappers/AutoWrapperTest.java b/src/test/java/com/comphenix/protocol/wrappers/AutoWrapperTest.java new file mode 100644 index 00000000..614c5ec3 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/AutoWrapperTest.java @@ -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 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; + } +}