Skip to content
This repository was archived by the owner on Feb 20, 2019. It is now read-only.

A path forward for pickling Java objects #296

Open
@eed3si9n

Description

@eed3si9n

Related: #60, #263, #33, #211, #295, #301

This is a general issue for discussing pickling Java objects. Hopefully we can reach some positive path forward.

Joda-Time isn't fixed

First, I would like to dispel the notion that Pickling 0.10.0 works with Joda-Time. It really doesn't.
Here's a demonstration of #33 and #60 combined:

scala> import scala.pickling._, Defaults._, json._
import scala.pickling._
import Defaults._
import json._

scala> import org.joda.time._
import org.joda.time._

scala> val dt = DateTime.now.plusDays(3)
dt: org.joda.time.DateTime = 2015-02-15T23:12:36.915-05:00

scala> dt.pickle
res0: scala.pickling.json.pickleFormat.PickleType =
JSONPickle({
  "$type": "org.joda.time.DateTime",
  "Millis": "1424059956915",
  "Chronology": {
    "$type": "org.joda.time.chrono.ISOChronology"
  }
})

scala> res0.unpickle[DateTime]
res1: org.joda.time.DateTime = 2015-02-15T23:12:36.915-05:00

scala> val ld = LocalDate.now.plusDays(3)
ld: org.joda.time.LocalDate = 2015-02-15

scala> ld.pickle
res2: scala.pickling.json.pickleFormat.PickleType =
JSONPickle({
  "$type": "org.joda.time.LocalDate"
})

scala> res2.unpickle[LocalDate]
res3: org.joda.time.LocalDate = 2015-02-12

Why does it "work" for DateTime, but not for LocalDate? What was added in #211 is a matching on method name that starts with "set" and "get". DateTime appears to roundtrip because it happens to have to have setters setChronology and setMillis. LocalDate doesn't have any methods that starts with "set" so Pickling 0.10.0 happily generates a pickler that pickles an empty object.
Note "set" and "get" are not the only pair allowed in JavaBeans Specification.

Guessing is dangerous

#263 illustrates that java.lang.Byte can roundtrip any number to 0.

scala> val r1: java.lang.Byte = 10.toByte
r1: Byte = 10

scala> val r2 = r1.pickle.unpickle[java.lang.Byte]
r2: Byte = 0

The fact that it's a boxed primitives is irrelevant. Due to the current logic of only picking up "set" and "get" pairs, the state of java.lang.Byte is not the exception, it's the rule. Note the problem with java.lang.Byte is not the fact that it's not handled, but the fact that it did not fail to compile. We should stop guessing, and fail when we don't have the complete information.

(More reports from the field)

scala> val uri = new java.net.URI("urn:isbn:096139210x")
uri: java.net.URI = urn:isbn:096139210x

scala> uri.pickle.unpickle[java.net.URI]
res5: java.net.URI = null

Java reflection

Are we doomed about Java? If we give up the speed and fall back to using reflection, we can still achieve correctness. I think that's a better approach.

scala> val r1: java.lang.Byte = 10.toByte
r1: Byte = 10

scala> r1.getClass.getDeclaredFields.toList foreach println
public static final byte java.lang.Byte.MIN_VALUE
public static final byte java.lang.Byte.MAX_VALUE
public static final java.lang.Class java.lang.Byte.TYPE
private final byte java.lang.Byte.value
public static final int java.lang.Byte.SIZE
private static final long java.lang.Byte.serialVersionUID

scala> {
     |   val f = r1.getClass.getDeclaredField("value")
     |   f.setAccessible(true)
     |   f.getByte(r1)
     | }
res1: Byte = 10

BeanInfo

There's BeanInfo, but I don't think we can discover setters reliably using it.

scala> import org.joda.time._
import org.joda.time._

scala> import java.beans.Introspector
import java.beans.Introspector

scala> val info = Introspector.getBeanInfo(classOf[DateTime])
info: java.beans.BeanInfo = java.beans.GenericBeanInfo@b508085

scala> info.getPropertyDescriptors.toList 
res2: List[java.beans.PropertyDescriptor] = List(java.beans.PropertyDescriptor[name=afterNow; propertyType=boolean; readMethod=public boolean org.joda.time.base.AbstractInstant.isAfterNow()]....

scala> info.getPropertyDescriptors.toList collect { case p if p.getWriteMethod != null => p }
res3: List[java.beans.PropertyDescriptor] = List()

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions