From 7b61796506907a7be331ccd37ae54ef4c144bd26 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Tue, 22 Nov 2016 17:36:49 -0500 Subject: [PATCH] Add mechanism to clone NonNullLists --- .../reflect/cloning/BukkitCloner.java | 44 +++++++++++++++++++ .../protocol/utility/MinecraftReflection.java | 4 ++ .../reflect/cloning/AggregateClonerTest.java | 22 ++++++++++ 3 files changed, 70 insertions(+) diff --git a/modules/API/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java b/modules/API/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java index ec39ecb7..b4931abe 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java +++ b/modules/API/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java @@ -16,10 +16,14 @@ */ package com.comphenix.protocol.reflect.cloning; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.BukkitConverters; @@ -67,6 +71,11 @@ public class BukkitCloner implements Cloner { addClass(7, MinecraftReflection.getIBlockDataClass()); } catch (Throwable ex) { } + + try { + addClass(8, MinecraftReflection.getNonNullListClass()); + } catch (Throwable ex) { + } } private void addClass(int id, Class clazz) { @@ -122,8 +131,43 @@ public class BukkitCloner implements Cloner { case 7: EquivalentConverter blockDataConverter = BukkitConverters.getWrappedBlockDataConverter(); return blockDataConverter.getGeneric(clonableClasses.get(7), blockDataConverter.getSpecific(source).deepClone()); + case 8: + return nonNullListCloner().clone(source); default: throw new IllegalArgumentException("Cannot clone objects of type " + source.getClass()); } } + + private static Constructor nonNullList = null; + + private static final Cloner nonNullListCloner() { + return new Cloner() { + @Override + public boolean canClone(Object source) { + return MinecraftReflection.is(MinecraftReflection.getNonNullListClass(), source); + } + + @Override + public Object clone(Object source) { + StructureModifier modifier = new StructureModifier<>(source.getClass(), true).withTarget(source); + List list = (List) modifier.read(0); + Object empty = modifier.read(1); + + if (nonNullList == null) { + try { + nonNullList = source.getClass().getDeclaredConstructor(List.class, Object.class); + nonNullList.setAccessible(true); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Could not find NonNullList constructor", ex); + } + } + + try { + return nonNullList.newInstance(new ArrayList<>(list), empty); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Could not create new NonNullList", ex); + } + } + }; + } } diff --git a/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 909fbc2a..5e2018a2 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1836,6 +1836,10 @@ public class MinecraftReflection { } } + public static Class getNonNullListClass() { + return getMinecraftClass("NonNullList"); + } + /** * Retrieve a CraftItemStack from a given ItemStack. * @param bukkitItemStack - the Bukkit ItemStack to convert. diff --git a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java index a6dbb0f2..023ae549 100644 --- a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java +++ b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java @@ -1,14 +1,20 @@ package com.comphenix.protocol.reflect.cloning; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertArrayEquals; import java.util.Arrays; import java.util.List; +import net.minecraft.server.v1_11_R1.ItemStack; +import net.minecraft.server.v1_11_R1.NonNullList; + import org.junit.BeforeClass; import org.junit.Test; import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; public class AggregateClonerTest { @@ -22,4 +28,20 @@ public class AggregateClonerTest { List input = Arrays.asList(1, 2, 3); assertEquals(input, AggregateCloner.DEFAULT.clone(input)); } + + @Test + public void testNonNullList() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS); + + NonNullList list = NonNullList.a(16, ItemStack.a); + packet.getModifier().write(1, list); + + PacketContainer cloned = packet.deepClone(); + + @SuppressWarnings("unchecked") + NonNullList list1 = (NonNullList) cloned.getModifier().read(1); + + assertEquals(list.size(), list1.size()); + assertArrayEquals(list.toArray(), list1.toArray()); + } }