Skip to content

Commit

Permalink
airframe-codec: Scala.js support with Pure Scala packer/unpacker (#789)
Browse files Browse the repository at this point in the history
* Add InfiniteByteArrayBuffer

* Rename some methods

* Add pure-scala unpacker

* Use ListMap to preserve MapValue element order

* Use pure-Scala packer/unpacker for Scala.js

* Run codec tests for Scala.js too

* Support java collection

* Fix Seq codec

* Reformat

* Do not use pure-Scala msgpack for JVM for safety
  • Loading branch information
xerial committed Nov 1, 2019
1 parent c34cef4 commit e565fea
Show file tree
Hide file tree
Showing 24 changed files with 502 additions and 55 deletions.
Expand Up @@ -12,7 +12,9 @@
* limitations under the License.
*/
package wvlet.airframe.codec
import wvlet.airframe.surface.{Parameter, Surface}
import wvlet.airframe.codec.ScalaStandardCodec.TupleCodec
import wvlet.airframe.surface.{GenericSurface, Surface}
import wvlet.log.LogSupport

/**
*
Expand All @@ -27,7 +29,30 @@ object Compat {
factory: MessageCodecFactory,
seenSet: Set[Surface]
): PartialFunction[Surface, MessageCodec[_]] = {
case other => throw new UnsupportedOperationException(s"${other}")
case g: GenericSurface
if g.rawType.getName.startsWith("scala.Tuple") && classOf[Product].isAssignableFrom(g.rawType) =>
TupleCodec(g.typeArgs.map(factory.ofSurface(_, seenSet)))
case g: GenericSurface if classOf[IndexedSeq[_]].isAssignableFrom(g.rawType) =>
val elementSurface = factory.ofSurface(g.typeArgs(0), seenSet)
new CollectionCodec.IndexedSeqCodec(g.typeArgs(0), elementSurface)
case g: GenericSurface if classOf[List[_]].isAssignableFrom(g.rawType) =>
val elementSurface = factory.ofSurface(g.typeArgs(0), seenSet)
new CollectionCodec.ListCodec(g.typeArgs(0), elementSurface)
case g: GenericSurface if classOf[java.util.List[_]].isAssignableFrom(g.rawType) =>
val elementSurface = factory.ofSurface(g.typeArgs(0), seenSet)
new CollectionCodec.JavaListCodec(elementSurface)
case g: GenericSurface if classOf[Seq[_]].isAssignableFrom(g.rawType) =>
val elementSurface = factory.ofSurface(g.typeArgs(0), seenSet)
new CollectionCodec.SeqCodec(g.typeArgs(0), elementSurface)
case g: GenericSurface if classOf[java.util.Map[_, _]].isAssignableFrom(g.rawType) =>
CollectionCodec.JavaMapCodec(
factory.ofSurface(g.typeArgs(0), seenSet),
factory.ofSurface(g.typeArgs(1), seenSet)
)
case g: GenericSurface if classOf[Map[_, _]].isAssignableFrom(g.rawType) =>
CollectionCodec.MapCodec(factory.ofSurface(g.typeArgs(0), seenSet), factory.ofSurface(g.typeArgs(1), seenSet))
// case other =>
// throw new UnsupportedOperationException(s"MessageCodec for ${other} is not found")
}
}
}
Expand Up @@ -12,8 +12,7 @@
* limitations under the License.
*/
package wvlet.airframe.codec
import wvlet.airframe.surface.reflect.RuntimeMethodParameter
import wvlet.airframe.surface.{Parameter, Surface, required}
import wvlet.airframe.surface.Surface

import scala.reflect.runtime.{universe => ru}

Expand Down
Expand Up @@ -23,6 +23,7 @@ import wvlet.airframe.surface.{EnumSurface, GenericSurface, Surface}
*/
object JVMCodecFactory extends CodecFinder {

// TODO: Share the code with Scala.js
def findCodec(
factory: MessageCodecFactory,
seenSet: Set[Surface] = Set.empty
Expand Down
Expand Up @@ -21,6 +21,8 @@ import wvlet.airspec.AirSpec
*
*/
class MessageCodecTest extends AirSpec {
scalaJsSupport

def `have surface`: Unit = {
val l = LongCodec.surface
debug(l)
Expand Down
Expand Up @@ -23,12 +23,13 @@ object ValueCodecTest {
case class RawMsgpackTest2(msgpack: MsgPack)
}

import ValueCodecTest._
import wvlet.airframe.codec.ValueCodecTest._

/**
*
*/
class ValueCodecTest extends CodecSpec {
scalaJsSupport

def `support MessagePack values`: Unit = {
roundtrip(ValueCodec, ValueFactory.newInteger(1), DataType.ANY)
Expand Down
Expand Up @@ -18,6 +18,7 @@ import java.util
import wvlet.airframe.json.JSONParseException
import wvlet.airframe.msgpack.spi.{MessagePack, Packer, Unpacker, ValueType}
import wvlet.airframe.surface.{Surface, Zero}
import wvlet.log.LogSupport

import scala.jdk.CollectionConverters._
import scala.collection.mutable
Expand Down Expand Up @@ -53,7 +54,8 @@ object CollectionCodec {
elementCodec.unpack(u, v)
if (v.isNull) {
// Add default value
b += Zero.zeroOf(surface).asInstanceOf[A]
val z = Zero.zeroOf(surface).asInstanceOf[A]
b += z
} else {
b += v.getLastValue.asInstanceOf[A]
}
Expand Down
Expand Up @@ -12,6 +12,7 @@
* limitations under the License.
*/
package wvlet.airframe.codec

import wvlet.airframe.msgpack.spi.MessagePack
import wvlet.airframe.surface.Surface
import wvlet.airspec.AirSpec
Expand Down
Expand Up @@ -22,13 +22,16 @@ import scala.jdk.CollectionConverters._
*
*/
class CollectionCodecTest extends CodecSpec {
scalaJsSupport

def `support Map type`: Unit = {
val v = Map("id" -> 1)
roundtrip(Surface.of[Map[String, Int]], v, DataType.ANY)
}

def `support Java Map type`: Unit = {
if (isScalaJS)
skip("Scala.js do not support Java Map")
val v = Map("id" -> 1).asJava
roundtrip(Surface.of[java.util.Map[String, Int]], v, DataType.ANY)
}
Expand All @@ -51,6 +54,8 @@ class CollectionCodecTest extends CodecSpec {
}

def `support JSON Map to java.util.Map`: Unit = {
if (isScalaJS)
skip("Scala.js doesn't support Java Map")
val codec = MessageCodec.of[java.util.Map[String, Int]]
val msgpack = MessagePack.newBufferPacker.packString("""{"leo":1, "yui":2}""").toByteArray
codec.unpackMsgPack(msgpack) shouldBe Some(Map("leo" -> 1, "yui" -> 2).asJava)
Expand Down
Expand Up @@ -19,6 +19,7 @@ import wvlet.airspec.AirSpec
*
*/
class InnerClassCodecTest extends AirSpec {
scalaJsSupport

case class A(id: Int, name: String)

Expand Down
Expand Up @@ -21,11 +21,15 @@ import wvlet.airspec.AirSpec
*
*/
class JSONCodecTest extends AirSpec {
scalaJsSupport

protected def check(json: String): Unit = {
val b = JSONCodec.toMsgPack(json)
JSONCodec.unpackMsgPack(b) match {
case Some(parsedJson) =>
JSON.parseAny(parsedJson) shouldBe JSON.parseAny(json)
val j1 = JSON.parseAny(parsedJson)
val j2 = JSON.parseAny(json)
j1 shouldBe j2
case None =>
fail(s"Failed to ser/de ${json}")
}
Expand All @@ -42,7 +46,7 @@ class JSONCodecTest extends AirSpec {
check("[true]")
check("[false]")
check("[null]")
check("""[1, 2, 3.0, "apple", true, false]""")
check("""[1,2,3.123,"apple",true,false]""")
check("{}")
check("[]")
}
Expand All @@ -52,7 +56,7 @@ class JSONCodecTest extends AirSpec {
check("null")
check("false")
check("1")
check("1.0e1")
check("1.23e1")
check("1.234")
}

Expand Down
Expand Up @@ -22,6 +22,8 @@ import wvlet.airframe.surface.required
*
*/
class ObjectCodecTest extends CodecSpec {
scalaJsSupport

val codec = MessageCodec.of[A1].asInstanceOf[ObjectCodec[A1]]

def `support reading map value`: Unit = {
Expand Down
Expand Up @@ -20,16 +20,19 @@ import org.scalacheck.util.Pretty
import wvlet.airframe.json.JSON.JSONString
import wvlet.airframe.msgpack.spi.MessagePack
import wvlet.airframe.msgpack.spi.Value.StringValue
import wvlet.airspec.spi.PropertyCheck
import wvlet.airframe.surface.{ArraySurface, GenericSurface, Surface}
import wvlet.airspec.spi.PropertyCheck

/**
*
*/
class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
import scala.jdk.CollectionConverters._
scalaJsSupport

import org.scalacheck._

import scala.jdk.CollectionConverters._

protected def roundTripTest[T](surface: Surface, dataType: DataType)(
implicit
a1: Arbitrary[T],
Expand Down Expand Up @@ -63,6 +66,7 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
// java.util.List[T] -> Array
roundtrip(javaListCodec, v.toSeq.asJava, DataType.ANY)
}

}

protected def roundTripTestWithStr[T](
Expand Down Expand Up @@ -130,8 +134,10 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
p.packNil // will be 0
p.packBigInteger(LARGE_VALUE) // will be 0

val codec = MessageCodec.of[Seq[Int]]
val seq = codec.unpackMsgPack(p.toByteArray)
val codec = MessageCodec.of[Seq[Int]]
val msgpack = p.toByteArray
debug(MessagePack.newUnpacker(msgpack).unpackValue)
val seq = codec.unpackMsgPack(msgpack)
seq shouldBe defined
seq.get shouldBe expected
}
Expand Down Expand Up @@ -301,7 +307,7 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
"13.2",
"false",
"true",
"10.0",
//"0.2",
"12345.01",
"",
LARGE_VALUE.toString,
Expand All @@ -316,7 +322,8 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
p.packString("13.2")
p.packBoolean(false)
p.packBoolean(true)
p.packFloat(10.0f)
// Scala.js uses double for float values
//p.packFloat(0.2f)
p.packDouble(12345.01)
p.packNil // will be 0
p.packBigInteger(LARGE_VALUE) // will be 0
Expand All @@ -340,7 +347,7 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
10,
100L,
10.0f,
12345.01,
//12345.01,
10.toByte,
12.toShort,
20.toChar,
Expand All @@ -362,7 +369,7 @@ class PrimitiveCodecTest extends CodecSpec with PropertyCheck {
10L,
100L,
10.0,
12345.01,
//12345.01,
10L,
12L,
20L,
Expand Down
Expand Up @@ -18,6 +18,8 @@ import wvlet.airframe.surface.Surface
*
*/
class ScalaStandardCodecTest extends CodecSpec {
scalaJsSupport

def `support Option[A]` : Unit = {
val v = Some("hello")
roundtrip(Surface.of[Option[String]], Some("hello"))
Expand Down
Expand Up @@ -14,6 +14,9 @@
package wvlet.airframe.msgpack.spi
import java.io.{InputStream, OutputStream}

import wvlet.airframe.msgpack.impl.{PureScalaBufferPacker, PureScalaBufferUnpacker}
import wvlet.airframe.msgpack.io.ByteArrayBuffer

/**
* Compatibility layer for Scala.js
*/
Expand All @@ -25,9 +28,15 @@ object Compat {
def floatToIntBits(v: Float): Int = java.lang.Float.floatToIntBits(v)
def doubleToLongBits(v: Double): Long = java.lang.Double.doubleToLongBits(v)

def newBufferPacker: BufferPacker = ???
def newPacker(out: OutputStream): Packer = ???
def newUnpacker(in: InputStream): Unpacker = ???
def newUnpacker(msgpack: Array[Byte]): Unpacker = ???
def newUnpacker(msgpack: Array[Byte], offset: Int, len: Int): Unpacker = ???
def newBufferPacker: BufferPacker = {
new PureScalaBufferPacker
}
def newPacker(out: OutputStream): Packer = ???
def newUnpacker(in: InputStream): Unpacker = ???
def newUnpacker(msgpack: Array[Byte]): Unpacker = {
newUnpacker(msgpack, 0, msgpack.length)
}
def newUnpacker(msgpack: Array[Byte], offset: Int, len: Int): Unpacker = {
new PureScalaBufferUnpacker(ByteArrayBuffer.fromArray(msgpack, offset, len))
}
}
Expand Up @@ -31,6 +31,8 @@ object Compat {
}

def newPacker(out: OutputStream): Packer = {
// TODO: Use pure-scala packer
// new PureScalaBufferPacker
new PackerImpl(mj.MessagePack.newDefaultPacker(out))
}

Expand All @@ -43,6 +45,8 @@ object Compat {
}

def newUnpacker(msgpack: Array[Byte], offset: Int, len: Int): Unpacker = {
// TODO use pure-scala unpacker
//new PureScalaBufferUnpacker(ByteArrayBuffer.fromArray(msgpack, offset, len))
new UnpackerImpl(mj.MessagePack.newDefaultUnpacker(msgpack, offset, len))
}
}
Expand Up @@ -71,6 +71,16 @@ class JSONToMessagePackConverterTest extends AirSpec {
msgpackToJson(msgpack) shouldBe JSON.parse(json)
}

def `convert twitter-single.json to MsgPack`: Unit = {
val json = IOUtil.readAsString("airframe-benchmark/src/main/resources/twitter-single.json")
val jsonValue = JSON.parse(json)
val msgpack = MessagePack.fromJSON(json)
val result = msgpackToJson(msgpack)
info(result)
info(jsonValue.toJSON)
deepEqual(result, jsonValue)
}

def `convert twitter.json to MsgPack`: Unit = {
val json = IOUtil.readAsString("airframe-json/src/test/resources/twitter.json")
val jsonValue = JSON.parse(json)
Expand Down

0 comments on commit e565fea

Please sign in to comment.