Make JsonValue serializable

This enables storing JsonValues in the HTTP session on the server

Bug: Issue 8364
Change-Id: I3c43b27dc2b86f16060170b425b9754a8a5d3d54
diff --git a/elemental/src/elemental/json/JsonValue.java b/elemental/src/elemental/json/JsonValue.java
index f4f2154..c0de7ab 100644
--- a/elemental/src/elemental/json/JsonValue.java
+++ b/elemental/src/elemental/json/JsonValue.java
@@ -15,10 +15,12 @@
  */
 package elemental.json;
 
+import java.io.Serializable;
+
 /**
  * Base interface for all Json values.
  */
-public interface JsonValue {
+public interface JsonValue extends Serializable {
 
   /**
    * Coerces underlying value to boolean according to the rules of Javascript coercion.
diff --git a/elemental/src/elemental/json/impl/JreJsonArray.java b/elemental/src/elemental/json/impl/JreJsonArray.java
index 5a6a240..c31ba33 100644
--- a/elemental/src/elemental/json/impl/JreJsonArray.java
+++ b/elemental/src/elemental/json/impl/JreJsonArray.java
@@ -15,9 +15,13 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import elemental.json.Json;
 import elemental.json.JsonArray;
 import elemental.json.JsonBoolean;
 import elemental.json.JsonFactory;
@@ -32,9 +36,11 @@
  */
 public class JreJsonArray extends JreJsonValue implements JsonArray {
 
-  private ArrayList<JsonValue> arrayValues = new ArrayList<JsonValue>();
+  private static final long serialVersionUID = 1L;
 
-  private JsonFactory factory;
+  private transient ArrayList<JsonValue> arrayValues = new ArrayList<JsonValue>();
+
+  private transient JsonFactory factory;
 
   public JreJsonArray(JsonFactory factory) {
     this.factory = factory;
@@ -162,4 +168,16 @@
     }
     visitor.endVisit(this, ctx);
   }
+
+  private void readObject(ObjectInputStream stream)
+          throws IOException, ClassNotFoundException {
+    JreJsonArray instance = parseJson(stream);
+    this.factory = Json.instance();
+    this.arrayValues = instance.arrayValues;
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.writeObject(toJson());
+  }
+
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonBoolean.java b/elemental/src/elemental/json/impl/JreJsonBoolean.java
index a947fd3..e48895f 100644
--- a/elemental/src/elemental/json/impl/JreJsonBoolean.java
+++ b/elemental/src/elemental/json/impl/JreJsonBoolean.java
@@ -15,6 +15,11 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import elemental.json.Json;
 import elemental.json.JsonBoolean;
 import elemental.json.JsonType;
 import elemental.json.JsonValue;
@@ -24,7 +29,9 @@
  */
 public class JreJsonBoolean extends JreJsonValue implements JsonBoolean {
 
-  private boolean bool;
+  private static final long serialVersionUID = 1L;
+
+  private transient boolean bool;
   public JreJsonBoolean(boolean bool) {
     this.bool = bool;
   }
@@ -69,4 +76,15 @@
   public String toJson() throws IllegalStateException {
     return String.valueOf(bool);
   }
+
+  private void readObject(ObjectInputStream stream)
+          throws IOException, ClassNotFoundException {
+    JreJsonBoolean instance = parseJson(stream);
+    this.bool = instance.bool;
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.writeObject(toJson());
+  }
+
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonNull.java b/elemental/src/elemental/json/impl/JreJsonNull.java
index 25acab1..7336417 100644
--- a/elemental/src/elemental/json/impl/JreJsonNull.java
+++ b/elemental/src/elemental/json/impl/JreJsonNull.java
@@ -15,6 +15,11 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+
 import elemental.json.JsonNull;
 import elemental.json.JsonType;
 import elemental.json.JsonValue;
@@ -24,6 +29,8 @@
  */
 public class JreJsonNull extends JreJsonValue implements JsonNull {
 
+  private static final long serialVersionUID = 1L;
+
   public static final JsonNull NULL_INSTANCE = new JreJsonNull();
 
   @Override
@@ -62,4 +69,9 @@
   public String toJson() {
     return null;
   }
+
+  private Object readResolve() throws ObjectStreamException {
+    return NULL_INSTANCE;
+  }
+
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonNumber.java b/elemental/src/elemental/json/impl/JreJsonNumber.java
index 4fb2842..26c6e61 100644
--- a/elemental/src/elemental/json/impl/JreJsonNumber.java
+++ b/elemental/src/elemental/json/impl/JreJsonNumber.java
@@ -15,6 +15,11 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import elemental.json.Json;
 import elemental.json.JsonNumber;
 import elemental.json.JsonType;
 import elemental.json.JsonValue;
@@ -24,7 +29,9 @@
  */
 public class JreJsonNumber extends JreJsonValue implements JsonNumber {
 
-  private double number;
+  private static final long serialVersionUID = 1L;
+
+  private transient double number;
 
   public JreJsonNumber(double number) {
     this.number = number;
@@ -74,4 +81,14 @@
     }
     return toReturn;
   }
+
+  private void readObject(ObjectInputStream stream)
+          throws IOException, ClassNotFoundException {
+    JreJsonNumber instance = parseJson(stream);
+    this.number = instance.number;
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.writeObject(toJson());
+  }
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonObject.java b/elemental/src/elemental/json/impl/JreJsonObject.java
index 992f9a0..732def2 100755
--- a/elemental/src/elemental/json/impl/JreJsonObject.java
+++ b/elemental/src/elemental/json/impl/JreJsonObject.java
@@ -15,6 +15,9 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -22,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 
+import elemental.json.Json;
 import elemental.json.JsonArray;
 import elemental.json.JsonBoolean;
 import elemental.json.JsonFactory;
@@ -36,6 +40,8 @@
  */
 public class JreJsonObject extends JreJsonValue implements JsonObject {
 
+  private static final long serialVersionUID = 1L;
+
   private static List<String> stringifyOrder(String[] keys) {
     List<String> toReturn = new ArrayList<String>();
     List<String> nonNumeric = new ArrayList<String>();
@@ -51,8 +57,8 @@
     return toReturn;
   }
 
-  private JsonFactory factory;
-  private Map<String, JsonValue> map = new LinkedHashMap<String, JsonValue>();
+  private transient JsonFactory factory;
+  private transient Map<String, JsonValue> map = new LinkedHashMap<String, JsonValue>();
 
   public JreJsonObject(JsonFactory factory) {
     this.factory = factory;
@@ -175,4 +181,16 @@
     }
     visitor.endVisit(this, ctx);
   }
+
+  private void readObject(ObjectInputStream stream)
+          throws IOException, ClassNotFoundException {
+    JreJsonObject instance = parseJson(stream);
+    this.factory = Json.instance();
+    this.map = instance.map;
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.writeObject(toJson());
+  }
+
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonString.java b/elemental/src/elemental/json/impl/JreJsonString.java
index f282245..690ea10 100644
--- a/elemental/src/elemental/json/impl/JreJsonString.java
+++ b/elemental/src/elemental/json/impl/JreJsonString.java
@@ -15,6 +15,11 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import elemental.json.Json;
 import elemental.json.JsonString;
 import elemental.json.JsonType;
 import elemental.json.JsonValue;
@@ -24,7 +29,9 @@
  */
 public class JreJsonString extends JreJsonValue implements JsonString {
 
-  private String string;
+  private static final long serialVersionUID = 1L;
+
+  private transient String string;
 
   public JreJsonString(String string) {
     this.string = string;
@@ -78,4 +85,15 @@
   public String toJson() throws IllegalStateException {
     return JsonUtil.quote(getString());
   }
+
+  private void readObject(ObjectInputStream stream) throws IOException,
+      ClassNotFoundException {
+    JreJsonString instance = parseJson(stream);
+    this.string = instance.string;
+  }
+
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    stream.writeObject(toJson());
+  }
+
 }
diff --git a/elemental/src/elemental/json/impl/JreJsonValue.java b/elemental/src/elemental/json/impl/JreJsonValue.java
index 313f8fb..94eccca 100644
--- a/elemental/src/elemental/json/impl/JreJsonValue.java
+++ b/elemental/src/elemental/json/impl/JreJsonValue.java
@@ -15,6 +15,10 @@
  */
 package elemental.json.impl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import elemental.json.Json;
 import elemental.json.JsonValue;
 
 /**
@@ -28,4 +32,10 @@
   public Object toNative() {
     return this;
   }
+
+  protected static <T extends JsonValue> T parseJson(ObjectInputStream stream)
+    throws ClassNotFoundException, IOException {
+    String jsonString = (String) stream.readObject();
+    return Json.instance().parse(jsonString);
+  }
 }
diff --git a/elemental/tests/elemental/json/impl/JreJsonSerialization.java b/elemental/tests/elemental/json/impl/JreJsonSerialization.java
new file mode 100644
index 0000000..a3097a7
--- /dev/null
+++ b/elemental/tests/elemental/json/impl/JreJsonSerialization.java
@@ -0,0 +1,114 @@
+package elemental.json.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import junit.framework.TestCase;
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonNull;
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+public class JreJsonSerialization extends TestCase {
+
+  public void testSerializeNull() throws Exception {
+    JsonNull null1 = Json.createNull();
+    JsonNull null2 = Json.createNull();
+    JsonValue out = serializeDeserialize(null1);
+    assertJsonEquals(null1, out);
+    assertSame(null1, out);
+    assertSame(null2, out);
+    assertSame(JreJsonNull.NULL_INSTANCE, out);
+  }
+
+  public void testSerializeObject() throws Exception {
+    JsonObject foo = Json.createObject();
+    foo.put("true", true);
+    foo.put("string", "string");
+    foo.put("number", 1.25);
+
+    JsonObject subObject = Json.createObject();
+    subObject.put("false", false);
+    subObject.put("string2", "string2");
+    subObject.put("number", -151);
+
+    JsonArray subArray = Json.createArray();
+    subArray.set(0, true);
+    subArray.set(1, 1);
+    subArray.set(2, "2");
+
+    foo.put("object", subObject);
+    foo.put("array", subArray);
+    foo.put("null", Json.createNull());
+
+    assertJsonEqualsAfterSerialization(foo);
+  }
+
+  public void testSerializeArray() throws Exception {
+    JsonObject subObject = Json.createObject();
+    subObject.put("false", false);
+    subObject.put("string2", "string2");
+    subObject.put("number", -151);
+
+    JsonArray subArray = Json.createArray();
+    subArray.set(0, true);
+    subArray.set(1, 1);
+    subArray.set(2, "2");
+
+    JsonArray array = Json.createArray();
+    array.set(0, true);
+    array.set(1, false);
+    array.set(2, 2);
+    array.set(3, "3");
+    array.set(4, subObject);
+    array.set(5, subArray);
+
+    assertJsonEqualsAfterSerialization(array);
+  }
+
+  public void testSerializeBoolean() throws Exception {
+    assertJsonEqualsAfterSerialization(Json.create(true));
+    assertJsonEqualsAfterSerialization(Json.create(false));
+  }
+
+  public void testSerializeString() throws Exception {
+    assertJsonEqualsAfterSerialization(Json.create("foo"));
+    assertJsonEqualsAfterSerialization(Json.create(""));
+  }
+
+  public void testSerializeNumber() throws Exception {
+    assertJsonEqualsAfterSerialization(Json.create(0));
+    assertJsonEqualsAfterSerialization(Json.create(-1.213123123));
+  }
+
+  private <T extends Serializable & JsonValue> void assertJsonEqualsAfterSerialization(
+      T in) throws Exception {
+    T out = serializeDeserialize(in);
+    assertNotSame(in, out);
+    assertJsonEquals(in, out);
+  }
+
+  private void assertJsonEquals(JsonValue a, JsonValue b) {
+    assertEquals(a.toJson(), b.toJson());
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends Serializable & JsonValue> T serializeDeserialize(
+      T originalJsonValue) throws Exception {
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    ObjectOutputStream out = new ObjectOutputStream(buffer);
+    out.writeObject(originalJsonValue);
+    out.close();
+
+    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
+        buffer.toByteArray()));
+    T processedJsonValue = (T) in.readObject();
+    in.close();
+    return processedJsonValue;
+  }
+
+}