diff options
author | Chas Emerick <cemerick@snowtide.com> | 2010-05-03 14:44:38 -0400 |
---|---|---|
committer | Stuart Halloway <stu@thinkrelevance.com> | 2010-05-04 09:24:40 -0400 |
commit | 68a14c88d11555353bb471efd94ba7d40c2d5008 (patch) | |
tree | bffbba3783ad9ea200bea46f6170be3cb6060701 | |
parent | 58fee964ca7b3d3ffed9dfbb37bc4f9179973edb (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.java | 4 | ||||
-rw-r--r-- | src/jvm/clojure/lang/AFunction.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/APersistentMap.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/APersistentSet.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/APersistentVector.java | 4 | ||||
-rw-r--r-- | src/jvm/clojure/lang/ASeq.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/ArrayChunk.java | 4 | ||||
-rw-r--r-- | src/jvm/clojure/lang/Cons.java | 4 | ||||
-rw-r--r-- | src/jvm/clojure/lang/EnumerationSeq.java | 7 | ||||
-rw-r--r-- | src/jvm/clojure/lang/IteratorSeq.java | 6 | ||||
-rw-r--r-- | src/jvm/clojure/lang/Namespace.java | 14 | ||||
-rw-r--r-- | src/jvm/clojure/lang/Obj.java | 4 | ||||
-rw-r--r-- | src/jvm/clojure/lang/PersistentArrayMap.java | 1 | ||||
-rw-r--r-- | src/jvm/clojure/lang/PersistentHashMap.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/PersistentList.java | 3 | ||||
-rw-r--r-- | src/jvm/clojure/lang/PersistentVector.java | 5 | ||||
-rw-r--r-- | src/jvm/clojure/lang/RT.java | 14 | ||||
-rw-r--r-- | src/jvm/clojure/lang/Range.java | 2 | ||||
-rw-r--r-- | test/clojure/test_clojure.clj | 1 | ||||
-rw-r--r-- | test/clojure/test_clojure/serialization.clj | 158 |
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 |