//############################################################################
// Serialization
//############################################################################

object Serialize {
  @throws(classOf[java.io.IOException])
  def write[A](o: A): Array[Byte] = {
    val ba = new java.io.ByteArrayOutputStream(512)
    val out = new java.io.ObjectOutputStream(ba)
    out.writeObject(o)
    out.close()
    ba.toByteArray()
  }
  @throws(classOf[java.io.IOException])
  @throws(classOf[ClassNotFoundException])
  def read[A](buffer: Array[Byte]): A = {
    val in =
      new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(buffer))
    in.readObject().asInstanceOf[A]
  }
  def check[A, B](x: A, y: B) {
    println("x = " + x)
    println("y = " + y)
    println("x equals y: " + (x equals y) + ", y equals x: " + (y equals x))
    assert((x equals y) && (y equals x))
    println()
  }
}
import Serialize._

//############################################################################
// Test classes in package "scala"

object Test1_scala {

  private def arrayToString[A](arr: Array[A]): String =
    arr.mkString("Array[",",","]")

  private def arrayEquals[A, B](a1: Array[A], a2: Array[B]): Boolean =
    (a1.length == a2.length) &&
    (Iterator.range(0, a1.length) forall { i => a1(i) == a2(i) })

  object WeekDay extends Enumeration {
    type WeekDay = Value
    val Monday, Tuesday, Wednesday, Thusday, Friday, Saturday, Sunday = Value
  }
  import WeekDay._, BigDecimal._, RoundingMode._

  // in alphabetic order
  try {
    // Array
    val a1 = Array(1, 2, 3)
    val _a1: Array[Int] = read(write(a1))
    println("a1 = " + arrayToString(a1))
    println("_a1 = " + arrayToString(_a1))
    println("arrayEquals(a1, _a1): " + arrayEquals(a1, _a1))
    println()

    // Either
    val e1 = Left(1)
    val _e1: Either[Int, String] = read(write(e1))
    println("e1 = " + e1)
    println("_e1 = " + _e1)
    println("e1 eq _e1: " + (e1 eq _e1) + ", _e1 eq e1: " + (_e1 eq e1))
    println("e1 equals _e1: " + (e1 equals _e1) + ", _e1 equals e1: " + (_e1 equals e1))
    println()

    // Enumeration
    val x7 = BigDecimal.RoundingMode
    val y7: RoundingMode.type = read(write(x7))
    println("x7 = " + x7)
    println("y7 = " + y7)
    println("x7 eq y7: " + (x7 eq y7) + ", y7 eq x7: " + (y7 eq x7))
    println("x7 equals y7: " + (x7 equals y7) + ", y7 equals x7: " + (y7 equals x7))
    println()

    val x8 = WeekDay
    val y8: WeekDay.type = read(write(x8))
    println("x8 = " + x8)
    println("y8 = " + y8)
    println("x8 eq y8: " + (x8 eq y8) + ", y8 eq x8: " + (y8 eq x8))
    println("x8 equals y8: " + (x8 equals y8) + ", y8 equals x8: " + (y8 equals x8))
    println()

    val x9 = UP
    val y9: RoundingMode = read(write(x9))
    println("x9 = " + x9)
    println("y9 = " + y9)
    println("x9 eq y9: " + (x9 eq y9) + ", y9 eq x9: " + (y9 eq x9))
    println("x9 equals y9: " + (x9 equals y9) + ", y9 equals x9: " + (y9 equals x9))
    println()

    val x10 = Monday
    val y10: WeekDay = read(write(x10))
    println("x10 = " + x10)
    println("y10 = " + y10)
    println("x10 eq y10: " + (x10 eq y10) + ", y10 eq x10: " + (y10 eq x10))
    println("x10 equals y10: " + (x10 equals y10) + ", y10 equals x10: " + (y10 equals x10))
    println()

    println("x9 eq x10: " + (x9 eq x10) + ", x10 eq x9: " + (x10 eq x9))
    println("x9 equals x10: " + (x9 equals x10) + ", x10 equals x9: " + (x10 equals x9))
    println("x9 eq y10: " + (x9 eq y10) + ", y10 eq x9: " + (y10 eq x9))
    println("x9 equals y10: " + (x9 equals y10) + ", y10 equals x9: " + (y10 equals x9))
    println()

    // Function
    val f1 = { x: Int => 2 * x }
    val _f1: Function[Int, Int] = read(write(f1))
    println("f1 = <na>")
    println("_f1 = <na>")
    println("f1(2): " + f1(2) + ", _f1(2): " + _f1(2))
    println()

    // List
    val xs0 = List(1, 2, 3)
    val _xs0: List[Int] = read(write(xs0))
    println("xs0 = " + xs0)
    println("_xs0 = " + _xs0)
    println("xs0 eq _xs0: " + (xs0 eq _xs0) + ", _xs0 eq xs0: " + (_xs0 eq xs0))
    println("xs0 equals _xs0: " + (xs0 equals _xs0) + ", _xs0 equals xs0: " + (_xs0 equals xs0))
    println()

    val xs1 = Nil
    val _xs1: List[Nothing] = read(write(xs1))
    println("xs1 = " + xs1)
    println("_xs1 = " + _xs1)
    println("xs1 eq _xs1: " + (xs1 eq _xs1) + ", _xs1 eq xs1: " + (_xs1 eq xs1))
    println()

    // Option
    val o1 = None
    val _o1: Option[Nothing] = read(write(o1))
    println("o1 = " + o1)
    println("_o1 = " + _o1)
    println("o1 eq _o1: " + (o1 eq _o1) + ", _o1 eq o1: " + (_o1 eq o1))
    println()

    val o2 = Some(1)
    val _o2: Option[Int] = read(write(o2))
    println("o2 = " + o2)
    println("_o2 = " + _o2)
    println("o2 eq _o2: " + (o2 eq _o2) + ", _o2 eq o2: " + (_o2 eq o2))
    println("o2 equals _o2: " + (o2 equals _o2) + ", _o2 equals o2: " + (_o2 equals o2))
    println()
/*
    // Responder
    val r1 = Responder.constant("xyz")
    val _r1: Responder[String] = read(write(r1))
    check(r1, _r1)
*/
    // Symbol
    val s1 = 'hello
    val _s1: Symbol = read(write(s1))
    println("s1 = " + s1)
    println("_s1 = " + _s1)
    println("s1 eq _s1: " + (s1 eq _s1) + ", _s1 eq s1: " + (_s1 eq s1))
    println("s1 equals _s1: " + (s1 equals _s1) + ", _s1 equals s1: " + (_s1 equals s1))
    println()

    // Tuple
    val t1 = ("BannerLimit", 12345)
    val _t1: (String, Int) = read(write(t1))
    println("t1 = " + t1)
    println("_t1 = " + _t1)
    println("t1 eq _t1: " + (t1 eq _t1) + ", _t1 eq t1: " + (_t1 eq t1))
    println("t1 equals _t1: " + (t1 equals _t1) + ", _t1 equals t1: " + (_t1 equals t1))
    println()
  }
  catch {
    case e: Exception =>
      println("Error in Test1_scala: " + e)
      throw e
  }
}

