summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChas Emerick <cemerick@snowtide.com>2010-05-03 14:44:38 -0400
committerStuart Halloway <stu@thinkrelevance.com>2010-05-04 09:24:40 -0400
commit68a14c88d11555353bb471efd94ba7d40c2d5008 (patch)
treebffbba3783ad9ea200bea46f6170be3cb6060701
parent58fee964ca7b3d3ffed9dfbb37bc4f9179973edb (diff)
Enabled Java Serialization for nearly all data structures - fixes 281
Signed-off-by: Stuart Halloway <stu@thinkrelevance.com>
-rw-r--r--src/jvm/clojure/lang/AFn.java4
-rw-r--r--src/jvm/clojure/lang/AFunction.java3
-rw-r--r--src/jvm/clojure/lang/APersistentMap.java3
-rw-r--r--src/jvm/clojure/lang/APersistentSet.java3
-rw-r--r--src/jvm/clojure/lang/APersistentVector.java4
-rw-r--r--src/jvm/clojure/lang/ASeq.java3
-rw-r--r--src/jvm/clojure/lang/ArrayChunk.java4
-rw-r--r--src/jvm/clojure/lang/Cons.java4
-rw-r--r--src/jvm/clojure/lang/EnumerationSeq.java7
-rw-r--r--src/jvm/clojure/lang/IteratorSeq.java6
-rw-r--r--src/jvm/clojure/lang/Namespace.java14
-rw-r--r--src/jvm/clojure/lang/Obj.java4
-rw-r--r--src/jvm/clojure/lang/PersistentArrayMap.java1
-rw-r--r--src/jvm/clojure/lang/PersistentHashMap.java3
-rw-r--r--src/jvm/clojure/lang/PersistentList.java3
-rw-r--r--src/jvm/clojure/lang/PersistentVector.java5
-rw-r--r--src/jvm/clojure/lang/RT.java14
-rw-r--r--src/jvm/clojure/lang/Range.java2
-rw-r--r--test/clojure/test_clojure.clj1
-rw-r--r--test/clojure/test_clojure/serialization.clj158
20 files changed, 223 insertions, 23 deletions
diff --git a/src/jvm/clojure/lang/AFn.java b/src/jvm/clojure/lang/AFn.java
index 075448e5..e6eafeba 100644
--- a/src/jvm/clojure/lang/AFn.java
+++ b/src/jvm/clojure/lang/AFn.java
@@ -12,9 +12,7 @@
package clojure.lang;
-import java.io.Serializable;
-
-public abstract class AFn implements IFn, Serializable{
+public abstract class AFn implements IFn {
public Object call() throws Exception{
return invoke();
diff --git a/src/jvm/clojure/lang/AFunction.java b/src/jvm/clojure/lang/AFunction.java
index baa11e1d..279bd6d7 100644
--- a/src/jvm/clojure/lang/AFunction.java
+++ b/src/jvm/clojure/lang/AFunction.java
@@ -12,9 +12,10 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.Comparator;
-public abstract class AFunction extends AFn implements IObj, Comparator, Fn{
+public abstract class AFunction extends AFn implements IObj, Comparator, Fn, Serializable {
public volatile MethodImplCache __methodImplCache;
diff --git a/src/jvm/clojure/lang/APersistentMap.java b/src/jvm/clojure/lang/APersistentMap.java
index 213eef71..0fe08cae 100644
--- a/src/jvm/clojure/lang/APersistentMap.java
+++ b/src/jvm/clojure/lang/APersistentMap.java
@@ -10,9 +10,10 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.*;
-public abstract class APersistentMap extends AFn implements IPersistentMap, Map, Iterable{
+public abstract class APersistentMap extends AFn implements IPersistentMap, Map, Iterable, Serializable {
int _hash = -1;
public String toString(){
diff --git a/src/jvm/clojure/lang/APersistentSet.java b/src/jvm/clojure/lang/APersistentSet.java
index 90275fcd..80415151 100644
--- a/src/jvm/clojure/lang/APersistentSet.java
+++ b/src/jvm/clojure/lang/APersistentSet.java
@@ -12,11 +12,12 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
-public abstract class APersistentSet extends AFn implements IPersistentSet, Collection, Set{
+public abstract class APersistentSet extends AFn implements IPersistentSet, Collection, Set, Serializable {
int _hash = -1;
final IPersistentMap impl;
diff --git a/src/jvm/clojure/lang/APersistentVector.java b/src/jvm/clojure/lang/APersistentVector.java
index a6b7405f..442c2ac6 100644
--- a/src/jvm/clojure/lang/APersistentVector.java
+++ b/src/jvm/clojure/lang/APersistentVector.java
@@ -12,11 +12,13 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.*;
public abstract class APersistentVector extends AFn implements IPersistentVector, Iterable,
List,
- RandomAccess, Comparable{
+ RandomAccess, Comparable,
+ Serializable {
int _hash = -1;
public String toString(){
diff --git a/src/jvm/clojure/lang/ASeq.java b/src/jvm/clojure/lang/ASeq.java
index ffa7fa47..24038a32 100644
--- a/src/jvm/clojure/lang/ASeq.java
+++ b/src/jvm/clojure/lang/ASeq.java
@@ -10,9 +10,10 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.*;
-public abstract class ASeq extends Obj implements ISeq, List{
+public abstract class ASeq extends Obj implements ISeq, List, Serializable {
transient int _hash = -1;
public String toString(){
diff --git a/src/jvm/clojure/lang/ArrayChunk.java b/src/jvm/clojure/lang/ArrayChunk.java
index e96b930b..88129a95 100644
--- a/src/jvm/clojure/lang/ArrayChunk.java
+++ b/src/jvm/clojure/lang/ArrayChunk.java
@@ -12,7 +12,9 @@
package clojure.lang;
-public final class ArrayChunk implements IChunk{
+import java.io.Serializable;
+
+public final class ArrayChunk implements IChunk, Serializable {
final Object[] array;
final int off;
diff --git a/src/jvm/clojure/lang/Cons.java b/src/jvm/clojure/lang/Cons.java
index ecb82a9d..83629c83 100644
--- a/src/jvm/clojure/lang/Cons.java
+++ b/src/jvm/clojure/lang/Cons.java
@@ -12,7 +12,9 @@
package clojure.lang;
-final public class Cons extends ASeq{
+import java.io.Serializable;
+
+final public class Cons extends ASeq implements Serializable {
private final Object _first;
private final ISeq _more;
diff --git a/src/jvm/clojure/lang/EnumerationSeq.java b/src/jvm/clojure/lang/EnumerationSeq.java
index 33485e40..42c733d7 100644
--- a/src/jvm/clojure/lang/EnumerationSeq.java
+++ b/src/jvm/clojure/lang/EnumerationSeq.java
@@ -12,6 +12,8 @@
package clojure.lang;
+import java.io.IOException;
+import java.io.NotSerializableException;
import java.util.Enumeration;
public class EnumerationSeq extends ASeq{
@@ -68,4 +70,9 @@ public ISeq next(){
public EnumerationSeq withMeta(IPersistentMap meta){
return new EnumerationSeq(meta, iter, state);
}
+
+private void writeObject (java.io.ObjectOutputStream out) throws IOException {
+ throw new NotSerializableException(getClass().getName());
+}
+
}
diff --git a/src/jvm/clojure/lang/IteratorSeq.java b/src/jvm/clojure/lang/IteratorSeq.java
index a510d53f..8a76cd10 100644
--- a/src/jvm/clojure/lang/IteratorSeq.java
+++ b/src/jvm/clojure/lang/IteratorSeq.java
@@ -10,6 +10,8 @@
package clojure.lang;
+import java.io.IOException;
+import java.io.NotSerializableException;
import java.util.Iterator;
public class IteratorSeq extends ASeq{
@@ -66,4 +68,8 @@ public ISeq next(){
public IteratorSeq withMeta(IPersistentMap meta){
return new IteratorSeq(meta, iter, state);
}
+
+private void writeObject (java.io.ObjectOutputStream out) throws IOException {
+ throw new NotSerializableException(getClass().getName());
+}
}
diff --git a/src/jvm/clojure/lang/Namespace.java b/src/jvm/clojure/lang/Namespace.java
index 85505d91..7849e04c 100644
--- a/src/jvm/clojure/lang/Namespace.java
+++ b/src/jvm/clojure/lang/Namespace.java
@@ -12,13 +12,15 @@
package clojure.lang;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
-public class Namespace extends AReference{
+public class Namespace extends AReference implements Serializable {
final public Symbol name;
-final AtomicReference<IPersistentMap> mappings = new AtomicReference<IPersistentMap>();
-final AtomicReference<IPersistentMap> aliases = new AtomicReference<IPersistentMap>();
+transient final AtomicReference<IPersistentMap> mappings = new AtomicReference<IPersistentMap>();
+transient final AtomicReference<IPersistentMap> aliases = new AtomicReference<IPersistentMap>();
final static ConcurrentHashMap<Symbol, Namespace> namespaces = new ConcurrentHashMap<Symbol, Namespace>();
@@ -204,4 +206,10 @@ public void removeAlias(Symbol alias) throws Exception{
map = getAliases();
}
}
+
+private Object readResolve() throws ObjectStreamException {
+ // ensures that serialized namespaces are "deserialized" to the
+ // namespace in the present runtime
+ return findOrCreate(name);
+}
}
diff --git a/src/jvm/clojure/lang/Obj.java b/src/jvm/clojure/lang/Obj.java
index 439e5760..bd7ee918 100644
--- a/src/jvm/clojure/lang/Obj.java
+++ b/src/jvm/clojure/lang/Obj.java
@@ -12,7 +12,9 @@
package clojure.lang;
-public abstract class Obj implements IObj{
+import java.io.Serializable;
+
+public abstract class Obj implements IObj, Serializable {
final IPersistentMap _meta;
diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java
index 7965c92e..5cdfdead 100644
--- a/src/jvm/clojure/lang/PersistentArrayMap.java
+++ b/src/jvm/clojure/lang/PersistentArrayMap.java
@@ -10,6 +10,7 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java
index 6f956a7f..935fab59 100644
--- a/src/jvm/clojure/lang/PersistentHashMap.java
+++ b/src/jvm/clojure/lang/PersistentHashMap.java
@@ -10,6 +10,7 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -292,7 +293,7 @@ static final class TransientHashMap extends ATransientMap {
}
}
-static interface INode{
+static interface INode extends Serializable {
INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf);
INode without(int shift, int hash, Object key);
diff --git a/src/jvm/clojure/lang/PersistentList.java b/src/jvm/clojure/lang/PersistentList.java
index 477d1cba..907b310a 100644
--- a/src/jvm/clojure/lang/PersistentList.java
+++ b/src/jvm/clojure/lang/PersistentList.java
@@ -10,9 +10,10 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.*;
-public class PersistentList extends ASeq implements IPersistentList, IReduce, List, Counted{
+public class PersistentList extends ASeq implements IPersistentList, IReduce, List, Counted {
private final Object _first;
private final IPersistentList _rest;
diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java
index a521eaa8..46f61bf9 100644
--- a/src/jvm/clojure/lang/PersistentVector.java
+++ b/src/jvm/clojure/lang/PersistentVector.java
@@ -12,13 +12,14 @@
package clojure.lang;
+import java.io.Serializable;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class PersistentVector extends APersistentVector implements IObj, IEditableCollection{
-static class Node{
- final AtomicReference<Thread> edit;
+static class Node implements Serializable {
+ transient final AtomicReference<Thread> edit;
final Object[] array;
Node(AtomicReference<Thread> edit, Object[] array){
diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java
index 8faf011e..4c9de96c 100644
--- a/src/jvm/clojure/lang/RT.java
+++ b/src/jvm/clojure/lang/RT.java
@@ -238,11 +238,19 @@ public static List<String> processCommandLine(String[] args){
}
static public final Object[] EMPTY_ARRAY = new Object[]{};
-static public final Comparator DEFAULT_COMPARATOR = new Comparator(){
- public int compare(Object o1, Object o2){
+static public final Comparator DEFAULT_COMPARATOR = new DefaultComparator();
+
+private static final class DefaultComparator implements Comparator, Serializable {
+ public int compare(Object o1, Object o2){
return Util.compare(o1, o2);
}
-};
+
+ private Object readResolve() throws ObjectStreamException {
+ // ensures that we aren't hanging onto a new default comparator for every
+ // sorted set, etc., we deserialize
+ return DEFAULT_COMPARATOR;
+ }
+}
static AtomicInteger id = new AtomicInteger(1);
diff --git a/src/jvm/clojure/lang/Range.java b/src/jvm/clojure/lang/Range.java
index 18a90901..4dc1add2 100644
--- a/src/jvm/clojure/lang/Range.java
+++ b/src/jvm/clojure/lang/Range.java
@@ -12,8 +12,6 @@
package clojure.lang;
-import java.util.concurrent.atomic.AtomicInteger;
-
public class Range extends ASeq implements IReduce, Counted{
final int end;
final int n;
diff --git a/test/clojure/test_clojure.clj b/test/clojure/test_clojure.clj
index 4613b2e8..81793ee5 100644
--- a/test/clojure/test_clojure.clj
+++ b/test/clojure/test_clojure.clj
@@ -54,6 +54,7 @@
:vectors
:annotations
:pprint
+ :serialization
])
(def test-namespaces
diff --git a/test/clojure/test_clojure/serialization.clj b/test/clojure/test_clojure/serialization.clj
new file mode 100644
index 00000000..cd71d083
--- /dev/null
+++ b/test/clojure/test_clojure/serialization.clj
@@ -0,0 +1,158 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+; which can be found in the file epl-v10.html at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+;; Author: Chas Emerick
+;; cemerick@snowtide.com
+
+(ns clojure.test-clojure.serialization
+ (:use clojure.test)
+ (:import (java.io ObjectOutputStream ObjectInputStream
+ ByteArrayOutputStream ByteArrayInputStream)))
+
+(defn- serialize
+ "Serializes a single object, returning a byte array."
+ [v]
+ (with-open [bout (ByteArrayOutputStream.)
+ oos (ObjectOutputStream. bout)]
+ (.writeObject oos v)
+ (.flush oos)
+ (.toByteArray bout)))
+
+(defn- deserialize
+ "Deserializes and returns a single object from the given byte array."
+ [bytes]
+ (with-open [ois (-> bytes ByteArrayInputStream. ObjectInputStream.)]
+ (.readObject ois)))
+
+(defrecord SerializationRecord [a b c])
+(defstruct SerializationStruct :a :b :c)
+
+(defn- build-via-transient
+ [coll]
+ (persistent!
+ (reduce conj! (transient coll) (map vec (partition 2 (range 1000))))))
+
+(defn- roundtrip
+ [v]
+ (let [rt (-> v serialize deserialize)
+ rt-seq (-> v seq serialize deserialize)]
+ (and (= v rt)
+ (= (seq v) (seq rt))
+ (= (seq v) rt-seq))))
+
+(deftest sequable-serialization
+ (are [val] (roundtrip val)
+ ; lists and related
+ (list)
+ (apply list (range 10))
+ (cons 0 nil)
+ (clojure.lang.Cons. 0 nil)
+
+ ; vectors
+ []
+ (into [] (range 10))
+ (into [] (range 25))
+ (into [] (range 100))
+ (into [] (range 500))
+ (into [] (range 1000))
+
+ ; maps
+ {}
+ {:a 5 :b 0}
+ (apply array-map (range 100))
+ (apply hash-map (range 100))
+
+ ; sets
+ #{}
+ #{'a 'b 'c}
+ (set (range 10))
+ (set (range 25))
+ (set (range 100))
+ (set (range 500))
+ (set (range 1000))
+ (sorted-set)
+ (sorted-set 'a 'b 'c)
+ (apply sorted-set (reverse (range 10)))
+ (apply sorted-set (reverse (range 25)))
+ (apply sorted-set (reverse (range 100)))
+ (apply sorted-set (reverse (range 500)))
+ (apply sorted-set (reverse (range 1000)))
+
+ ; queues
+ clojure.lang.PersistentQueue/EMPTY
+ (into clojure.lang.PersistentQueue/EMPTY (range 50))
+
+ ; lazy seqs
+ (lazy-seq nil)
+ (lazy-seq (range 50))
+
+ ; transient / persistent! round-trip
+ (build-via-transient [])
+ (build-via-transient {})
+ (build-via-transient #{})
+
+ ; array-seqs
+ (seq (make-array Object 10))
+ (seq (make-array Boolean/TYPE 10))
+ (seq (make-array Byte/TYPE 10))
+ (seq (make-array Character/TYPE 10))
+ (seq (make-array Double/TYPE 10))
+ (seq (make-array Float/TYPE 10))
+ (seq (make-array Integer/TYPE 10))
+ (seq (make-array Long/TYPE 10))
+
+ ; "records"
+ (SerializationRecord. 0 :foo (range 20))
+ (struct SerializationStruct 0 :foo (range 20))
+
+ ; misc seqs
+ (seq "s11n")
+ (range 50)
+ (rseq (apply sorted-set (reverse (range 100))))))
+
+(deftest misc-serialization
+ (are [v] (= v (-> v serialize deserialize))
+ 25/3
+ :keyword
+ ::namespaced-keyword
+ 'symbol))
+
+(deftest interned-serializations
+ (are [v] (identical? v (-> v serialize deserialize))
+ clojure.lang.RT/DEFAULT_COMPARATOR
+
+ ; namespaces just get deserialized back into the same-named ns in the present runtime
+ ; (they're referred to by defrecord instances)
+ *ns*))
+
+(deftest function-serialization
+ (let [capture 5]
+ (are [f] (= capture ((-> f serialize deserialize)))
+ (constantly 5)
+ (fn [] 5)
+ #(do 5)
+ (constantly capture)
+ (fn [] capture)
+ #(do capture))))
+
+(deftest check-unserializable-objects
+ (are [t] (thrown? java.io.NotSerializableException (serialize t))
+ ;; transients
+ (transient [])
+ (transient {})
+ (transient #{})
+
+ ;; reference types
+ (atom nil)
+ (ref nil)
+ (agent nil)
+ #'+
+
+ ;; stateful seqs
+ (enumeration-seq (java.util.Collections/enumeration (range 50)))
+ (iterator-seq (.iterator (range 50))))) \ No newline at end of file