Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting rid of a circular dependency between bijection-macros and chill #228

Merged
merged 2 commits into from
Sep 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.scalatest.prop.PropertyChecks

import com.twitter.bijection._
import com.twitter.bijection.macros._
import com.twitter.chill.Externalizer

trait MacroPropTests extends PropSpec with PropertyChecks with Matchers with MacroTestHelper {
import MacroImplicits._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.scalatest.{ Matchers, WordSpec }

import com.twitter.bijection._
import com.twitter.bijection.macros._
import com.twitter.chill.Externalizer

import scala.util.{ Failure, Success }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package com.twitter.bijection.macros

import org.scalatest.Matchers
import com.twitter.chill.Externalizer

import _root_.java.io.{
ByteArrayOutputStream,
ByteArrayInputStream,
Externalizable,
ObjectInput,
ObjectOutput,
ObjectInputStream,
ObjectOutputStream
}

import _root_.java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }

object MacroCaseClasses extends java.io.Serializable {
type Atup = (Int, String)
Expand All @@ -20,6 +31,98 @@ object MacroCaseClasses extends java.io.Serializable {
class SampleClassD // Non-case class
}

object Externalizer {
def apply[T](t: T): Externalizer[T] = {
val x = new Externalizer[T]
x.set(t)
x
}
}

/**
* This is a simplified version of com.twitter.chill.Externalizer
* which only does Java serialization
*/
class Externalizer[T] extends Externalizable {
// Either points to a result or a delegate Externalizer to fufil that result.
private var item: Either[Externalizer[T], Option[T]] = Right(None)
import Externalizer._

@transient private val doesJavaWork = new AtomicReference[Option[Boolean]](None)
@transient private val testing = new AtomicBoolean(false)

// No vals or var's below this line!

def getOption: Option[T] = item match {
case Left(e) => e.getOption
case Right(i) => i
}

def get: T = getOption.get // This should never be None when get is called

/**
* Unfortunately, Java serialization requires mutable objects if
* you are going to control how the serialization is done.
* Use the companion object to creat new instances of this
*/
def set(it: T): Unit = {
item match {
case Left(e) => e.set(it)
case Right(x) =>
assert(x.isEmpty, "Tried to call .set on an already constructed Externalizer")
item = Right(Some(it))
}
}

// 1 here is 1 thread, since we will likely only serialize once
// this should not be a val because we don't want to capture a reference

def javaWorks: Boolean =
doesJavaWork.get match {
case Some(v) => v
case None => probeJavaWorks
}

/**
* Try to round-trip and see if it works without error
*/
private def probeJavaWorks: Boolean = {
if (!testing.compareAndSet(false, true)) return true
try {
val baos = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(baos)
oos.writeObject(getOption)
val bytes = baos.toByteArray
val testInput = new ByteArrayInputStream(bytes)
val ois = new ObjectInputStream(testInput)
ois.readObject // this may throw
doesJavaWork.set(Some(true))
true
} catch {
case t: Throwable =>
t.printStackTrace
doesJavaWork.set(Some(false))
false
} finally {
testing.set(false)
}
}

override def readExternal(in: ObjectInput) = readJava(in)

private def readJava(in: ObjectInput) {
item = Right(in.readObject.asInstanceOf[Option[T]])
}

protected def writeJava(out: ObjectOutput): Boolean =
javaWorks && {
out.writeObject(getOption)
true
}

override def writeExternal(out: ObjectOutput) = writeJava(out)
}

trait MacroTestHelper extends Matchers {
def canExternalize(t: AnyRef) { Externalizer(t).javaWorks shouldBe true }
}
3 changes: 1 addition & 2 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ object BijectionBuild extends Build {
libraryDependencies <++= (scalaVersion) { scalaVersion => Seq(
"org.scala-lang" % "scala-library" % scalaVersion,
"org.scala-lang" % "scala-reflect" % scalaVersion,
"org.scalatest" %% "scalatest" % "2.2.2" % "test",
"com.twitter" %% "chill" % "0.5.0" % "test"
"org.scalatest" %% "scalatest" % "2.2.2" % "test"
) ++ (if (scalaVersion.startsWith("2.10")) Seq("org.scalamacros" %% "quasiquotes" % "2.0.1") else Seq())
},
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full)
Expand Down