//############################################################################
// Test classes in package "scala.collection.immutable"

object Test2_immutable {
  import scala.collection.immutable.{
    BitSet, HashMap, HashSet, ListMap, ListSet, Queue, Range, SortedMap,
    SortedSet, Stack, Stream, TreeMap, TreeSet, Vector}

  // in alphabetic order
  try {
    // BitSet
    val bs1 = BitSet.empty + 1 + 2
    val _bs1: BitSet = read(write(bs1))
    check(bs1, _bs1)

    val bs2 = {
      val bs = new collection.mutable.BitSet()
      bs += 2; bs += 3
      bs.toImmutable
    }
    val _bs2: BitSet = read(write(bs2))
    check(bs2, _bs2)

    // HashMap
    val hm1 = new HashMap[Int, String] + (1 -> "A", 2 -> "B", 3 -> "C")
    val _hm1: HashMap[Int, String] = read(write(hm1))
    check(hm1, _hm1)

    // HashSet
    val hs1 = new HashSet[Int] + 1 + 2
    val _hs1: HashSet[Int] = read(write(hs1))
    check(hs1, _hs1)

    // List
    val xs1 = List(("buffers", 20), ("layers", 2), ("title", 3))
    val _xs1: List[(String, Int)] = read(write(xs1))
    check(xs1, _xs1)

    // ListMap
    val lm1 = new ListMap[String, Int] + ("buffers" -> 20, "layers" -> 2, "title" -> 3)
    val _lm1: ListMap[String, Int] = read(write(lm1))
    check(lm1, _lm1)

    // ListSet
    val ls1 = new ListSet[Int] + 3 + 5
    val _ls1: ListSet[Int] = read(write(ls1))
    check(ls1, _ls1)

    // Queue
    val q1 = Queue("a", "b", "c")
    val _q1: Queue[String] = read(write(q1))
    check(q1, _q1)

    // Range
    val r1 = 0 until 10
    val _r1: Range = read(write(r1))
    check(r1, _r1)

    val r2 = Range.Long(0L, 10L, 1)
    val _r2: r2.type = read(write(r2))
    check(r2, _r2)

    // SortedMap
    val sm1 = SortedMap.empty[Int, String] + (2 -> "B", 3 -> "C", 1 -> "A")
    val _sm1: SortedMap[Int, String] = read(write(sm1))
    check(sm1, _sm1)

    // SortedSet
    val ss1 = SortedSet.empty[Int] + 2 + 3 + 1
    val _ss1: SortedSet[Int] = read(write(ss1))
    check(ss1, _ss1)

    // Stack
    val s1 = new Stack().push("a", "b", "c")
    val _s1: Stack[String] = read(write(s1))
    check(s1, _s1)

    // Stream
    val st1 = Stream.range(0, 10)
    val _st1: Stream[Int] = read(write(st1))
    check(st1, _st1)

    // TreeMap
    val tm1 = new TreeMap[Int, String] + (42 -> "FortyTwo")
    val _tm1: TreeMap[Int, String] = read(write(tm1))
    check(tm1, _tm1)

    // TreeSet
    val ts1 = new TreeSet[Int]() + 2 + 0
    val _ts1: TreeSet[Int] = read(write(ts1))
    check(ts1, _ts1)

    // Vector
    val v1 = Vector('a, 'b, 'c)
    val _v1: Vector[Symbol] = read(write(v1))
    check(v1, _v1)
  }
  catch {
    case e: Exception =>
      println("Error in Test2_immutable: " + e)
      throw e
  }
}

//############################################################################
// Test classes in package "scala.collection.mutable"

object Test3_mutable {
  import scala.reflect.ClassTag
  import scala.collection.mutable.{
    ArrayBuffer, ArrayBuilder, ArraySeq, ArrayStack, BitSet, DoubleLinkedList,
    HashMap, HashSet, History, LinkedHashMap, LinkedHashSet, LinkedList, ListBuffer,
    Publisher, Queue, Stack, StringBuilder, WrappedArray, TreeSet}
  import scala.collection.concurrent.TrieMap

