diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java index bbbad165..b2d8ee28 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java @@ -35,6 +35,11 @@ public final class Packets { */ public static final int MAXIMUM_PACKET_ID = 255; + /** + * The maximum number of unique packet IDs. It's unlikely this will ever change. + */ + public static final int PACKET_COUNT = 256; + /** * List of packets sent only by the server. * @author Kristian diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java index c99e18e1..a163cb0a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/IntegerSet.java @@ -43,6 +43,18 @@ public class IntegerSet { this.array = new boolean[maximumCount]; } + /** + * Initialize a lookup table with a given maximum and value list. + *

+ * The provided elements must be in the range [0, count). + * @param maximumCount - the maximum element value and count. + * @param values - the elements to add to the set. + */ + public IntegerSet(int maximumCount, Collection values) { + this.array = new boolean[maximumCount]; + addAll(values); + } + /** * Determine whether or not the given element exists in the set. * @param element - the element to check. Must be in the range [0, count). diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 144a0a97..22b3be8a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentMap; @@ -39,6 +40,8 @@ import org.bukkit.WorldType; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -50,6 +53,7 @@ import com.comphenix.protocol.reflect.cloning.Cloner; import com.comphenix.protocol.reflect.cloning.CollectionCloner; import com.comphenix.protocol.reflect.cloning.FieldCloner; import com.comphenix.protocol.reflect.cloning.ImmutableDetector; +import com.comphenix.protocol.reflect.cloning.ReflectionCloner; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.instances.DefaultInstances; @@ -110,6 +114,10 @@ public class PacketContainer implements Serializable { }). build(); + // Packets that cannot be cloned by our default deep cloner + private static final IntegerSet CLONING_UNSUPPORTED = new IntegerSet(Packets.PACKET_COUNT, + Arrays.asList(Packets.Server.UPDATE_ATTRIBUTES)); + /** * Creates a packet container for a new packet. * @param id - ID of the packet to create. @@ -424,10 +432,17 @@ public class PacketContainer implements Serializable { * @return A deep copy of the current packet. */ public PacketContainer deepClone() { - Object clonedPacket = DEEP_CLONER.clone(getHandle()); + Object clonedPacket = null; + + // Fall back on the alternative (but slower) method of reading and writing back the packet + if (CLONING_UNSUPPORTED.contains(id)) { + clonedPacket = ReflectionCloner.clone(this).getHandle(); + } else { + clonedPacket = DEEP_CLONER.clone(getHandle()); + } return new PacketContainer(getID(), clonedPacket); } - + // To save space, we'll skip copying the inflated buffers in packet 51 and 56 private static Function getSpecializedDeepClonerFactory() { // Look at what you've made me do Java, look at it!! diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java index 73fdea0b..fafe48de 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java @@ -9,8 +9,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.apache.commons.lang.NotImplementedException; - import com.google.common.collect.Lists; /** @@ -186,7 +184,7 @@ public abstract class MethodInfo implements GenericDeclaration, Member { */ @Override public String toString() { - throw new NotImplementedException(); + throw new UnsupportedOperationException(); } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ReflectionCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ReflectionCloner.java new file mode 100644 index 00000000..2c4d3492 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ReflectionCloner.java @@ -0,0 +1,47 @@ +package com.comphenix.protocol.reflect.cloning; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Represents a cloner that can clone any class that implements Serializable. + * @author Kristian Stangeland + */ +public class ReflectionCloner implements Cloner { + + @Override + public boolean canClone(Object source) { + if (source == null) + return false; + return source instanceof Serializable; + } + + @Override + public Object clone(Object source) { + return clone(source); + } + + /** + * Clone the given object using serialization. + * @param obj - the object to clone. + * @return The cloned object. + * @throws RuntimeException If we were unable to clone the object. + */ + @SuppressWarnings("unchecked") + public static T clone(final T obj) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(out); + + oout.writeObject(obj); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + return (T) in.readObject(); + + } catch (Exception e) { + throw new RuntimeException("Unable to clone object " + obj, e); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java index 35b92d9a..c761554b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java @@ -7,8 +7,6 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; -import org.apache.commons.lang.NotImplementedException; - import com.comphenix.protocol.reflect.MethodInfo; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; @@ -65,7 +63,7 @@ public class FuzzyMethodContract extends AbstractFuzzyMember { @Override public boolean isMatch(Class[] value, Object parent) { - throw new NotImplementedException("Use the parameter match instead."); + throw new UnsupportedOperationException("Use the parameter match instead."); } @Override diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 917b160a..c6e67b0c 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -20,8 +20,15 @@ package com.comphenix.protocol.events; import static org.junit.Assert.*; import java.lang.reflect.Array; import java.util.List; +import java.util.UUID; + +import net.minecraft.server.v1_6_R2.AttributeModifier; +import net.minecraft.server.v1_6_R2.AttributeSnapshot; +import net.minecraft.server.v1_6_R2.Packet44UpdateAttributes; import org.apache.commons.lang.SerializationUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; // Will have to be updated for every version though import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory; @@ -317,7 +324,29 @@ public class PacketContainerTest { assertEquals(3, copy.getID()); assertEquals("Test", copy.getStrings().read(0)); } + + @Test + public void testAttributeList() { + PacketContainer attribute = new PacketContainer(Packets.Server.UPDATE_ATTRIBUTES); + attribute.getIntegers().write(0, 123); // Entity ID + + // Initialize some test data + List modifiers = Lists.newArrayList( + new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10, 0)); + AttributeSnapshot snapshot = new AttributeSnapshot( + (Packet44UpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers); + + attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot)); + PacketContainer cloned = attribute.deepClone(); + AttributeSnapshot clonedSnapshot = (AttributeSnapshot) cloned.getSpecificModifier(List.class).read(0).get(0); + + assertEquals( + ToStringBuilder.reflectionToString(snapshot, ToStringStyle.SHORT_PREFIX_STYLE), + ToStringBuilder.reflectionToString(clonedSnapshot, ToStringStyle.SHORT_PREFIX_STYLE)); + } + + @Test public void testDeepClone() { // Try constructing all the packets