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