@@ -25,7 +25,16 @@ import _root_.java.io.{
2525 ObjectOutputStream
2626}
2727
28+ import com .esotericsoftware .kryo .serializers .JavaSerializer
29+ import com .esotericsoftware .kryo .DefaultSerializer
30+ import _root_ .java .util .concurrent .atomic .{AtomicBoolean , AtomicReference }
31+ import com .esotericsoftware .kryo .KryoSerializable
32+
2833object Externalizer {
34+ /* Tokens used to distinguish if we used Kryo or Java */
35+ private val KRYO = 0
36+ private val JAVA = 1
37+
2938 def apply [T ](t : T ): Externalizer [T ] = {
3039 val x = new Externalizer [T ]
3140 x.set(t)
@@ -39,24 +48,38 @@ object Externalizer {
3948 * work. Of course, Java serialization may fail if the contained
4049 * item is not Java serializable
4150 */
42- class Externalizer [T ] extends Externalizable {
43- private var item : Option [T ] = None
51+ class Externalizer [T ] extends Externalizable with KryoSerializable {
52+ // Either points to a result or a delegate Externalizer to fufil that result.
53+ private var item : Either [Externalizer [T ], Option [T ]] = Right (None )
54+ import Externalizer ._
55+
56+ @ transient private val doesJavaWork = new AtomicReference [Option [Boolean ]](None )
57+ @ transient private val testing = new AtomicBoolean (false )
58+ // For backwards compatibility
59+ private def KRYO = Externalizer .KRYO
4460
45- def getOption : Option [T ] = item
46- def get : T = item.get // This should never be None when get is called
61+ // No vals or var's below this line!
62+
63+ def getOption : Option [T ] = item match {
64+ case Left (e) => e.getOption
65+ case Right (i) => i
66+ }
67+
68+ def get : T = getOption.get // This should never be None when get is called
4769
4870 /** Unfortunately, Java serialization requires mutable objects if
4971 * you are going to control how the serialization is done.
5072 * Use the companion object to creat new instances of this
5173 */
5274 def set (it : T ): Unit = {
53- assert(item.isEmpty, " Tried to call .set on an already constructed Externalizer" )
54- item = Some (it)
75+ item match {
76+ case Left (e) => e.set(it)
77+ case Right (x) =>
78+ assert(x.isEmpty, " Tried to call .set on an already constructed Externalizer" )
79+ item = Right (Some (it))
80+ }
5581 }
5682
57- /* Tokens used to distinguish if we used Kryo or Java */
58- private val KRYO = 0
59- private val JAVA = 1
6083
6184 /** Override this to configure Kryo creation with a named subclass,
6285 * e.g.
@@ -70,35 +93,47 @@ class Externalizer[T] extends Externalizable {
7093 (new ScalaKryoInstantiator ).setReferences(true )
7194
7295 // 1 here is 1 thread, since we will likely only serialize once
73- private val kpool = KryoPool .withByteArrayOutputStream(1 , kryo)
96+ // this should not be a val because we don't want to capture a reference
97+
98+
99+ def javaWorks : Boolean =
100+ doesJavaWork.get match {
101+ case Some (v) => v
102+ case None => probeJavaWorks
103+ }
74104
75105 /** Try to round-trip and see if it works without error
76106 */
77- lazy val javaWorks : Boolean = {
107+ private def probeJavaWorks : Boolean = {
108+ if (! testing.compareAndSet(false , true )) return true
78109 try {
79110 val baos = new ByteArrayOutputStream ()
80111 val oos = new ObjectOutputStream (baos)
81- oos.writeObject(item )
112+ oos.writeObject(getOption )
82113 val bytes = baos.toByteArray
83114 val testInput = new ByteArrayInputStream (bytes)
84115 val ois = new ObjectInputStream (testInput)
85116 ois.readObject // this may throw
117+ doesJavaWork.set(Some (true ))
86118 true
87119 }
88120 catch {
89121 case t : Throwable =>
90122 Option (System .getenv.get(" CHILL_EXTERNALIZER_DEBUG" ))
91123 .filter(_.toBoolean)
92124 .foreach { _ => t.printStackTrace }
125+ doesJavaWork.set(Some (false ))
93126 false
94127 }
128+ finally {
129+ testing.set(false )
130+ }
95131 }
96132
97- private def safeToBytes : Option [Array [Byte ]] = {
133+ private def safeToBytes ( kryo : KryoInstantiator ) : Option [Array [Byte ]] = {
98134 try {
99- val bytes = kpool.toBytesWithClass(item)
100- // Make sure we can read without throwing
101- fromBytes(bytes)
135+ val kpool = KryoPool .withByteArrayOutputStream(1 , kryo)
136+ val bytes = kpool.toBytesWithClass(getOption)
102137 Some (bytes)
103138 }
104139 catch {
@@ -109,41 +144,80 @@ class Externalizer[T] extends Externalizable {
109144 None
110145 }
111146 }
112- private def fromBytes (b : Array [Byte ]): Option [T ] =
113- kpool.fromBytes(b).asInstanceOf [Option [T ]]
147+ private def fromBytes (b : Array [Byte ], kryo : KryoInstantiator ): Option [T ] =
148+ KryoPool .withByteArrayOutputStream(1 , kryo)
149+ .fromBytes(b)
150+ .asInstanceOf [Option [T ]]
114151
115- def readExternal (in : ObjectInput ) {
152+ override def readExternal (in : ObjectInput ) = maybeReadJavaKryo(in, kryo)
153+
154+ private def maybeReadJavaKryo (in : ObjectInput , kryo : KryoInstantiator ) {
116155 in.read match {
117156 case JAVA =>
118- item = in.readObject.asInstanceOf [Option [T ]]
157+ item = Right ( in.readObject.asInstanceOf [Option [T ]])
119158 case KRYO =>
120159 val sz = in.readInt
121160 val buf = new Array [Byte ](sz)
122161 in.readFully(buf)
123- item = fromBytes(buf)
162+ item = Right ( fromBytes(buf, kryo) )
124163 }
125164 }
126165
127166 protected def writeJava (out : ObjectOutput ): Boolean =
128167 javaWorks && {
129168 out.write(JAVA )
130- out.writeObject(item )
169+ out.writeObject(getOption )
131170 true
132171 }
133172
134- protected def writeKryo (out : ObjectOutput ): Boolean =
135- safeToBytes.map { bytes =>
173+ protected def writeKryo (out : ObjectOutput ): Boolean = writeKryo(out, kryo)
174+
175+ protected def writeKryo (out : ObjectOutput , kryo : KryoInstantiator ): Boolean =
176+ safeToBytes(kryo).map { bytes =>
136177 out.write(KRYO )
137178 out.writeInt(bytes.size)
138179 out.write(bytes)
139180 true
140181 }.getOrElse(false )
141182
142- def writeExternal (out : ObjectOutput ) {
143- writeJava(out) || writeKryo(out) || {
144- val inner = item. get
183+ private def maybeWriteJavaKryo (out : ObjectOutput , kryo : KryoInstantiator ) {
184+ writeJava(out) || writeKryo(out, kryo ) || {
185+ val inner = get
145186 sys.error(" Neither Java nor Kyro works for class: %s instance: %s\n export CHILL_EXTERNALIZER_DEBUG=true to see both stack traces"
146187 .format(inner.getClass, inner))
147188 }
148189 }
190+
191+ override def writeExternal (out : ObjectOutput ) = maybeWriteJavaKryo(out, kryo)
192+
193+ def write (kryo : Kryo , output : Output ): Unit = {
194+ val resolver = kryo.getReferenceResolver
195+ resolver.getWrittenId(item) match {
196+ case - 1 =>
197+ output.writeInt(- 1 )
198+ resolver.addWrittenObject(item)
199+ val oStream = new ObjectOutputStream (output)
200+ maybeWriteJavaKryo(oStream, () => kryo)
201+ oStream.flush
202+ case n =>
203+ output.writeInt(n)
204+ }
205+ }
206+
207+ def read (kryo : Kryo , input : Input ): Unit = {
208+ doesJavaWork.set(None )
209+ testing.set(false )
210+ val state = input.readInt()
211+ val resolver = kryo.getReferenceResolver
212+ state match {
213+ case - 1 =>
214+ val objId = resolver.nextReadId(this .getClass)
215+ resolver.setReadObject(objId, this )
216+ maybeReadJavaKryo(new ObjectInputStream (input), () => kryo)
217+ case n =>
218+ val z = resolver.getReadObject(this .getClass, n).asInstanceOf [Externalizer [T ]]
219+ if (! (z eq this )) item = Left (z)
220+ }
221+ }
222+
149223}
0 commit comments