  // in alphabetic order
  try {
    // ArrayBuffer
    val ab1 = new ArrayBuffer[String]
    ab1 ++= List("one", "two")
    val _ab1: ArrayBuffer[String] = read(write(ab1))
    check(ab1, _ab1)

    // ArrayBuilder
    val abu1 = ArrayBuilder.make[Long]
    val _abu1: ArrayBuilder[ClassTag[Long]] = read(write(abu1))
    check(abu1, _abu1)

    val abu2 = ArrayBuilder.make[Float]
    val _abu2: ArrayBuilder[ClassTag[Float]] = read(write(abu2))
    check(abu2, _abu2)

    // ArraySeq
    val aq1 = ArraySeq(1, 2, 3)
    val _aq1: ArraySeq[Int] = read(write(aq1))
    check(aq1, _aq1)

    // ArrayStack
    val as1 = new ArrayStack[Int]
    as1 ++= List(20, 2, 3).iterator
    val _as1: ArrayStack[Int] = read(write(as1))
    check(as1, _as1)

    // BitSet
    val bs1 = new BitSet()
    bs1 += 0
    bs1 += 8
    bs1 += 9
    val _bs1: BitSet = read(write(bs1))
    check(bs1, _bs1)
/*
    // DoubleLinkedList
    val dl1 = new DoubleLinkedList[Int](2, null)
    dl1.append(new DoubleLinkedList(3, null))
    val _dl1: DoubleLinkedList[Int] = read(write(dl1))
    check(dl1, _dl1)
*/
    // HashMap
    val hm1 = new HashMap[String, Int]
    hm1 ++= List(("A", 1), ("B", 2), ("C", 3)).iterator
    val _hm1: HashMap[String, Int] = read(write(hm1))
    check(hm1, _hm1)

    // HashSet
    val hs1 = new HashSet[String]
    hs1 ++= List("layers", "buffers", "title").iterator
    val _hs1: HashSet[String] = read(write(hs1))
    check(hs1, _hs1)

    val h1 = new History[String, Int]
    val _h1: History[String, Int] = read(write(h1))
    check(h1, _h1)

    // LinkedHashMap
    { val lhm1 = new LinkedHashMap[String, Int]
      val list = List(("Linked", 1), ("Hash", 2), ("Map", 3))
      lhm1 ++= list.iterator
      val _lhm1: LinkedHashMap[String, Int] = read(write(lhm1))
      check(lhm1, _lhm1)
      check(lhm1.toSeq, _lhm1.toSeq) // check elements order
      check(lhm1.toSeq, list) // check elements order
    }

    // LinkedHashSet
    { val lhs1 = new LinkedHashSet[String]
      val list = List("layers", "buffers", "title")
      lhs1 ++= list.iterator
      val _lhs1: LinkedHashSet[String] = read(write(lhs1))
      check(lhs1, _lhs1)
      check(lhs1.toSeq, _lhs1.toSeq) // check elements order
      check(lhs1.toSeq, list) // check elements order
    }
/*
    // LinkedList
    val ll1 = new LinkedList[Int](2, null)
    ll1.append(new LinkedList(3, null))
    val _ll1: LinkedList[Int] = read(write(ll1))
    check(ll1, _ll1)
*/
    // ListBuffer
    val lb1 = new ListBuffer[String]
    lb1 ++= List("white", "black")
    val _lb1: ListBuffer[String] = read(write(lb1))
    check(lb1, _lb1)

    // Queue
    val q1 = new Queue[Int]
    q1 ++= List(20, 2, 3).iterator
    val _q1: Queue[Int] = read(write(q1))
    check(q1, _q1)

    // Stack
    val s1 = new Stack[Int]
    s1 pushAll q1
    val _s1: Stack[Int] = read(write(s1))
    check(s1, _s1)

    // StringBuilder
    val sb1 = new StringBuilder
    sb1 append "abc"
    val _sb1: StringBuilder = read(write(sb1))
    check(sb1, _sb1)

    // WrappedArray
    val wa1 = WrappedArray.make(Array(1, 2, 3))
    val _wa1: WrappedArray[Int] = read(write(wa1))
    check(wa1, _wa1)

    // TreeSet
    val ts1 = TreeSet[Int]() ++= Array(1, 2, 3)
    val _ts1: TreeSet[Int] = read(write(ts1))
    check(ts1, _ts1)

    // concurrent.TrieMap
    val ct1 = TrieMap[Int, String]() ++= Array(1 -> "one", 2 -> "two", 3 -> "three")
    val _ct1: TrieMap[Int, String] = read(write(ct1))
    check(ct1, _ct1)
  }
  catch {
    case e: Exception =>
      println("Error in Test3_mutable: " + e)
      throw e
  }
}

//############################################################################
// Test user-defined classes WITHOUT nesting

class Person(_name: String) extends Serializable {
  private var name = _name
  override def toString() = name
  override def equals(that: Any): Boolean =
    that.isInstanceOf[Person] &&
    (name == that.asInstanceOf[Person].name)
}

class Employee(_name: String) extends Serializable {
  private var name = _name
  override def toString() = name
}

object bob extends Employee("Bob")

object Test5 {
  val x1 = new Person("Tim")
  val x2 = bob

