From b0cec61d66bb6a975fb6f26c011ef0fa85dc037e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 17 Jul 2013 20:07:02 +0200 Subject: [PATCH] Added more utility methods to the stream serializer class. It now supports the default Minecraft string encodign (UTF-16). --- .../protocol/events/NetworkMarker.java | 14 +++ .../protocol/utility/StreamSerializer.java | 88 +++++++++++++++++-- .../utility/StreamSerializerTest.java | 20 +++++ 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java index ccb6f2b6..a0ac8f62 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -10,6 +10,7 @@ import java.util.PriorityQueue; import javax.annotation.Nonnull; +import com.comphenix.protocol.utility.StreamSerializer; import com.google.common.base.Preconditions; import com.google.common.primitives.Ints; @@ -27,6 +28,9 @@ public class NetworkMarker { private final ConnectionSide side; + // Cache serializer too + private StreamSerializer serializer; + /** * Construct a new network marker. *

@@ -50,6 +54,16 @@ public class NetworkMarker { return side; } + /** + * Retrieve a utility class for serializing and deserializing Minecraft objects. + * @return Serialization utility class. + */ + public StreamSerializer getSerializer() { + if (serializer == null) + serializer = new StreamSerializer(); + return serializer; + } + /** * Retrieve the serialized packet data (excluding the header) from the network input stream. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index a1d6dda5..9cf083f3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -24,9 +24,12 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; */ public class StreamSerializer { // Cached methods - private static Method readItemMethod; - private static Method writeItemMethod; + private static Method READ_ITEM_METHOD; + private static Method WRITE_ITEM_METHOD; + private static Method READ_STRING_METHOD; + private static Method WRITE_STRING_METHOD; + /** * Read or deserialize an item stack from an underlying input stream. *

@@ -40,8 +43,8 @@ public class StreamSerializer { public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException { if (input == null) throw new IllegalArgumentException("Input stream cannot be NULL."); - if (readItemMethod == null) { - readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + if (READ_ITEM_METHOD == null) { + READ_ITEM_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( FuzzyMethodContract.newBuilder(). parameterCount(1). parameterDerivedOf(DataInput.class). @@ -49,7 +52,7 @@ public class StreamSerializer { build()); } try { - Object nmsItem = readItemMethod.invoke(null, input); + Object nmsItem = READ_ITEM_METHOD.invoke(null, input); // Convert back to a Bukkit item stack return MinecraftReflection.getBukkitItemStack(nmsItem); @@ -59,6 +62,41 @@ public class StreamSerializer { } } + /** + * Deserialize a string using the standard Minecraft UTF-16 encoding. + *

+ * Note that strings cannot exceed 32767 characters, regardless if maximum lenght. + * @param input - the input stream. + * @param maximumLength - the maximum lenght of the string. + * @return + * @throws IOException + */ + public String deserializeString(@Nonnull DataInputStream input, int maximumLength) throws IOException { + if (input == null) + throw new IllegalArgumentException("Input stream cannot be NULL."); + if (maximumLength > 32767) + throw new IllegalArgumentException("Maximum lenght cannot exceed 32767 characters."); + if (maximumLength < 0) + throw new IllegalArgumentException("Maximum lenght cannot be negative."); + + if (READ_STRING_METHOD == null) { + READ_STRING_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterDerivedOf(DataInput.class, 0). + parameterExactType(int.class, 1). + returnTypeExact(String.class). + build()); + } + + try { + // Convert back to a Bukkit item stack + return (String) READ_STRING_METHOD.invoke(null, input, maximumLength); + } catch (Exception e) { + throw new IOException("Cannot read Minecraft string.", e); + } + } + /** * Deserialize an item stack from a base-64 encoded string. * @param input - base-64 encoded string. @@ -68,7 +106,6 @@ public class StreamSerializer { public ItemStack deserializeItemStack(@Nonnull String input) throws IOException { if (input == null) throw new IllegalArgumentException("Input text cannot be NULL."); - ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input)); return deserializeItemStack(new DataInputStream(inputStream)); @@ -93,20 +130,53 @@ public class StreamSerializer { // Get the NMS version of the ItemStack Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); - if (writeItemMethod == null) - writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + if (WRITE_ITEM_METHOD == null) + WRITE_ITEM_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( FuzzyMethodContract.newBuilder(). parameterCount(2). parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0). parameterDerivedOf(DataOutput.class, 1). build()); try { - writeItemMethod.invoke(null, nmsItem, output); + WRITE_ITEM_METHOD.invoke(null, nmsItem, output); } catch (Exception e) { throw new IOException("Cannot write item stack " + stack, e); } } + /** + * Deserialize a string using the standard Minecraft UTF-16 encoding. + *

+ * Note that strings cannot exceed 32767 characters, regardless if maximum lenght. + * @param input - the input stream. + * @param maximumLength - the maximum lenght of the string. + * @return + * @throws IOException + */ + public void serializeString(@Nonnull DataOutputStream output, String text) throws IOException { + if (output == null) + throw new IllegalArgumentException("output stream cannot be NULL."); + if (text == null) + throw new IllegalArgumentException("text cannot be NULL."); + + if (WRITE_STRING_METHOD == null) { + WRITE_STRING_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterExactType(String.class, 0). + parameterDerivedOf(DataOutput.class, 1). + returnTypeVoid(). + build()); + } + + try { + // Convert back to a Bukkit item stack + WRITE_STRING_METHOD.invoke(null, text, output); + } catch (Exception e) { + throw new IOException("Cannot read Minecraft string.", e); + } + } + /** * Serialize an item stack as a base-64 encoded string. *

diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java index 0895023b..14c1a11d 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java @@ -2,6 +2,10 @@ package com.comphenix.protocol.utility; import static org.junit.Assert.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; import org.bukkit.Material; @@ -33,4 +37,20 @@ public class StreamSerializerTest { assertEquals(before.getType(), after.getType()); assertEquals(before.getAmount(), after.getAmount()); } + + @Test + public void testStrings() throws IOException { + StreamSerializer serializer = new StreamSerializer(); + String initial = "Hello - this is a ÆØÅ test."; + + // Buffer + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + serializer.serializeString(new DataOutputStream(buffer), initial); + + DataInputStream input = new DataInputStream( + new ByteArrayInputStream(buffer.toByteArray())); + String deserialized = serializer.deserializeString(input, 50); + + assertEquals(initial, deserialized); + } }