summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWesley Wolfe <weswolf@aol.com>2013-04-25 06:06:12 -0500
committerWesley Wolfe <weswolf@aol.com>2013-08-06 18:19:15 -0500
commite5a14e2dcc4bd5eafc5de1124898e2393df2d945 (patch)
tree3a75d284a00950c99893138f540c4982fdb6e93a
parent5ebcf56b9bd8d3e773316003003294fdfd51393a (diff)
downloadbukkit-e5a14e2dcc4bd5eafc5de1124898e2393df2d945.tar
bukkit-e5a14e2dcc4bd5eafc5de1124898e2393df2d945.tar.gz
bukkit-e5a14e2dcc4bd5eafc5de1124898e2393df2d945.tar.lz
bukkit-e5a14e2dcc4bd5eafc5de1124898e2393df2d945.tar.xz
bukkit-e5a14e2dcc4bd5eafc5de1124898e2393df2d945.zip
Add ConfigurationSerializable-Serializable compatibility. Adds BUKKIT-4662
This commit adds a comaptibility layer for use between ConfigurationSerializable and Java Serializable, such that when using the Bukkit object streams, any ConfigurationSerializable acts as if it implements Serializable for purposes of that wrapped stream. Included are a set of unit tests for the stream with a check for backward compatibility across versions.
-rw-r--r--src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java63
-rw-r--r--src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java53
-rw-r--r--src/main/java/org/bukkit/util/io/Wrapper.java23
-rw-r--r--src/main/javadoc/org/bukkit/util/io/package-info.java6
-rw-r--r--src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java173
5 files changed, 318 insertions, 0 deletions
diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
new file mode 100644
index 00000000..ed5675fe
--- /dev/null
+++ b/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java
@@ -0,0 +1,63 @@
+package org.bukkit.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+/**
+ * This class is designed to be used in conjunction with the {@link
+ * ConfigurationSerializable} API. It translates objects back to their
+ * original implementation after being serialized by {@link
+ * BukkitObjectInputStream}.
+ * <p>
+ * Behavior of implementations extending this class is not guaranteed across
+ * future versions.
+ */
+public class BukkitObjectInputStream extends ObjectInputStream {
+
+ /**
+ * Constructor provided to mirror super functionality.
+ *
+ * @throws IOException
+ * @throws SecurityException
+ * @see {@link ObjectInputStream#ObjectInputStream()}
+ */
+ protected BukkitObjectInputStream() throws IOException, SecurityException {
+ super();
+ super.enableResolveObject(true);
+ }
+
+ /**
+ * Object input stream decoration constructor.
+ *
+ * @param in
+ * @throws IOException
+ * @see {@link ObjectInputStream#ObjectInputStream(InputStream)}
+ */
+ public BukkitObjectInputStream(InputStream in) throws IOException {
+ super(in);
+ super.enableResolveObject(true);
+ }
+
+ @Override
+ protected Object resolveObject(Object obj) throws IOException {
+ if (obj instanceof Wrapper) {
+ try {
+ (obj = ConfigurationSerialization.deserializeObject(((Wrapper<?>) obj).map)).getClass(); // NPE
+ } catch (Throwable ex) {
+ throw newIOException("Failed to deserialize object", ex);
+ }
+ }
+
+ return super.resolveObject(obj);
+ }
+
+ private static IOException newIOException(String string, Throwable cause) {
+ IOException exception = new IOException(string);
+ exception.initCause(cause);
+ return exception;
+ }
+}
diff --git a/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java
new file mode 100644
index 00000000..afdd7e3f
--- /dev/null
+++ b/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java
@@ -0,0 +1,53 @@
+package org.bukkit.util.io;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+
+/**
+ * This class is designed to be used in conjunction with the {@link
+ * ConfigurationSerializable} API. It translates objects to an internal
+ * implementation for later deserialization using {@link
+ * BukkitObjectInputStream}.
+ * <p>
+ * Behavior of implementations extending this class is not guaranteed across
+ * future versions.
+ */
+public class BukkitObjectOutputStream extends ObjectOutputStream {
+
+ /**
+ * Constructor provided to mirror super functionality.
+ *
+ * @throws IOException
+ * @throws SecurityException
+ * @see {@link ObjectOutputStream#ObjectOutputStream()}
+ */
+ protected BukkitObjectOutputStream() throws IOException, SecurityException {
+ super();
+ super.enableReplaceObject(true);
+ }
+
+ /**
+ * Object output stream decoration constructor.
+ *
+ * @param out
+ * @throws IOException
+ * @see {@link ObjectOutputStream#ObjectOutputStream(OutputStream)}
+ */
+ public BukkitObjectOutputStream(OutputStream out) throws IOException {
+ super(out);
+ super.enableReplaceObject(true);
+ }
+
+ @Override
+ protected Object replaceObject(Object obj) throws IOException {
+ if (!(obj instanceof Serializable) && (obj instanceof ConfigurationSerializable)) {
+ obj = Wrapper.newWrapper((ConfigurationSerializable) obj);
+ }
+
+ return super.replaceObject(obj);
+ }
+}
diff --git a/src/main/java/org/bukkit/util/io/Wrapper.java b/src/main/java/org/bukkit/util/io/Wrapper.java
new file mode 100644
index 00000000..e45605b3
--- /dev/null
+++ b/src/main/java/org/bukkit/util/io/Wrapper.java
@@ -0,0 +1,23 @@
+package org.bukkit.util.io;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+import com.google.common.collect.ImmutableMap;
+
+class Wrapper<T extends Map<String, ?> & Serializable> implements Serializable {
+ private static final long serialVersionUID = -986209235411767547L;
+
+ final T map;
+
+ static Wrapper<ImmutableMap<String, ?>> newWrapper(ConfigurationSerializable obj) {
+ return new Wrapper<ImmutableMap<String, ?>>(ImmutableMap.<String, Object>builder().put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(obj.getClass())).putAll(obj.serialize()).build());
+ }
+
+ private Wrapper(T map) {
+ this.map = map;
+ }
+}
diff --git a/src/main/javadoc/org/bukkit/util/io/package-info.java b/src/main/javadoc/org/bukkit/util/io/package-info.java
new file mode 100644
index 00000000..8e46cca4
--- /dev/null
+++ b/src/main/javadoc/org/bukkit/util/io/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Classes used to facilitate stream processing for specific Bukkit concepts.
+ * <p>
+ */
+package org.bukkit.util.io;
+
diff --git a/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java b/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java
new file mode 100644
index 00000000..d0af4a01
--- /dev/null
+++ b/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java
@@ -0,0 +1,173 @@
+package org.bukkit.util.io;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.bukkit.Color;
+import org.bukkit.FireworkEffect;
+import org.bukkit.FireworkEffect.Type;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.util.Vector;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
+
+import com.google.common.collect.ImmutableList;
+
+@RunWith(Parameterized.class)
+public class BukkitObjectStreamTest {
+
+ @Parameters(name= "{index}: {0}")
+ public static List<Object[]> data() {
+ return ImmutableList.<Object[]>of(
+ new Object[] {
+ Color.class.getName(),
+ "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAABXNyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAEdAACPT10AANSRUR0AARCTFVFdAAFR1JFRU51cQB+AAMAAAAEdAAFQ29sb3JzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAA/3NxAH4AEQAAAP9zcQB+ABEAAAD/c3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAAAHNxAH4AEQAAAIBzcQB+ABEAAACAc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAAgHNxAH4AEQAAAIBxAH4AGnNxAH4ABXNxAH4ACHVxAH4AAwAAAARxAH4AC3EAfgAMcQB+AA1xAH4ADnVxAH4AAwAAAARxAH4AEHNxAH4AEQAAAP9xAH4AGnEAfgAac3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAA/3EAfgAac3EAfgARAAAApQ==",
+ ImmutableList.of(
+ Color.WHITE,
+ Color.TEAL,
+ Color.PURPLE,
+ Color.RED,
+ Color.ORANGE
+ )
+ },
+ new Object[] {
+ FireworkEffect.class.getName(),
+ "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAA3NyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAGdAACPT10AAdmbGlja2VydAAFdHJhaWx0AAZjb2xvcnN0AAtmYWRlLWNvbG9yc3QABHR5cGV1cQB+AAMAAAAGdAAIRmlyZXdvcmtzcgARamF2YS5sYW5nLkJvb2xlYW7NIHKA1Zz67gIAAVoABXZhbHVleHABc3EAfgATAHNxAH4AAHVxAH4AAwAAAAJzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAt0AANSRUR0AARCTFVFdAAFR1JFRU51cQB+AAMAAAAEdAAFQ29sb3JzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAHEAfgAicQB+ACJzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAtxAH4AG3EAfgAccQB+AB11cQB+AAMAAAAEcQB+AB9zcQB+ACAAAADAc3EAfgAgAAAAwHNxAH4AIAAAAMBzcQB+AAB1cQB+AAMAAAABc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+ABtxAH4AHHEAfgAddXEAfgADAAAABHEAfgAfc3EAfgAgAAAA/3NxAH4AIAAAAP9zcQB+ACAAAAD/dAAKQkFMTF9MQVJHRXNxAH4ABXNxAH4ACHVxAH4AAwAAAAZxAH4AC3EAfgAMcQB+AA1xAH4ADnEAfgAPcQB+ABB1cQB+AAMAAAAGcQB+ABJxAH4AFXEAfgAVc3EAfgAAdXEAfgADAAAAAXNxAH4ABXNxAH4ACHVxAH4AAwAAAARxAH4AC3EAfgAbcQB+ABxxAH4AHXVxAH4AAwAAAARxAH4AH3EAfgAic3EAfgAgAAAAgHEAfgAic3EAfgAAdXEAfgADAAAAAHQABEJBTExzcQB+AAVzcQB+AAh1cQB+AAMAAAAGcQB+AAtxAH4ADHEAfgANcQB+AA5xAH4AD3EAfgAQdXEAfgADAAAABnEAfgAScQB+ABRxAH4AFHNxAH4AAHVxAH4AAwAAAAFzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAtxAH4AG3EAfgAccQB+AB11cQB+AAMAAAAEcQB+AB9zcQB+ACAAAACAcQB+ACJxAH4AInEAfgA/dAAHQ1JFRVBFUg==",
+ ImmutableList.of(
+ FireworkEffect.builder()
+ .withColor(Color.BLACK, Color.SILVER)
+ .with(Type.BALL_LARGE)
+ .withFade(Color.WHITE)
+ .withFlicker()
+ .build(),
+ FireworkEffect.builder()
+ .withColor(Color.NAVY)
+ .build(),
+ FireworkEffect.builder()
+ .withColor(Color.MAROON)
+ .withTrail()
+ .withFlicker()
+ .with(Type.CREEPER)
+ .build()
+ ),
+ },
+ new Object[] {
+ Vector.class.getName(),
+ "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAABHNyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAEdAACPT10AAF4dAABeXQAAXp1cQB+AAMAAAAEdAAGVmVjdG9yc3IAEGphdmEubGFuZy5Eb3VibGWAs8JKKWv7BAIAAUQABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAAAAAABzcQB+ABEAAAAAAAAAAHNxAH4AEQAAAAAAAAAAc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARQIOFwo9cKPZzcQB+ABFAtCKcKPXCj3NxAH4AEUBzrpeNT987c3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARwEQTMzMzMzNzcQB+ABFASYAAAAAAAHNxAH4AEcCjqG3UQTVUc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARQd/////AAABzcQB+ABHB4AAAAAAAAHNxAH4AEQAAAAAAAAAA",
+ ImmutableList.of(
+ new Vector(0, 0, 0),
+ new Vector(624.72, 5154.61, 314.912),
+ new Vector(-40.15, 51, -2516.21451),
+ new Vector(Integer.MAX_VALUE, Integer.MIN_VALUE, 0)
+ )
+ });
+ }
+
+ @Parameter(0)
+ public String className;
+
+ @Parameter(1)
+ public String preEncoded;
+
+ @Parameter(2)
+ public List<ConfigurationSerializable> object;
+
+ @Test
+ public void checkSerlialization() throws Throwable {
+ // If this test fails, you may start your trek to debug by commenting the '@Ignore' on the next method
+ // (and of course, you would read those comments too)
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = null;
+ try {
+ oos = new BukkitObjectOutputStream(out);
+ oos.writeObject(object);
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ final byte[] preEncodedArray = Base64Coder.decode(preEncoded);
+
+ final Object readBack;
+ final Object preEncoded;
+
+ ObjectInputStream ois = null;
+ ObjectInputStream preois = null;
+ try {
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ ByteArrayInputStream preIn = new ByteArrayInputStream(preEncodedArray);
+ ois = new BukkitObjectInputStream(in);
+ preois = new BukkitObjectInputStream(preIn);
+
+ readBack = ois.readObject();
+ preEncoded = preois.readObject();
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException ex) {
+ }
+ }
+ if (preois != null) {
+ try {
+ preois.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+
+ assertThat(object, is(readBack));
+ assertThat(object, is(preEncoded));
+ }
+
+ @Ignore
+ @Test
+ public void preEncoded() throws Throwable {
+ // This test is placed in the case that a necessary change is made to change the encoding format
+ // Just remove the ignore (or run manually) and it'll give you the new pre-encoded values
+
+ // It really does not matter if the encoded array is different per system (hence why this test is set to not run),
+ // as long as all systems can deserialize it.
+
+ // The entire reason the pre-encoded string was added is to make a build (test) fail if someone accidentally makes it not backward-compatible
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = null;
+ try {
+ oos = new BukkitObjectOutputStream(out);
+ oos.writeObject(object);
+ oos.flush();
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ final String string = new String(Base64Coder.encode(out.toByteArray()));
+ try {
+ assertThat(preEncoded, is(string));
+ } catch (Throwable t) {
+ System.out.println(className + ": \"" + string + "\"");
+ throw t;
+ }
+ }
+}