  try {
    val y1: Person   = read(write(x1))
    val y2: Employee = read(write(x2))

    check(x1, y1)
    check(x2, y2)
  }
  catch {
    case e: Exception =>
      println("Error in Test5: " + e)
  }
}

//############################################################################
// Test user-defined classes WITH nesting

object Test6 {
  object bill extends Employee("Bill") {
    val x = paul
  }
  object paul extends Person("Paul") {
    val x = 4  //  bill; => StackOverflowException !!!
  }
  val x1 = new Person("John")
  val x2 = bill
  val x3 = paul

  try {
    val y1: Person   = read(write(x1))
    val y2: Employee = read(write(x2))
    val y3: Person   = read(write(x3))

    check(x1, y1)
    check(x2, y2)
    check(x3, y3)
  }
  catch {
    case e: Exception =>
      println("Error in Test6: " + e)
  }
}

//############################################################################
// Nested objects cannot get readresolve automatically because after deserialization
// they would be null (they are treated as lazy vals)
class Outer extends Serializable {
    object Inner extends Serializable
}

object Test7 {
  val x = new Outer
  x.Inner // initialize
  val y:Outer = read(write(x))
  if (y.Inner == null)
    println("Inner object is null")
}

// Verify that transient lazy vals don't get serialized
class WithTransient extends Serializable {
  @transient lazy val a1 = 1
  @transient private lazy val a2 = 2
  @transient object B extends Serializable
  @transient private object C extends Serializable

  def test = {
    println(a1)
    println(a2)
    if (B == null || C == null)
     println("Transient nested object failed to serialize properly")
  }
}

object Test8 {
    val x = new WithTransient
    x.test
  try {
    val y:WithTransient = read(write(x))
    y.test
  }
  catch {
  case e: Exception =>
    println("Error in Test8: " + e)
  }
}

//############################################################################
// Test code

object Test {
  def main(args: Array[String]): Unit = {
    Test1_scala
    Test2_immutable
    Test3_mutable
    Test5
    Test6
    Test7
    Test8
  }
}
