├── .github └── workflows │ └── scala.yml ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build.sbt ├── jmh └── src │ └── main │ ├── resources │ └── metadata.json │ └── scala │ └── org.velvia │ ├── BasicPerfBenchmark.scala │ ├── Json4sBenchmark.scala │ └── RojomaJsonBenchmark.scala ├── project ├── build.properties └── plugins.sbt ├── src └── org.velvia │ ├── MsgPack.scala │ ├── MsgPackUtils.scala │ ├── msgpack │ ├── CaseClassCodecs.scala │ ├── Codecs.scala │ ├── CollectionCodecs.scala │ ├── ExtraCodecs.scala │ ├── FastByteMap.scala │ ├── Format.scala │ ├── Json4sCodecs.scala │ ├── PlayJsonCodecs.scala │ ├── RojomaJsonCodecs.scala │ ├── TransformCodecs.scala │ └── TupleCodecs.scala │ └── package.scala ├── test └── org.velvia │ ├── MsgPackSpec.scala │ ├── MsgPackTypeClassSpec.scala │ └── MsgPackUtilsSpec.scala └── version.sbt /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v2 18 | with: 19 | java-version: '11' 20 | distribution: 'adopt' 21 | - name: Run tests 22 | run: sbt test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | 12 | # Scala-IDE specific 13 | .scala_dependencies 14 | .idea 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | env: 3 | global: 4 | # _JAVA_OPTIONS="-Xmx1500m -XX:MaxPermSize=512m -Dakka.test.timefactor=3" 5 | scala: 6 | - 2.11.7 7 | - 2.12.0 8 | jdk: 9 | - oraclejdk8 10 | 11 | before_script: 12 | - sudo chmod +x /usr/local/bin/sbt 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2012-2014 Evan Chan 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | msgpack4s 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/velvia/msgpack4s.svg?branch=master)](https://travis-ci.org/velvia/msgpack4s) 5 | 6 | A super-simple MessagePack serialization library for Scala. 7 | 8 | * Simple, type-safe API 9 | * Extensible via `Codec` type classes 10 | * Limited support for case classes and tuples 11 | * Designed and tested for long binary streaming applications 12 | * Built-in support for [Json4s](http://github.com/json4s/json4s), Play-JSON, and [rojoma-json](http://github.com/rjmac/rojoma-json) ASTs... easily supercharge your JSON Scala apps! 13 | - 10x speedup over Json4s for double-heavy applications such as GeoJSON 14 | - Over 2x speedup for regular string-heavy JSON documents 15 | * Directly unpacks maps, sequences, and any non-cyclic nested sequences/maps to Scala immutable collections 16 | * Can unpack `Map[Any, Any]` or `Map[String, Any]` without extra code, unlike msgpack-scala 17 | * No extra dependencies. No need to build a separate Java library. 18 | * significantly (3x as of 3/29/13) faster than msgpack-scala for unpacking Maps 19 | 20 | For the exact semantics of packing and unpacking, see the ScalaDoc. 21 | 22 | Using this library 23 | ================== 24 | 25 | Include this in `build.sbt`: 26 | 27 | ```scala 28 | resolvers += "velvia maven" at "http://dl.bintray.com/velvia/maven" 29 | 30 | libraryDependencies += "org.velvia" %% "msgpack4s" % "0.6.0" 31 | ``` 32 | 33 | The jars are published to JCenter as well. If you are using newer versions of SBT that resolve JCenter, you should not need to add the resolvers. 34 | 35 | Currently cross-compiled for Scala 2.11 and 2.12. 36 | 37 | You will probably want to use the type-safe API that uses TypeClasses: 38 | 39 | ```scala 40 | import org.velvia.msgpack._ 41 | import org.velvia.msgpack.SimpleCodecs._ 42 | 43 | val byteArray = pack(123) 44 | val num = unpack[Int](byteArray) 45 | ``` 46 | 47 | Serializing maps and sequences takes a bit more work, to instantiate a codec that has the specific types. The reward is speed when your collections have specific well-known types -- if you reuse the codecs. You can also use the older non-type-safe Any APIs (ex. `MsgPack.pack(...)`), which use `AnyCodecs.DefaultAnyCodec`, but these are slower because they assume both Maps and Seqs have Anys. 48 | 49 | ```scala 50 | import org.velvia.msgpack.CollectionCodecs._ 51 | 52 | val intSeqCodec = new SeqCodec[Int] 53 | 54 | val seq1 = Seq(1, 2, 3, 4, 5) 55 | unpack(pack(seq1)(intSeqCodec))(intSeqCodec) should equal (seq1) 56 | ``` 57 | 58 | Serializing Json4s and rojoma-json ASTs is easy. See the example in `MsgPackTypeClassSpec` -- all you need to do is import from the right codecs. 59 | 60 | There are also the older APIs that work with Any, but are not type safe. They also are not extensible to custom objects the way the type-safe APIs are. 61 | 62 | ```scala 63 | import org.velvia.MsgPack 64 | MsgPack.unpack(MsgPack.pack(Map("key" -> 3))) 65 | ``` 66 | 67 | Streaming mode 68 | ============== 69 | 70 | msgpack4s is under the covers designed to work with streams - in fact it works great for very long binary streams and has been tested with that in mind. Even the byte array APIs just wrap the streaming APIs with a `ByteArrayOutputStream`. Here is how to use it in streaming mode, including ensuring that the streams get closed properly at the end or in case of failure: 71 | 72 | ```scala 73 | import com.rojoma.simplearm.util._ 74 | import java.io.DataOutputStream 75 | 76 | for { 77 | os <- managed(resp.getOutputStream) 78 | dos <- managed(new DataOutputStream(os)) 79 | data <- listOfObjects 80 | } { 81 | msgpack.pack(data, dos) 82 | } 83 | ``` 84 | 85 | Convenience Functions 86 | ===================== 87 | 88 | For the older Any-based API, `MsgPackUtils` has convenience functions so you can pull out the right types from `unpack` without needing 89 | verbose `isInstanceOf[..]`. They are especially useful when working with numbers. For example: 90 | 91 | ```scala 92 | import org.velvia.MsgPackUtils._ 93 | val map = unpackMap(bytes) 94 | println("My number = " + map.asInt("number") + 99) 95 | ``` 96 | 97 | There are also functions `getInt` and `getLong` to conveniently get an Int or Long out, because MessagePack will pack them as [U]INT8/16/32/64's. 98 | 99 | Compatibility Mode 100 | ================== 101 | The MessagePack format was upgraded to differentiate STRings vs RAWs. So now 102 | one no longer has to pass in an option to decide how to unpack strings vs raw bytes. 103 | OTOH the unpack interface has been changed to provide a compatibility mode: 104 | setting to true allows to parse STR formats as raw bytes, so that MessagePack messages 105 | sent using older encoders can be parsed as raw bytes if needed. 106 | 107 | To unpack older MessagePack messages as raw bytes instead of strings: 108 | 109 | MsgPack.unpack(inStream, true) 110 | 111 | Running Perf Tests 112 | ================== 113 | msgpack4s comes with several benchmarks in the jmh project. They are used to compare Json4s, rojoma-json, to msgpack4s. To run them with profiling: 114 | 115 | jmh/run -wi 5 -i 5 -prof stack -jvmArgsAppend -Djmh.stack.lines=7 116 | 117 | You can also pass a regex at the end to limit which benchmarks to run. 118 | 119 | Building, testing, packaging 120 | ============================ 121 | 122 | sbt test 123 | sbt "+ package" 124 | sbt "+ make-pom" 125 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import ReleaseTransformations._ 2 | 3 | name := "msgpack4s" 4 | 5 | val commonSettings = Seq( 6 | scalaVersion := "2.12.0", 7 | organization := "org.velvia", 8 | crossScalaVersions := Seq("2.11.7", "2.12.0") 9 | ) 10 | 11 | unmanagedSourceDirectories in Compile <++= Seq(baseDirectory(_ / "src" )).join 12 | 13 | unmanagedSourceDirectories in Test <++= Seq(baseDirectory(_ / "test" )).join 14 | 15 | // Testing deps 16 | libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.0.0" % "test", 17 | "org.mockito" % "mockito-all" % "1.9.0" % "test") 18 | 19 | lazy val rojomaJson = "com.rojoma" %% "rojoma-json-v3" % "3.7.0" 20 | lazy val json4s = "org.json4s" %% "json4s-native" % "3.5.0" 21 | lazy val commonsIo = "org.apache.commons" % "commons-io" % "1.3.2" 22 | lazy val playJson = "com.typesafe.play" %% "play-json" % "2.6.0-M1" 23 | 24 | // Extra dependencies for type classes for JSON libraries 25 | libraryDependencies ++= Seq(rojomaJson % "provided", 26 | json4s % "provided", 27 | playJson % "provided") 28 | 29 | licenses += ("Apache-2.0", url("http://choosealicense.com/licenses/apache/")) 30 | 31 | // POM settings for Sonatype 32 | homepage := Some(url("https://github.com/velvia/msgpack4s")) 33 | 34 | scmInfo := Some(ScmInfo(url("https://github.com/velvia/msgpack4s"), 35 | "git@github.com:velvia/msgpack4s.git")) 36 | 37 | developers := List(Developer("velvia", 38 | "Evan Chan", 39 | "velvia@gmail.com", 40 | url("https://github.com/velvia"))) 41 | 42 | pomIncludeRepository := (_ => false) 43 | 44 | // Add sonatype repository settings 45 | publishTo := Some( 46 | if (isSnapshot.value) 47 | Opts.resolver.sonatypeSnapshots 48 | else 49 | Opts.resolver.sonatypeStaging 50 | ) 51 | 52 | releaseProcess := Seq[ReleaseStep]( 53 | checkSnapshotDependencies, 54 | inquireVersions, 55 | runClean, 56 | runTest, 57 | setReleaseVersion, 58 | commitReleaseVersion, 59 | tagRelease, 60 | ReleaseStep(action = Command.process("publishSigned", _)), 61 | setNextVersion, 62 | commitNextVersion, 63 | ReleaseStep(action = Command.process("sonatypeReleaseAll", _)), 64 | pushChanges 65 | ) 66 | 67 | lazy val msgpack4s = (project in file(".")).settings(commonSettings: _*) 68 | 69 | lazy val jmh = (project in file("jmh")).dependsOn(msgpack4s) 70 | .settings(commonSettings: _*) 71 | .settings(jmhSettings: _*) 72 | .settings(libraryDependencies += rojomaJson) 73 | .settings(libraryDependencies += json4s) 74 | .settings(libraryDependencies += commonsIo) 75 | -------------------------------------------------------------------------------- /jmh/src/main/resources/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "w2m8-wu5q", 3 | "name" : "New Visualize testing not delete", 4 | "averageRating" : 0, 5 | "createdAt" : 1383613024, 6 | "displayType" : "chart", 7 | "downloadCount" : 6, 8 | "indexUpdatedAt" : 1418094767, 9 | "moderationStatus" : true, 10 | "newBackend" : false, 11 | "numberOfComments" : 2, 12 | "oid" : 93007, 13 | "publicationAppendEnabled" : true, 14 | "publicationDate" : 1390326932, 15 | "publicationGroup" : 12536, 16 | "publicationStage" : "published", 17 | "rowsUpdatedAt" : 1398916994, 18 | "rowsUpdatedBy" : "ufjq-hmyn", 19 | "tableId" : 68222, 20 | "totalTimesRated" : 0, 21 | "viewCount" : 47174, 22 | "viewLastModified" : 1418094767, 23 | "viewType" : "tabular", 24 | "columns" : [ { 25 | "id" : 416943, 26 | "name" : "Year", 27 | "dataTypeName" : "number", 28 | "fieldName" : "year", 29 | "position" : 1, 30 | "renderTypeName" : "number", 31 | "tableColumnId" : 47750, 32 | "width" : 148, 33 | "cachedContents" : { 34 | "non_null" : 9, 35 | "smallest" : "2009", 36 | "sum" : "18090", 37 | "null" : 0, 38 | "average" : "2010", 39 | "largest" : "2011", 40 | "top" : [ { 41 | "count" : 20, 42 | "item" : "2009" 43 | }, { 44 | "count" : 19, 45 | "item" : "2010" 46 | }, { 47 | "count" : 18, 48 | "item" : "2011" 49 | } ] 50 | }, 51 | "format" : { 52 | "precisionStyle" : "standard", 53 | "align" : "right", 54 | "noCommas" : "true" 55 | } 56 | }, { 57 | "id" : 416944, 58 | "name" : "First Name", 59 | "dataTypeName" : "text", 60 | "fieldName" : "first_name", 61 | "position" : 2, 62 | "renderTypeName" : "text", 63 | "tableColumnId" : 47751, 64 | "width" : 220, 65 | "cachedContents" : { 66 | "non_null" : 9, 67 | "smallest" : "AARON", 68 | "null" : 0, 69 | "largest" : "MARK", 70 | "top" : [ { 71 | "count" : 20, 72 | "item" : "AARON" 73 | }, { 74 | "count" : 19, 75 | "item" : "DANIEL" 76 | }, { 77 | "count" : 18, 78 | "item" : "MARK" 79 | } ] 80 | }, 81 | "format" : { 82 | } 83 | }, { 84 | "id" : 416945, 85 | "name" : "Sex", 86 | "dataTypeName" : "text", 87 | "fieldName" : "sex", 88 | "position" : 3, 89 | "renderTypeName" : "text", 90 | "tableColumnId" : 47752, 91 | "width" : 136, 92 | "cachedContents" : { 93 | "non_null" : 9, 94 | "smallest" : "M", 95 | "null" : 0, 96 | "largest" : "M", 97 | "top" : [ { 98 | "count" : 20, 99 | "item" : "M" 100 | } ] 101 | }, 102 | "format" : { 103 | } 104 | }, { 105 | "id" : 416946, 106 | "name" : "Count", 107 | "dataTypeName" : "number", 108 | "fieldName" : "count", 109 | "position" : 4, 110 | "renderTypeName" : "number", 111 | "tableColumnId" : 47753, 112 | "width" : 160, 113 | "cachedContents" : { 114 | "non_null" : 9, 115 | "smallest" : "93", 116 | "sum" : "5103", 117 | "null" : 0, 118 | "average" : "567", 119 | "largest" : "1292", 120 | "top" : [ { 121 | "count" : 20, 122 | "item" : "423" 123 | }, { 124 | "count" : 19, 125 | "item" : "404" 126 | }, { 127 | "count" : 18, 128 | "item" : "417" 129 | }, { 130 | "count" : 17, 131 | "item" : "1292" 132 | }, { 133 | "count" : 16, 134 | "item" : "1138" 135 | }, { 136 | "count" : 15, 137 | "item" : "1052" 138 | }, { 139 | "count" : 14, 140 | "item" : "132" 141 | }, { 142 | "count" : 13, 143 | "item" : "93" 144 | }, { 145 | "count" : 12, 146 | "item" : "152" 147 | } ] 148 | }, 149 | "format" : { 150 | } 151 | } ], 152 | "displayFormat" : { 153 | "hideDsgMsg" : false, 154 | "valueColumns" : [ { 155 | "color" : "#003366", 156 | "fieldName" : "count" 157 | } ], 158 | "fixedColumns" : [ "first_name" ], 159 | "seriesColumns" : [ { 160 | "fieldName" : "year" 161 | } ], 162 | "pointSize" : "3", 163 | "colors" : [ "#003366", "#D95F02", "#1B9E77", "#e6ab02", "#7570b3" ], 164 | "smoothLine" : false, 165 | "sortSeries" : false, 166 | "lineSize" : "2", 167 | "chartType" : "line", 168 | "yAxis" : { 169 | "formatter" : { 170 | "abbreviate" : false 171 | } 172 | }, 173 | "dataLabels" : false, 174 | "legendDetails" : { 175 | "showSeries" : true, 176 | "showValueMarkers" : true 177 | }, 178 | "legend" : "bottom", 179 | "descriptionColumns" : [ { 180 | } ] 181 | }, 182 | "grants" : [ { 183 | "inherited" : true, 184 | "type" : "viewer", 185 | "flags" : [ "public" ] 186 | }, { 187 | "inherited" : false, 188 | "type" : "viewer", 189 | "userId" : "fenw-99f4" 190 | } ], 191 | "metadata" : { 192 | "custom_fields" : { 193 | "Test New" : { 194 | "Pass" : "" 195 | }, 196 | "new 1" : { 197 | "new 3" : "", 198 | "new 4" : "" 199 | }, 200 | "NMP" : { 201 | "No" : "" 202 | }, 203 | "ertert" : { 204 | "qwewe" : "" 205 | }, 206 | "Plan Details" : { 207 | "Plan Name" : "", 208 | "Leadership Dashboard" : "" 209 | } 210 | }, 211 | "renderTypeConfig" : { 212 | "visible" : { 213 | "chart" : true, 214 | "table" : true 215 | } 216 | }, 217 | "availableDisplayTypes" : [ "chart", "table", "fatrow", "page" ], 218 | "thumbnail" : { 219 | "page" : { 220 | "created" : true, 221 | "selection" : { 222 | "height" : 503, 223 | "width" : 942, 224 | "y1" : 15, 225 | "y2" : 518, 226 | "x2" : 1214, 227 | "x1" : 272 228 | }, 229 | "blobId" : "92ba94b745ba58d854120e648b0c04cf" 230 | } 231 | }, 232 | "jsonQuery" : { 233 | "where" : { 234 | "children" : [ { 235 | "children" : [ { 236 | "value" : "mark", 237 | "operator" : "EQUALS", 238 | "columnFieldName" : "first_name", 239 | "metadata" : { 240 | "freeform" : true 241 | } 242 | }, { 243 | "value" : "daniel", 244 | "operator" : "EQUALS", 245 | "columnFieldName" : "first_name", 246 | "metadata" : { 247 | "freeform" : true 248 | } 249 | }, { 250 | "value" : "aaron", 251 | "operator" : "EQUALS", 252 | "columnFieldName" : "first_name", 253 | "metadata" : { 254 | "freeform" : true 255 | } 256 | } ], 257 | "operator" : "OR", 258 | "metadata" : { 259 | "customValues" : [ [ "kyle" ], [ "brian" ] ], 260 | "tableColumnId" : { 261 | "12536" : 47751 262 | }, 263 | "operator" : "EQUALS" 264 | } 265 | }, { 266 | "children" : [ { 267 | "value" : 2009, 268 | "operator" : "EQUALS", 269 | "columnFieldName" : "year", 270 | "metadata" : { 271 | "freeform" : true 272 | } 273 | }, { 274 | "value" : 2010, 275 | "operator" : "EQUALS", 276 | "columnFieldName" : "year", 277 | "metadata" : { 278 | "freeform" : true 279 | } 280 | }, { 281 | "value" : 2011, 282 | "operator" : "EQUALS", 283 | "columnFieldName" : "year", 284 | "metadata" : { 285 | "freeform" : true 286 | } 287 | } ], 288 | "operator" : "OR", 289 | "metadata" : { 290 | "tableColumnId" : { 291 | "12536" : 47750 292 | }, 293 | "operator" : "EQUALS" 294 | } 295 | } ], 296 | "operator" : "AND", 297 | "metadata" : { 298 | "unifiedVersion" : 2 299 | } 300 | } 301 | }, 302 | "conditionalFormatting" : [ { 303 | "color" : "#ff0000", 304 | "condition" : { 305 | "_key" : "(47751|STARTS_WITH|D)", 306 | "value" : "D", 307 | "tableColumnId" : 47751, 308 | "operator" : "STARTS_WITH" 309 | } 310 | } ] 311 | }, 312 | "owner" : { 313 | "id" : "3nte-ch42", 314 | "displayName" : "anu", 315 | "profileImageUrlLarge" : "/api/users/3nte-ch42/profile_images/LARGE", 316 | "profileImageUrlMedium" : "/api/users/3nte-ch42/profile_images/THUMB", 317 | "profileImageUrlSmall" : "/api/users/3nte-ch42/profile_images/TINY", 318 | "screenName" : "anu", 319 | "rights" : [ "create_datasets", "edit_others_datasets", "edit_sdp", "edit_site_theme", "moderate_comments", "manage_users", "chown_datasets", "edit_nominations", "approve_nominations", "feature_items", "federations", "manage_stories", "manage_approval", "change_configurations", "view_domain", "view_others_datasets", "edit_pages", "create_pages", "short_session", "view_goals", "view_dashboards", "edit_goals", "edit_dashboards", "create_dashboards" ], 320 | "flags" : [ "admin" ] 321 | }, 322 | "privateMetadata" : { 323 | "custom_fields" : { 324 | "Test New" : { 325 | "Fail" : "" 326 | }, 327 | "NMP" : { 328 | "Yes" : "", 329 | "Not Sure" : "" 330 | }, 331 | "Plan Details" : { 332 | "Archive" : "" 333 | } 334 | } 335 | }, 336 | "query" : { 337 | "filterCondition" : { 338 | "type" : "operator", 339 | "value" : "AND", 340 | "children" : [ { 341 | "type" : "operator", 342 | "value" : "OR", 343 | "children" : [ { 344 | "type" : "operator", 345 | "value" : "EQUALS", 346 | "children" : [ { 347 | "columnId" : 416944, 348 | "type" : "column" 349 | }, { 350 | "type" : "literal", 351 | "value" : "mARK" 352 | } ], 353 | "metadata" : { 354 | "freeform" : true 355 | } 356 | }, { 357 | "type" : "operator", 358 | "value" : "EQUALS", 359 | "children" : [ { 360 | "columnId" : 416944, 361 | "type" : "column" 362 | }, { 363 | "type" : "literal", 364 | "value" : "daniel" 365 | } ], 366 | "metadata" : { 367 | "freeform" : true 368 | } 369 | }, { 370 | "type" : "operator", 371 | "value" : "EQUALS", 372 | "children" : [ { 373 | "columnId" : 416944, 374 | "type" : "column" 375 | }, { 376 | "type" : "literal", 377 | "value" : "aaron" 378 | } ], 379 | "metadata" : { 380 | "freeform" : true 381 | } 382 | } ], 383 | "metadata" : { 384 | "operator" : "EQUALS", 385 | "tableColumnId" : { 386 | "12536" : 47751 387 | }, 388 | "customValues" : [ [ "kyle" ], [ "brian" ] ] 389 | } 390 | }, { 391 | "type" : "operator", 392 | "value" : "OR", 393 | "children" : [ { 394 | "type" : "operator", 395 | "value" : "EQUALS", 396 | "children" : [ { 397 | "columnId" : 416943, 398 | "type" : "column" 399 | }, { 400 | "type" : "literal", 401 | "value" : "2009" 402 | } ], 403 | "metadata" : { 404 | "freeform" : true 405 | } 406 | }, { 407 | "type" : "operator", 408 | "value" : "EQUALS", 409 | "children" : [ { 410 | "columnId" : 416943, 411 | "type" : "column" 412 | }, { 413 | "type" : "literal", 414 | "value" : "2010" 415 | } ], 416 | "metadata" : { 417 | "freeform" : true 418 | } 419 | }, { 420 | "type" : "operator", 421 | "value" : "EQUALS", 422 | "children" : [ { 423 | "columnId" : 416943, 424 | "type" : "column" 425 | }, { 426 | "type" : "literal", 427 | "value" : "2011" 428 | } ], 429 | "metadata" : { 430 | "freeform" : true 431 | } 432 | } ], 433 | "metadata" : { 434 | "operator" : "EQUALS", 435 | "tableColumnId" : { 436 | "12536" : 47750 437 | } 438 | } 439 | } ], 440 | "metadata" : { 441 | "unifiedVersion" : 2 442 | } 443 | } 444 | }, 445 | "rights" : [ "read", "write", "add", "delete", "grant", "add_column", "remove_column", "update_column", "update_view", "delete_view" ], 446 | "tableAuthor" : { 447 | "id" : "tirm-miwi", 448 | "displayName" : "Karin Hellman", 449 | "roleName" : "publisher", 450 | "screenName" : "Karin Hellman", 451 | "rights" : [ "create_datasets", "edit_others_datasets", "chown_datasets", "edit_nominations", "approve_nominations", "moderate_comments", "manage_stories", "feature_items", "change_configurations", "view_domain", "view_others_datasets", "view_goals", "view_dashboards", "edit_goals", "edit_dashboards" ], 452 | "flags" : [ "admin" ] 453 | }, 454 | "viewFilters" : { 455 | "type" : "operator", 456 | "value" : "AND", 457 | "children" : [ { 458 | "type" : "operator", 459 | "value" : "OR", 460 | "children" : [ { 461 | "type" : "operator", 462 | "value" : "EQUALS", 463 | "children" : [ { 464 | "columnId" : 416944, 465 | "type" : "column" 466 | }, { 467 | "type" : "literal", 468 | "value" : "mARK" 469 | } ], 470 | "metadata" : { 471 | "freeform" : true 472 | } 473 | }, { 474 | "type" : "operator", 475 | "value" : "EQUALS", 476 | "children" : [ { 477 | "columnId" : 416944, 478 | "type" : "column" 479 | }, { 480 | "type" : "literal", 481 | "value" : "daniel" 482 | } ], 483 | "metadata" : { 484 | "freeform" : true 485 | } 486 | }, { 487 | "type" : "operator", 488 | "value" : "EQUALS", 489 | "children" : [ { 490 | "columnId" : 416944, 491 | "type" : "column" 492 | }, { 493 | "type" : "literal", 494 | "value" : "aaron" 495 | } ], 496 | "metadata" : { 497 | "freeform" : true 498 | } 499 | } ], 500 | "metadata" : { 501 | "operator" : "EQUALS", 502 | "tableColumnId" : { 503 | "12536" : 47751 504 | }, 505 | "customValues" : [ [ "kyle" ], [ "brian" ] ] 506 | } 507 | }, { 508 | "type" : "operator", 509 | "value" : "OR", 510 | "children" : [ { 511 | "type" : "operator", 512 | "value" : "EQUALS", 513 | "children" : [ { 514 | "columnId" : 416943, 515 | "type" : "column" 516 | }, { 517 | "type" : "literal", 518 | "value" : "2009" 519 | } ], 520 | "metadata" : { 521 | "freeform" : true 522 | } 523 | }, { 524 | "type" : "operator", 525 | "value" : "EQUALS", 526 | "children" : [ { 527 | "columnId" : 416943, 528 | "type" : "column" 529 | }, { 530 | "type" : "literal", 531 | "value" : "2010" 532 | } ], 533 | "metadata" : { 534 | "freeform" : true 535 | } 536 | }, { 537 | "type" : "operator", 538 | "value" : "EQUALS", 539 | "children" : [ { 540 | "columnId" : 416943, 541 | "type" : "column" 542 | }, { 543 | "type" : "literal", 544 | "value" : "2011" 545 | } ], 546 | "metadata" : { 547 | "freeform" : true 548 | } 549 | } ], 550 | "metadata" : { 551 | "operator" : "EQUALS", 552 | "tableColumnId" : { 553 | "12536" : 47750 554 | } 555 | } 556 | } ], 557 | "metadata" : { 558 | "unifiedVersion" : 2 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /jmh/src/main/scala/org.velvia/BasicPerfBenchmark.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import org.openjdk.jmh.annotations.Benchmark 4 | import org.openjdk.jmh.annotations.BenchmarkMode 5 | import org.openjdk.jmh.annotations.{Mode, State, Scope} 6 | import org.openjdk.jmh.annotations.OutputTimeUnit 7 | import util.Random 8 | 9 | import java.util.concurrent.TimeUnit 10 | 11 | /** 12 | * Measures basic read benchmark with no NAs for an IntColumn. 13 | * Just raw read speed basically. 14 | * 15 | * For a description of the JMH measurement modes, see 16 | * https://github.com/ktoso/sbt-jmh/blob/master/src/sbt-test/sbt-jmh/jmh-run/src/main/scala/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.scala 17 | */ 18 | @State(Scope.Thread) 19 | class BasicPerfBenchmark { 20 | def genMap() = { 21 | Map("eventType" -> Random.nextInt(25), 22 | "user" -> "abcDEFghiDEF", 23 | "downloads" -> Map("bytes" -> 123456, "millis" -> Random.nextInt(50000)), 24 | "someList" -> List.fill(5)(Random.nextInt(16))) 25 | } 26 | 27 | import org.velvia.msgpack.CollectionCodecs._ 28 | import org.velvia.msgpack.SimpleCodecs._ 29 | import org.velvia.msgpack.RawStringCodecs._ 30 | import org.velvia.msgpack.AnyCodecs._ 31 | 32 | implicit val anyCodec = new AnyCodec[String, Any](false)(StringCodec, DefaultAnyCodec) 33 | implicit val mapCodec = new MapCodec[String, Any] 34 | 35 | val map = genMap() 36 | val bytes = MsgPack.pack(genMap()) 37 | 38 | // According to @ktosopl, be sure to return some value if possible so that JVM won't 39 | // optimize out the method body. However JMH is apparently very good at avoiding this. 40 | @Benchmark 41 | @BenchmarkMode(Array(Mode.AverageTime)) 42 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 43 | def packMap(): Int = { 44 | var total = 0 45 | while (total < 100) { 46 | msgpack.pack(map) 47 | total += 1 48 | } 49 | total 50 | } 51 | 52 | @Benchmark 53 | @BenchmarkMode(Array(Mode.AverageTime)) 54 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 55 | def unpackMap(): Int = { 56 | var total = 0 57 | while (total < 100) { 58 | msgpack.unpack(bytes)(mapCodec) 59 | total += 1 60 | } 61 | total 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jmh/src/main/scala/org.velvia/Json4sBenchmark.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import org.openjdk.jmh.annotations.Benchmark 4 | import org.openjdk.jmh.annotations.BenchmarkMode 5 | import org.openjdk.jmh.annotations.{Mode, State, Scope} 6 | import org.openjdk.jmh.annotations.OutputTimeUnit 7 | import util.Random 8 | 9 | import java.util.concurrent.TimeUnit 10 | 11 | /** 12 | * Measures serialization speeds for Json4S AST, JSON vs msgpack4s, using a GeoJSON-based example 13 | * Obviously this is meant to highlight an advantage for msgpack4s. See the Rojoma benchmark 14 | * for a more common JSON example. 15 | * 16 | * For a description of the JMH measurement modes, see 17 | * https://github.com/ktoso/sbt-jmh/blob/master/src/sbt-test/sbt-jmh/jmh-run/src/main/scala/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.scala 18 | */ 19 | @State(Scope.Thread) 20 | class Json4sBenchmark { 21 | // From City of Chicago GeoJSON 22 | val geoJsonStr = """ 23 | {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-122.419224000303,37.8084529988503],[-122.419209999933,37.8083879987715],[-122.419086000307,37.8078029987107],[-122.41890800035,37.8068629993342],[-122.418781999832,37.8062409988176],[-122.418380999904,37.8059769991614],[-122.417226000312,37.8051699988847],[-122.416818000394,37.8048869994474],[-122.416696000492,37.8042769990165],[-122.416605000011,37.8038229991133],[-122.416510999499,37.8033479991638],[-122.416323999798,37.8024109989096],[-122.417965999793,37.8022009988738],[-122.417877000219,37.8017069992708],[-122.417798999763,37.8012669989059],[-122.416663000154,37.8014129990293],[-122.416133000089,37.8014809989957],[-122.416038000462,37.8010129991899],[-122.415943000256,37.8005469990861],[-122.415863999599,37.8001419988723],[-122.415761999512,37.7996159987249],[-122.416491000306,37.7995249990543],[-122.417385000124,37.7994139989871],[-122.417480999887,37.7994009989596],[-122.41822599992,37.7993049987749],[-122.419048999584,37.7991979986564],[-122.419961000145,37.7990849989214],[-122.420086000365,37.7990689993906],[-122.420345999847,37.7990369993177],[-122.420579999694,37.7990079986842],[-122.420688999905,37.7989709994636],[-122.422337000473,37.7987619991301],[-122.423981999868,37.7985519989933],[-122.423861000083,37.798471998645],[-122.423708999928,37.7977229989347],[-122.423793000159,37.7976169993813],[-122.423672000058,37.7975359994118],[-122.423599000042,37.797178998943],[-122.42352099958,37.7967929986915],[-122.42360100042,37.7966909991849],[-122.423483999513,37.7966059990962],[-122.423333000076,37.7958599988102],[-122.423413000187,37.7957369993079],[-122.42349300001,37.7956139988478],[-122.423347999475,37.7948969990462],[-122.42322699954,37.7948169990056],[-122.423311000554,37.7947109987824],[-122.423169999934,37.7940149993583],[-122.423049000492,37.7939349991406],[-122.423132999628,37.7938289990457],[-122.422993000132,37.793129999101],[-122.422870999532,37.7930499988197],[-122.422955000191,37.7929439987974],[-122.422816000046,37.792254999293],[-122.422693999938,37.7921749988344],[-122.422778000101,37.7920679994696],[-122.422635999458,37.7913689991735],[-122.422515000385,37.7912889993441], 24 | [-122.422598999561,37.7911829995412],[-122.422454999551,37.7904719994434],[-122.422329000257,37.7903699993831],[-122.422203000399,37.7902669988519],[-122.422052000247,37.7895179990498],[-122.422134999856,37.7894119986923],[-122.422013999994,37.7893319992953],[-122.421939999979,37.7889689991135],[-122.421865000341,37.7885959990568],[-122.421948999645,37.7884899987272],[-122.421826999892,37.7884099992294],[-122.421749999473,37.7880269993593],[-122.421674000157,37.7876539992202],[-122.421757999804,37.78754799899],[-122.421636999979,37.7874679992346],[-122.421559999892,37.7870869989993],[-122.421484000221,37.7867139987112],[-122.421569999558,37.7866189986795],[-122.423213999894,37.7864019993457],[-122.423028000164,37.785481999374],[-122.423547999843,37.7854099991388],[-122.423820000059,37.7853809994596],[-122.424441000297,37.7853959989632],[-122.424559000435,37.7854049992196],[-122.424687000192,37.7853349994892],[-122.424858000364,37.7861959988031],[-122.425047000517,37.7871299989474],[-122.425141000462,37.7875969993815],[-122.42523700019,37.7880699989081],[-122.425331000431,37.7885359987952],[-122.425422999987,37.788994999068],[-122.427059999649,37.7887869994699],[-122.427259999468,37.7897429989565],[-122.428905000317,37.7895339987381],[-122.430550999934,37.7893239992641],[-122.430355999649,37.7883679993638],[-122.432002999712,37.788158999109],[-122.432826000184,37.7880549986607],[-122.433646999951,37.7879499987527],[-122.435293000415,37.7877409992626],[-122.436934999447,37.7875329994664],[-122.438582000057,37.7873249989537],[-122.438394000529,37.786391998938],[-122.436746999668,37.7866009993508],[-122.43510599953,37.7868109992292],[-122.434917999813,37.7858759990286],[-122.436557000391,37.7856679989008],[-122.438206000072,37.7854589994592],[-122.439872999773,37.7852479989634],[-122.441536999578,37.7850359994511],[-122.443183000148,37.7848269991394],[-122.444830999636,37.7846169991396],[-122.446246999773,37.7844379993583],[-122.446032999705,37.7835089995015],[-122.444642999893,37.7836859991523],[-122.442989000039,37.7838969988009],[-122.441349000497,37.7841069992481],[-122.441255000434,37.7836419988363],[-122.441158000102,37.7831639989596], 25 | [-122.44112999985,37.7830299988013],[-122.440962999994,37.7822389993173],[-122.440784000371,37.7813099994215],[-122.440589000548,37.7803729992522],[-122.440396999909,37.7794389990516],[-122.440957999616,37.7793639994193],[-122.441119000517,37.7793719992517],[-122.442053000085,37.7792439987032],[-122.443698999537,37.7790379989305],[-122.445346999464,37.778836998866],[-122.446035000084,37.778749999083],[-122.447040000184,37.7786219988612],[-122.447016999541,37.7787259994541],[-122.447289000534,37.7800619991812],[-122.447352000475,37.780151999121],[-122.447322999472,37.7802409988826],[-122.447444999881,37.7810119987019],[-122.447517999958,37.7810699995052],[-122.447466999865,37.7811599989498],[-122.447539000439,37.7815099995216],[-122.447501999482,37.7816939994686],[-122.447311000291,37.7821829994228],[-122.447302000455,37.7823919989871],[-122.447317999456,37.7824319995127],[-122.447366000241,37.7825549993786],[-122.448064000198,37.7824829988538],[-122.448989000557,37.7823599988332],[-122.449078999807,37.7823139987406],[-122.450025999899,37.7822029987946],[-122.450092000529,37.7821169993655],[-122.450199000054,37.7821769992476],[-122.451045999616,37.7820729993392],[-122.45112000039,37.7819839992283],[-122.45123300055,37.7820509987078],[-122.452061000434,37.7819469994933],[-122.452142999782,37.7818529987761],[-122.452252000261,37.7819129988348],[-122.453099000048,37.7818029990518],[-122.453169999812,37.7817169988255],[-122.453268000559,37.7817789988554],[-122.454069999762,37.7816859992635],[-122.454175999923,37.7815989992197],[-122.454283000178,37.7816549994079],[-122.455211000208,37.7815359992582],[-122.455280999983,37.7814589994935],[-122.455378000184,37.781523999242],[-122.455469000415,37.781513999482],[-122.455523999804,37.7814309992867],[-122.455632999994,37.781489999511],[-122.456225999696,37.781453998919],[-122.456296000392,37.7813809990483],[-122.456396000486,37.7814469990214],[-122.456628999639,37.7814389993181],[-122.457647000354,37.7813959986357],[-122.457733000229,37.7813149988177],[-122.457822999979,37.7813759987488],[-122.458730000288,37.7813429988535],[-122.458859000048,37.7812569993584] 26 | ]]]},"properties":{"supdist":"SUPERVISORIAL DISTRICT 2" 27 | ,"supname":"Farrell","supervisor":"2","numbertext":"TWO","_feature_id":"1","_feature_id_string":"Elect_Super 28 | visor_Dists.1"} 29 | } 30 | """ 31 | 32 | import org.json4s.native.JsonMethods._ 33 | import org.json4s._ 34 | 35 | val ast = parse(geoJsonStr) 36 | val os = new org.apache.commons.io.output.NullOutputStream 37 | val dos = new java.io.DataOutputStream(os) 38 | 39 | import org.velvia.msgpack.Json4sCodecs._ 40 | 41 | @Benchmark 42 | @BenchmarkMode(Array(Mode.AverageTime)) 43 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 44 | def json4sAstMsgpack(): Int = { 45 | var total = 0 46 | while (total < 100) { 47 | msgpack.pack(ast, dos) 48 | dos.flush() 49 | total += 1 50 | } 51 | total 52 | } 53 | 54 | @Benchmark 55 | @BenchmarkMode(Array(Mode.AverageTime)) 56 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 57 | def json4sAstJson(): Int = { 58 | var total = 0 59 | while (total < 100) { 60 | dos.write(compact(render(ast)).getBytes("UTF-8")) 61 | total += 1 62 | } 63 | total 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /jmh/src/main/scala/org.velvia/RojomaJsonBenchmark.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import com.rojoma.json.v3.io.{JsonReader, CompactJsonWriter} 4 | import org.openjdk.jmh.annotations.Benchmark 5 | import org.openjdk.jmh.annotations.BenchmarkMode 6 | import org.openjdk.jmh.annotations.{Mode, State, Scope} 7 | import org.openjdk.jmh.annotations.OutputTimeUnit 8 | import util.Random 9 | 10 | import java.util.concurrent.TimeUnit 11 | 12 | /** 13 | * Measures serialization speeds over an OutputStream API for a prototypical, string-heavy 14 | * 13KB JSON document. Rojoma-JSON AST, Rojoma-JSON CompactWriter vs msgpack4s. 15 | * 16 | * For a description of the JMH measurement modes, see 17 | * https://github.com/ktoso/sbt-jmh/blob/master/src/sbt-test/sbt-jmh/jmh-run/src/main/scala/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.scala 18 | */ 19 | @State(Scope.Thread) 20 | class RojomaJsonBenchmark { 21 | // From checked in metadata JSON file, more typically JSON data 22 | val rawJsonStr = io.Source.fromURL(getClass.getResource("/metadata.json")).getLines.mkString("") 23 | 24 | val ast = JsonReader.fromString(rawJsonStr) 25 | val os = new org.apache.commons.io.output.NullOutputStream 26 | val dos = new java.io.DataOutputStream(os) 27 | val osw = new java.io.OutputStreamWriter(os) 28 | 29 | import org.velvia.msgpack.RojomaJsonCodecs._ 30 | 31 | // According to @ktosopl, be sure to return some value if possible so that JVM won't 32 | // optimize out the method body. However JMH is apparently very good at avoiding this. 33 | @Benchmark 34 | @BenchmarkMode(Array(Mode.AverageTime)) 35 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 36 | def rojomaAstMsgpack(): Int = { 37 | var total = 0 38 | while (total < 100) { 39 | msgpack.pack(ast, dos) 40 | dos.flush() 41 | total += 1 42 | } 43 | total 44 | } 45 | 46 | @Benchmark 47 | @BenchmarkMode(Array(Mode.AverageTime)) 48 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 49 | def rojomaAstJson(): Int = { 50 | var total = 0 51 | while (total < 100) { 52 | CompactJsonWriter.toWriter(osw, ast) 53 | total += 1 54 | } 55 | total 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.1.12") 2 | 3 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.5") 4 | 5 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") 6 | 7 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1") 8 | -------------------------------------------------------------------------------- /src/org.velvia/MsgPack.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import java.io.ByteArrayInputStream 4 | import java.io.ByteArrayOutputStream 5 | import java.io.DataInputStream 6 | import java.io.DataOutputStream 7 | import java.io.EOFException 8 | import java.io.IOException 9 | 10 | 11 | class InvalidMsgPackDataException(msg: String) extends Exception(msg) 12 | 13 | /** 14 | * A super simple MessagePack serialization library for Scala 15 | * based on the msgpack-java-lite project 16 | * https://bitbucket.org/sirbrialliance 17 | * 18 | * This implementation uses the builtin Scala types: 19 | * - null 20 | * - Boolean 21 | * - Number 22 | * byte, short, int, and long will be packed and unpacked into the smallest possible representation, 23 | * BigInt's will be used for anything larger than long.MAX_VALUE 24 | * - String (UTF-8), Array[Byte], or ByteBuffer (the *whole* buffer) (defaults to unpacked as a String) 25 | * - Map (any Map may be used for packing, always unpacked as a HashMap) 26 | * - Seq (any Seq may be used for packing, always unpacked as a Vector) 27 | * - Arrays (always unpacked as a Vector) 28 | * Passing any other types will throw an IllegalArumentException. 29 | * 30 | * NOTE: This API is old. Try the newer TypeClass-based API. 31 | * 32 | * @author velvia 33 | */ 34 | object MsgPack { 35 | import org.velvia.msgpack.Format._ 36 | import org.velvia.msgpack.AnyCodecs._ 37 | 38 | /** 39 | * Packs an item using the msgpack protocol. 40 | * 41 | * Warning: this does not do any recursion checks. If you pass a cyclic object, 42 | * you will run in an infinite loop until you run out of memory. 43 | * 44 | * @param item 45 | * @return the packed data 46 | * @throws UnpackableItemException If the given data cannot be packed. 47 | */ 48 | def pack(item: Any): Array[Byte] = { 49 | val out = new ByteArrayOutputStream() 50 | try { 51 | pack(item, new DataOutputStream(out)) 52 | } catch { 53 | case e: IOException => 54 | //this shouldn't happen 55 | throw new RuntimeException("ByteArrayOutputStream threw an IOException!", e); 56 | } 57 | out.toByteArray(); 58 | } 59 | 60 | /** 61 | * Unpacks the given data. 62 | * 63 | * @param data the byte array to unpack 64 | * @return the unpacked data 65 | * @throws InvalidMsgPackDataException If the given data cannot be unpacked. 66 | */ 67 | def unpack(data: Array[Byte]): Any = { 68 | val in = new ByteArrayInputStream(data) 69 | try { 70 | unpack(new DataInputStream(in)) 71 | } catch { 72 | case ex: InvalidMsgPackDataException => throw ex 73 | case ex: IOException => //this shouldn't happen 74 | throw new RuntimeException("ByteArrayInStream threw an IOException!", ex); 75 | } 76 | } 77 | 78 | /** 79 | * Packs the item, streaming the data to the given OutputStream. 80 | * Warning: this does not do any recursion checks. If you pass a cyclic object, 81 | * you will run in an infinite loop until you run out of memory/space to write. 82 | * @param item 83 | * @param out 84 | */ 85 | def pack(item: Any, out: DataOutputStream) { 86 | DefaultAnyCodec.pack(out, item) 87 | } 88 | 89 | /** 90 | * Unpacks the item, streaming the data from the given OutputStream. 91 | * @param in Input stream to read from 92 | * @param compatibilityMode True for compatibility mode 93 | * MessagePack format used to pack raw bytes and strings with the same message format 94 | * header (0xda/b, 0xa0-0xbf). If you want compatibility with old MessagePack messages, 95 | * setting this to True will return all String formatted messages as raw bytes instead. 96 | * The old UNPACK_RAW_AS_STRING option becomes the default behavior now since the old 97 | * RAW format is now the STRING format. 98 | * @throws IOException if the underlying stream has an error 99 | * @throws InvalidMsgPackDataException If the given data cannot be unpacked. 100 | */ 101 | def unpack(in: DataInputStream, compatibilityMode: Boolean = false): Any = { 102 | if (compatibilityMode) DefaultAnyCodecCompat.unpack(in) 103 | else DefaultAnyCodec.unpack(in) 104 | } 105 | } -------------------------------------------------------------------------------- /src/org.velvia/MsgPackUtils.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import scala.language.implicitConversions 4 | import java.io.DataInputStream 5 | 6 | /** 7 | * Some convenient methods for unpacking things 8 | */ 9 | object MsgPackUtils { 10 | import MsgPack.unpack 11 | 12 | def getInt(item: Any): Int = item match { 13 | case i: Int => i 14 | case b: Byte => b.toInt 15 | case s: Short => s.toInt 16 | case x: Any => throw new ClassCastException("Can't convert " + x.getClass.getName 17 | + " to an Int") 18 | } 19 | 20 | def getLong(item: Any): Long = item match { 21 | case i: Int => i.toLong 22 | case l: Long => l 23 | case b: Byte => b.toLong 24 | case s: Short => s.toLong 25 | case x: Any => throw new ClassCastException("Can't convert " + x.getClass.getName 26 | + " to a Long") 27 | } 28 | 29 | def unpackInt(rawData: Array[Byte]): Int = getInt(unpack(rawData)) 30 | 31 | def unpackLong(rawData: Array[Byte]): Long = getLong(unpack(rawData)) 32 | 33 | def unpackSeq(rawData: Array[Byte]): Seq[Any] = unpack(rawData).asInstanceOf[Seq[Any]] 34 | 35 | def unpackMap(rawData: Array[Byte]): Map[Any, Any] = unpack(rawData).asInstanceOf[Map[Any, Any]] 36 | 37 | def unpackInt(dis: DataInputStream): Int = getInt(unpack(dis)) 38 | 39 | def unpackLong(dis: DataInputStream): Long = getLong(unpack(dis)) 40 | 41 | def unpackSeq(dis: DataInputStream): Seq[Any] = unpack(dis).asInstanceOf[Seq[Any]] 42 | 43 | def unpackMap(dis: DataInputStream): Map[Any, Any] = unpack(dis).asInstanceOf[Map[Any, Any]] 44 | 45 | /** 46 | * Implicit conversions so we can access elements of the map through below extension methods 47 | */ 48 | implicit def toMapWrapper[K](map: collection.Map[K, Any]) = new MsgPackMapWrapper(map) 49 | } 50 | 51 | /** 52 | * A wrapper around maps to provide convenient extension methods 53 | */ 54 | class MsgPackMapWrapper[K](map: collection.Map[K, Any]) { 55 | def as[T](key: K): T = map(key).asInstanceOf[T] 56 | 57 | def asOpt[T](key: K): Option[T] = map.get(key).asInstanceOf[Option[T]] 58 | 59 | def asMap(key: K): Map[K, Any] = map(key).asInstanceOf[Map[K, Any]] 60 | 61 | def asInt(key: K): Int = MsgPackUtils.getInt(map(key)) 62 | 63 | def asLong(key: K): Long = MsgPackUtils.getLong(map(key)) 64 | } 65 | -------------------------------------------------------------------------------- /src/org.velvia/msgpack/CaseClassCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream} 4 | 5 | import org.velvia.msgpack.TupleCodecs.{TupleCodec4, TupleCodec3, TupleCodec2} 6 | 7 | object CaseClassCodecs { 8 | 9 | class CaseClassCodec1[T, A]( 10 | apply: A => T, 11 | unapply: T => Option[A] 12 | )(implicit K: Codec[A]) extends Codec[T] { 13 | 14 | private val codec1 = implicitly[Codec[A]] 15 | 16 | override def pack(out: DataOutputStream, item: T): Unit = { 17 | out.write(0x01 | Format.MP_FIXARRAY) 18 | codec1.pack(out, unapply(item).get) 19 | } 20 | val unpackFuncMap = FastByteMap[UnpackFunc]( 21 | (0x01 | Format.MP_FIXARRAY).toByte -> { in: DIS => 22 | val r1 = codec1.unpack(in) 23 | apply(r1) 24 | } 25 | ) 26 | } 27 | 28 | 29 | class CaseClassCodec2[T, A1, A2]( 30 | apply: (A1, A2) => T, 31 | unapply: T => Option[(A1, A2)] 32 | )( 33 | implicit K1: Codec[A1], 34 | K2: Codec[A2] 35 | ) extends Codec[T] { 36 | val codec = new TupleCodec2[A1, A2] 37 | val _apply: ((A1, A2)) => T = Function.tupled(apply) 38 | 39 | override def pack(out: DataOutputStream, item: T): Unit = { 40 | codec.pack(out, unapply(item).get) 41 | } 42 | val unpackFuncMap = codec.unpackFuncMap.mapValues(_.andThen(_apply)) 43 | } 44 | 45 | class CaseClassCodec3[T, A1, A2, A3]( 46 | apply: (A1, A2, A3) => T, 47 | unapply: T => Option[(A1, A2, A3)] 48 | )( 49 | implicit K1: Codec[A1], 50 | K2: Codec[A2], 51 | K3: Codec[A3] 52 | ) extends Codec[T] { 53 | val codec = new TupleCodec3[A1, A2, A3] 54 | val _apply = Function.tupled(apply) 55 | 56 | override def pack(out: DataOutputStream, item: T): Unit = { 57 | codec.pack(out, unapply(item).get) 58 | } 59 | val unpackFuncMap = codec.unpackFuncMap.mapValues(_.andThen(_apply)) 60 | } 61 | 62 | class CaseClassCodec4[T, A1, A2, A3, A4]( 63 | apply: (A1, A2, A3, A4) => T, 64 | unapply: T => Option[(A1, A2, A3, A4)] 65 | )( 66 | implicit K1: Codec[A1], 67 | K2: Codec[A2], 68 | K3: Codec[A3], 69 | K4: Codec[A4] 70 | ) extends Codec[T] { 71 | val codec = new TupleCodec4[A1, A2, A3, A4] 72 | val _apply = Function.tupled(apply) 73 | 74 | override def pack(out: DataOutputStream, item: T): Unit = { 75 | codec.pack(out, unapply(item).get) 76 | } 77 | val unpackFuncMap = codec.unpackFuncMap.mapValues(_.andThen(_apply)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/org.velvia/msgpack/Codecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream, EOFException} 4 | import java.nio.ByteBuffer 5 | import org.velvia.InvalidMsgPackDataException 6 | 7 | /** 8 | * A typeclass for packing and unpacking specific types from MessagePack 9 | */ 10 | trait Codec[@specialized(Int, Long, Short, Byte, Double, Float) A] { 11 | def pack(out: DataOutputStream, item: A) 12 | 13 | // unpack reads the next chunk from the DIS stream, including the first byte, and 14 | // throws an exception if the chunk does not match one of the supported types 15 | // in the unpackFuncMap. 16 | def unpack(in: DIS): A = { 17 | try { 18 | val headerByte = in.readByte() 19 | val func = unpackFuncMap.getOrElse(headerByte, defaultUnpackFunc(headerByte) ) 20 | func(in) 21 | } catch { 22 | case ex: EOFException => 23 | throw new InvalidMsgPackDataException("No more input available when expecting a value") 24 | } 25 | } 26 | 27 | type UnpackFunc = DIS => A 28 | 29 | def defaultUnpackFunc(headerByte: Byte): UnpackFunc = 30 | { dis => throw new InvalidMsgPackDataException("Input contains invalid type value " + headerByte) } 31 | 32 | // The unpackFuncMap maps the first byte that contains the MessagePack format type 33 | // to a function passed a DIS to read any additional bytes required to unpack A. 34 | val unpackFuncMap: FastByteMap[UnpackFunc] 35 | } 36 | 37 | /** 38 | * Contains codecs for simple types: bool, ints, longs, floats, doubles 39 | */ 40 | object SimpleCodecs { 41 | import Format._ 42 | 43 | implicit object BooleanCodec extends Codec[Boolean] { 44 | def pack(out: DataOutputStream, item: Boolean) { 45 | out.write(if (item) MP_TRUE else MP_FALSE) 46 | } 47 | 48 | val unpackFuncMap = FastByteMap[UnpackFunc]( 49 | MP_FALSE -> { dis: DIS => false }, 50 | MP_TRUE -> { dis: DIS => true } 51 | ) 52 | } 53 | 54 | val byteFuncMap = FastByteMap[DIS => Byte]( 55 | (MP_NEGATIVE_FIXNUM.toInt to Byte.MaxValue).map(_.toByte).map { b => 56 | b -> { dis: DIS => b } 57 | } :_*) 58 | 59 | implicit object ByteCodec extends Codec[Byte] { 60 | def pack(out: DataOutputStream, item: Byte) { packLong(item.toLong, out) } 61 | val unpackFuncMap = FastByteMap[UnpackFunc]( 62 | MP_UINT8 -> { in: DIS => in.readByte() }, 63 | MP_INT8 -> { in: DIS => in.readByte() } 64 | ) ++ byteFuncMap 65 | } 66 | 67 | implicit object ShortCodec extends Codec[Short] { 68 | def pack(out: DataOutputStream, item: Short) { packLong(item.toLong, out) } 69 | val unpackFuncMap = FastByteMap[UnpackFunc]( 70 | MP_UINT16 -> { in: DIS => in.readShort() }, 71 | MP_INT16 -> { in: DIS => in.readShort() }, 72 | MP_UINT8 -> { in: DIS => in.readUnsignedByte().toShort }, 73 | MP_INT8 -> { in: DIS => in.readByte().toShort } 74 | ) ++ byteFuncMap.mapValues(_.andThen(_.toShort)) 75 | } 76 | 77 | implicit object IntCodec extends Codec[Int] { 78 | def pack(out: DataOutputStream, item: Int) { packLong(item.toLong, out) } 79 | val unpackFuncMap = FastByteMap[UnpackFunc]( 80 | MP_UINT16 -> { in: DIS => in.readShort() & MAX_16BIT }, 81 | MP_INT16 -> { in: DIS => in.readShort().toInt }, 82 | MP_UINT32 -> { in: DIS => in.readInt() }, 83 | MP_INT32 -> { in: DIS => in.readInt() }, 84 | MP_UINT8 -> { in: DIS => in.readUnsignedByte() }, 85 | MP_INT8 -> { in: DIS => in.readByte().toInt } 86 | ) ++ byteFuncMap.mapValues(_.andThen(_.toInt)) 87 | } 88 | 89 | implicit object LongCodec extends Codec[Long] { 90 | def pack(out: DataOutputStream, item: Long) { packLong(item, out) } 91 | val unpackFuncMap = FastByteMap[UnpackFunc]( 92 | MP_UINT16 -> { in: DIS => (in.readShort() & MAX_16BIT).toLong }, 93 | MP_INT16 -> { in: DIS => in.readShort().toLong }, 94 | MP_UINT32 -> { in: DIS => in.readInt() & MAX_32BIT }, 95 | MP_INT32 -> { in: DIS => in.readInt().toLong }, 96 | MP_UINT64 -> { in: DIS => in.readLong() }, 97 | MP_INT64 -> { in: DIS => in.readLong() }, 98 | MP_UINT8 -> { in: DIS => in.readUnsignedByte().toLong }, 99 | MP_INT8 -> { in: DIS => in.readByte().toLong } 100 | ) ++ byteFuncMap.mapValues(_.andThen(_.toLong)) 101 | } 102 | 103 | implicit object FloatCodec extends Codec[Float] { 104 | def pack(out: DataOutputStream, item: Float) { 105 | out.write(MP_FLOAT) 106 | out.writeFloat(item) 107 | } 108 | 109 | val unpackFuncMap = FastByteMap[UnpackFunc]( 110 | MP_FLOAT -> { in: DIS => in.readFloat() } 111 | ) 112 | } 113 | 114 | implicit object DoubleCodec extends Codec[Double] { 115 | def pack(out: DataOutputStream, item: Double) { 116 | out.write(MP_DOUBLE) 117 | out.writeDouble(item) 118 | } 119 | 120 | val unpackFuncMap = FastByteMap[UnpackFunc]( 121 | MP_DOUBLE -> { in: DIS => in.readDouble() } 122 | ) 123 | } 124 | } 125 | 126 | /** 127 | * Codecs for newer MessagePack strings and raw bytes. 128 | * These codecs can also be used for older MessagePack messages if you did not encode raw byte values. 129 | */ 130 | object RawStringCodecs { 131 | import Format._ 132 | 133 | implicit object StringCodec extends Codec[String] { 134 | def pack(out: DataOutputStream, item: String) { packString(item, out) } 135 | val unpackFuncMap = FastByteMap[UnpackFunc]( 136 | MP_STR8 -> { in: DIS => unpackString(in.read(), in) }, 137 | MP_STR16 -> { in: DIS => unpackString(in.readShort() & MAX_16BIT, in) }, 138 | MP_STR32 -> { in: DIS => unpackString(in.readInt(), in) } 139 | ) ++ (0 to MAX_5BIT).map { strlen => 140 | (MP_FIXSTR | strlen).toByte -> { in: DIS => unpackString(strlen, in) } 141 | } 142 | } 143 | 144 | implicit object ByteArrayCodec extends Codec[Array[Byte]] { 145 | def pack(out: DataOutputStream, item: Array[Byte]) { packRawBytes(item, out) } 146 | val unpackFuncMap = FastByteMap[UnpackFunc]( 147 | MP_RAW8 -> { in: DIS => unpackByteArray(in.read(), in) }, 148 | MP_RAW16 -> { in: DIS => unpackByteArray(in.readShort() & MAX_16BIT, in) }, 149 | MP_RAW32 -> { in: DIS => unpackByteArray(in.readInt(), in) } 150 | ) 151 | } 152 | 153 | val allFuncMaps = StringCodec.unpackFuncMap.mapAnyFunc ++ 154 | ByteArrayCodec.unpackFuncMap.mapAnyFunc 155 | } 156 | 157 | /** 158 | * Codecs to read older MessagePack messages as raw bytes instead of strings. 159 | * Note: it cannot be used to write. 160 | */ 161 | object CompatibilityCodecs { 162 | import Format._ 163 | 164 | implicit object ByteArrayCodec extends Codec[Array[Byte]] { 165 | def pack(out: DataOutputStream, item: Array[Byte]) { ??? } 166 | val unpackFuncMap = FastByteMap[UnpackFunc]( 167 | MP_STR16 -> { in: DIS => unpackByteArray(in.readShort() & MAX_16BIT, in) }, 168 | MP_STR32 -> { in: DIS => unpackByteArray(in.readInt(), in) } 169 | ) ++ (0 to MAX_5BIT).map { strlen => 170 | (MP_FIXSTR | strlen).toByte -> { in: DIS => unpackByteArray(strlen, in) } 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * Codecs to read any value. 177 | * For Longs it is slightly different than the LongCodec because a UINT64 can return a BigInt 178 | * when the value is greater than Long.MaxValue 179 | */ 180 | object AnyCodecs { 181 | import Format._ 182 | import CollectionCodecs._ 183 | import SimpleCodecs._ 184 | 185 | /** 186 | * A codec for packing and unpacking to and from Any values. 187 | * NOTE: it has to be defined like this so that we can create default objects which refer to 188 | * themselves as the base K and V codecs 189 | * @param compatibilityMode true if want to read raw bytes from old messagepack STR values 190 | */ 191 | trait AnyCodecBase[K, V] extends Codec[Any] { 192 | def compatibilityMode: Boolean 193 | implicit def keyCodec: Codec[K] 194 | implicit def valCodec: Codec[V] 195 | val seqCodec = new SeqCodec[V] 196 | val mapCodec = new MapCodec[K, V] 197 | 198 | def pack(out: DataOutputStream, item: Any) { 199 | item match { 200 | case s: String => packString(s, out) 201 | case n: Number => n.asInstanceOf[Any] match { 202 | case f: Float => FloatCodec.pack(out, f) 203 | case d: Double => DoubleCodec.pack(out, d) 204 | case _ => packLong(n.longValue, out) 205 | } 206 | case map: Map[K, V] => mapCodec.pack(out, map) 207 | case s: Seq[V] => seqCodec.pack(out, s) 208 | case b: Array[Byte] => packRawBytes(b, out) 209 | case a: Array[V] => seqCodec.pack(out, a.toSeq) 210 | case bb: ByteBuffer => 211 | if (bb.hasArray()) 212 | packRawBytes(bb.array, out) 213 | else { 214 | val data = new Array[Byte](bb.capacity()) 215 | bb.position(); bb.limit(bb.capacity()) 216 | bb.get(data) 217 | packRawBytes(data, out) 218 | } 219 | case x: Boolean => BooleanCodec.pack(out, x) 220 | case null => out.write(MP_NULL) 221 | case item => 222 | throw new IllegalArgumentException("Cannot msgpack object of type " + item.getClass().getCanonicalName()) 223 | } 224 | } 225 | 226 | val unpackFuncMapBase = FastByteMap[UnpackFunc]( 227 | MP_NULL -> { in: DIS => null }, 228 | MP_UINT64 -> { in: DIS => // Return a bigInt, it could be > Long.MaxValue 229 | val v = in.readLong() 230 | if (v >= 0) v 231 | else 232 | //this is a little bit more tricky, since we don't have unsigned longs 233 | math.BigInt(1, Array[Byte](((v >> 24) & 0xff).toByte, 234 | ((v >> 16) & 0xff).toByte, 235 | ((v >> 8) & 0xff).toByte, (v & 0xff).toByte)) 236 | }, 237 | MP_INT64 -> { in: DIS => in.readLong() } 238 | ) ++ IntCodec.unpackFuncMap.mapAnyFunc ++ 239 | BooleanCodec.unpackFuncMap.mapAnyFunc ++ 240 | FloatCodec.unpackFuncMap.mapAnyFunc ++ 241 | DoubleCodec.unpackFuncMap.mapAnyFunc ++ 242 | seqCodec.unpackFuncMap.mapAnyFunc ++ 243 | mapCodec.unpackFuncMap.mapAnyFunc 244 | 245 | val unpackFuncMap = unpackFuncMapBase ++ { 246 | if (compatibilityMode) CompatibilityCodecs.ByteArrayCodec.unpackFuncMap.mapAnyFunc 247 | else RawStringCodecs.allFuncMaps } 248 | } 249 | 250 | /** 251 | * Use this class when the K and V are not Any, however it cannot be used to initialize itself 252 | */ 253 | class AnyCodec[K, V](val compatibilityMode: Boolean) 254 | (implicit val kCodec: Codec[K], 255 | implicit val vCodec: Codec[V]) extends AnyCodecBase[K, V] { 256 | implicit def keyCodec = kCodec 257 | implicit def valCodec = vCodec 258 | } 259 | 260 | 261 | /** 262 | * MUST USE def's in trait initialization otherwise you end up with nasty NPEs 263 | */ 264 | object DefaultAnyCodec extends AnyCodecBase[Any, Any] { 265 | def compatibilityMode = false 266 | implicit def keyCodec = this 267 | implicit def valCodec = this 268 | } 269 | 270 | object DefaultAnyCodecCompat extends AnyCodecBase[Any, Any] { 271 | def compatibilityMode = true 272 | implicit def keyCodec = this 273 | implicit def valCodec = this 274 | } 275 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/CollectionCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream} 4 | import scala.collection.{Map => CMap} 5 | 6 | object CollectionCodecs { 7 | import Format._ 8 | 9 | // NOTE: Arrays automatically get converted to Seqs via WrappedArray 10 | class SeqCodec[T: Codec] extends Codec[Seq[T]] { 11 | def pack(out: DataOutputStream, s: Seq[T]) { packSeq(s, out) } 12 | 13 | val unpackFuncMap = FastByteMap[UnpackFunc]( 14 | MP_ARRAY16 -> { in: DIS => unpackSeq(in.readShort() & MAX_16BIT, in) }, 15 | MP_ARRAY32 -> { in: DIS => unpackSeq(in.readInt(), in) } 16 | ) ++ (0 to MAX_4BIT).map { len => 17 | (MP_FIXARRAY | len).toByte -> { in: DIS => unpackSeq(len, in) } 18 | } 19 | } 20 | 21 | class MapCodec[K: Codec, V: Codec] extends Codec[Map[K, V]] { 22 | private val keyCodec = implicitly[Codec[K]] 23 | private val valCodec = implicitly[Codec[V]] 24 | 25 | def pack(out: DataOutputStream, m: Map[K, V]) { packMap(m, out) } 26 | 27 | val unpackFuncMap = FastByteMap[UnpackFunc]( 28 | MP_MAP16 -> { in: DIS => unpackMap(in.readShort() & MAX_16BIT, in)(keyCodec, valCodec) }, 29 | MP_MAP32 -> { in: DIS => unpackMap(in.readInt(), in)(keyCodec, valCodec) } 30 | ) ++ (0 to MAX_4BIT).map { len => 31 | (MP_FIXMAP | len).toByte -> { in: DIS => unpackMap(len, in)(keyCodec, valCodec) } 32 | } 33 | } 34 | 35 | class CMapCodec[K: Codec, V: Codec] extends Codec[CMap[K, V]] { 36 | private val keyCodec = implicitly[Codec[K]] 37 | private val valCodec = implicitly[Codec[V]] 38 | 39 | def pack(out: DataOutputStream, m: CMap[K, V]) { packMap(m, out) } 40 | 41 | // Unfortunately have to copy this, maybe can share via trait or something 42 | val unpackFuncMap = FastByteMap[UnpackFunc]( 43 | MP_MAP16 -> { in: DIS => unpackMap(in.readShort() & MAX_16BIT, in)(keyCodec, valCodec) }, 44 | MP_MAP32 -> { in: DIS => unpackMap(in.readInt(), in)(keyCodec, valCodec) } 45 | ) ++ (0 to MAX_4BIT).map { len => 46 | (MP_FIXMAP | len).toByte -> { in: DIS => unpackMap(len, in)(keyCodec, valCodec) } 47 | } 48 | } 49 | 50 | class SetCodec[T: Codec] extends Codec[Set[T]] { 51 | private val seqCodec = new SeqCodec[T] 52 | def pack(out: DataOutputStream, s: Set[T]): Unit = { 53 | seqCodec.pack(out, s.toSeq) 54 | } 55 | val unpackFuncMap = seqCodec.unpackFuncMap.mapValues(_.andThen(_.toSet)) 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/ExtraCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream} 4 | import java.math.{BigDecimal, BigInteger} 5 | 6 | object ExtraCodecs { 7 | import Format._ 8 | import SimpleCodecs._ 9 | import RawStringCodecs.ByteArrayCodec 10 | 11 | // BigDecimals are packed as an array of 2: [scale: Int, unscaledValue: ByteArray] 12 | implicit object BigDecimalCodec extends Codec[BigDecimal] { 13 | def pack(out: DataOutputStream, bd: BigDecimal) { 14 | out.write(0x2 | MP_FIXARRAY) 15 | packLong(bd.scale, out) 16 | packRawBytes(bd.unscaledValue.toByteArray, out) 17 | } 18 | val unpackFuncMap = FastByteMap[UnpackFunc]( 19 | (MP_FIXARRAY | 0x2).toByte -> { in: DIS => 20 | val scale = IntCodec.unpack(in) 21 | new BigDecimal(new java.math.BigInteger(ByteArrayCodec.unpack(in)), scale) 22 | } 23 | ) 24 | } 25 | 26 | // BigInteger is packed as a byte array 27 | implicit object BigIntegerCodec extends Codec[BigInteger] { 28 | def pack(out: DataOutputStream, item: BigInteger) { ByteArrayCodec.pack(out, item.toByteArray) } 29 | val unpackFuncMap = ByteArrayCodec.unpackFuncMap.mapValues(_.andThen(new BigInteger(_))) 30 | } 31 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/FastByteMap.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS} 4 | 5 | case class FastByteMap[V](things: (Byte, V)*) { 6 | val aray = new Array[Any](256) 7 | things.foreach { case (k, v) => aray(k & 0x00ff) = v } 8 | 9 | def ++(other: FastByteMap[V]): FastByteMap[V] = { 10 | val items = for { i <- 0 to 255 if other.aray(i) != null || aray(i) != null 11 | } yield { 12 | i.toByte -> { if (other.aray(i) != null) other.aray(i) else aray(i) } 13 | } 14 | FastByteMap[Any](items:_*).asInstanceOf[FastByteMap[V]] 15 | } 16 | 17 | def ++(items: Seq[(Byte, V)]): FastByteMap[V] = ++(FastByteMap[V](items:_*)) 18 | 19 | def mapValues[W](func: V => W): FastByteMap[W] = { 20 | val items = for { i <- 0 to 255 if aray(i) != null } yield { 21 | i.toByte -> func(aray(i).asInstanceOf[V]) 22 | } 23 | FastByteMap[Any](items:_*).asInstanceOf[FastByteMap[W]] 24 | } 25 | 26 | def mapAnyFunc = mapValues(_.asInstanceOf[DIS => Any]) 27 | def mapAs[V] = mapValues(_.asInstanceOf[DIS => V]) 28 | 29 | def getOrElse(b: Byte, default: => V): V = { 30 | val v = aray(b & 0x00ff) 31 | if (v != null) v.asInstanceOf[V] else default 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/org.velvia/msgpack/Format.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | import java.nio.ByteBuffer 6 | import org.velvia.InvalidMsgPackDataException 7 | 8 | /** 9 | * Encapsulates the MessagePack binary format, according to 10 | * https://github.com/msgpack/msgpack/blob/master/spec.md 11 | */ 12 | object Format { 13 | val MAX_4BIT = 0xf 14 | val MAX_5BIT = 0x1f 15 | val MAX_7BIT = 0x7f 16 | val MAX_8BIT = 0xff 17 | val MAX_15BIT = 0x7fff 18 | val MAX_16BIT = 0xffff 19 | val MAX_31BIT = 0x7fffffff 20 | val MAX_32BIT = 0xffffffffL 21 | 22 | //these values are from https://github.com/msgpack/msgpack/blob/master/spec.md 23 | val MP_NULL = 0xc0.toByte 24 | val MP_FALSE = 0xc2.toByte 25 | val MP_TRUE = 0xc3.toByte 26 | 27 | val MP_FLOAT = 0xca.toByte 28 | val MP_DOUBLE = 0xcb.toByte 29 | 30 | val MP_FIXNUM = 0x00.toByte //last 7 bits is value 31 | val MP_UINT8 = 0xcc.toByte 32 | val MP_UINT16 = 0xcd.toByte 33 | val MP_UINT32 = 0xce.toByte 34 | val MP_UINT64 = 0xcf.toByte 35 | 36 | val MP_NEGATIVE_FIXNUM = 0xe0.toByte //last 5 bits is value 37 | val MP_NEGATIVE_FIXNUM_INT = 0xe0 // /me wishes for signed numbers. 38 | val MP_INT8 = 0xd0.toByte 39 | val MP_INT16 = 0xd1.toByte 40 | val MP_INT32 = 0xd2.toByte 41 | val MP_INT64 = 0xd3.toByte 42 | 43 | val MP_FIXARRAY = 0x90.toByte //last 4 bits is size 44 | val MP_FIXARRAY_INT = 0x90 45 | val MP_ARRAY16 = 0xdc.toByte 46 | val MP_ARRAY32 = 0xdd.toByte 47 | 48 | val MP_FIXMAP = 0x80.toByte //last 4 bits is size 49 | val MP_FIXMAP_INT = 0x80 50 | val MP_MAP16 = 0xde.toByte 51 | val MP_MAP32 = 0xdf.toByte 52 | 53 | val MP_FIXSTR = 0xa0.toByte //last 5 bits is size 54 | val MP_FIXSTR_INT = 0xa0 55 | val MP_STR8 = 0xd9.toByte 56 | val MP_STR16 = 0xda.toByte 57 | val MP_STR32 = 0xdb.toByte 58 | 59 | // Raw bytes 60 | val MP_RAW8 = 0xc4.toByte 61 | val MP_RAW16 = 0xc5.toByte 62 | val MP_RAW32 = 0xc6.toByte 63 | 64 | def packRawBytes(data: Array[Byte], out: DataOutputStream) { 65 | if (data.length <= MAX_8BIT) { 66 | out.write(MP_RAW8) 67 | out.write(data.length) 68 | } else if (data.length <= MAX_16BIT) { 69 | out.write(MP_RAW16) 70 | out.writeShort(data.length) 71 | } else { 72 | out.write(MP_RAW32) 73 | out.writeInt(data.length) 74 | } 75 | out.write(data) 76 | } 77 | 78 | def packString(str: String, out: DataOutputStream) { 79 | val bytes = str.getBytes("UTF-8") 80 | if (bytes.length <= MAX_5BIT) { 81 | out.write(bytes.length | MP_FIXSTR) 82 | } else if (bytes.length <= MAX_8BIT) { 83 | out.write(MP_STR8) 84 | out.write(bytes.length) 85 | } else if (bytes.length <= MAX_16BIT) { 86 | out.write(MP_STR16) 87 | out.writeShort(bytes.length) 88 | } else { 89 | out.write(MP_STR32) 90 | out.writeInt(bytes.length) 91 | } 92 | out.write(bytes) 93 | } 94 | 95 | def packLong(value: Long, out: DataOutputStream) { 96 | if (value >= 0) { 97 | if (value <= MAX_7BIT) { 98 | out.write(value.toInt | MP_FIXNUM) 99 | } else if (value <= MAX_8BIT) { 100 | out.write(MP_UINT8) 101 | out.write(value.toInt) 102 | } else if (value <= MAX_16BIT) { 103 | out.write(MP_UINT16); 104 | out.writeShort(value.toInt) 105 | } else if (value <= MAX_32BIT) { 106 | out.write(MP_UINT32) 107 | out.writeInt(value.toInt) 108 | } else { 109 | out.write(MP_UINT64) 110 | out.writeLong(value) 111 | } 112 | } else { 113 | if (value >= -(MAX_5BIT + 1)) { 114 | out.write((value & 0xff).toInt) 115 | } else if (value >= -(MAX_7BIT + 1)) { 116 | out.write(MP_INT8) 117 | out.write(value.toInt) 118 | } else if (value >= -(MAX_15BIT + 1)) { 119 | out.write(MP_INT16) 120 | out.writeShort(value.toInt) 121 | } else if (value >= -(MAX_31BIT + 1)) { 122 | out.write(MP_INT32) 123 | out.writeInt(value.toInt) 124 | } else { 125 | out.write(MP_INT64) 126 | out.writeLong(value) 127 | } 128 | } 129 | } 130 | 131 | def packSeq[T: Codec](s: Seq[T], out: DataOutputStream) { 132 | val packer = implicitly[Codec[T]] 133 | if (s.length <= MAX_4BIT) { 134 | out.write(s.length | MP_FIXARRAY) 135 | } else if (s.length <= MAX_16BIT) { 136 | out.write(MP_ARRAY16) 137 | out.writeShort(s.length) 138 | } else { 139 | out.write(MP_ARRAY32) 140 | out.writeInt(s.length) 141 | } 142 | s foreach { packer.pack(out, _) } 143 | } 144 | 145 | def packMap[K: Codec, V: Codec](map: collection.Map[K, V], out: DataOutputStream) { 146 | val keyCodec = implicitly[Codec[K]] 147 | val valCodec = implicitly[Codec[V]] 148 | writeMapHeader(map.size, out) 149 | map foreach { case (k, v) => keyCodec.pack(out, k); valCodec.pack(out, v) } 150 | } 151 | 152 | def packMapSeq[K: Codec, V: Codec](map: Seq[(K, V)], out: DataOutputStream) { 153 | val keyCodec = implicitly[Codec[K]] 154 | val valCodec = implicitly[Codec[V]] 155 | writeMapHeader(map.size, out) 156 | map foreach { case (k, v) => keyCodec.pack(out, k); valCodec.pack(out, v) } 157 | } 158 | 159 | private def writeMapHeader(mapSize: Int, out: DataOutputStream) { 160 | if (mapSize <= MAX_4BIT) { 161 | out.write(mapSize | MP_FIXMAP) 162 | } else if (mapSize <= MAX_16BIT) { 163 | out.write(MP_MAP16) 164 | out.writeShort(mapSize) 165 | } else { 166 | out.write(MP_MAP32) 167 | out.writeInt(mapSize) 168 | } 169 | } 170 | 171 | val UNPACK_RAW_AS_STRING = 0x1 172 | val UNPACK_RAW_AS_BYTE_BUFFER = 0x2 173 | 174 | // NOTE: MessagePack format used to pack raw bytes and strings with the same message format 175 | // header (0xda/b, 0xa0-0xbf). If you want compatibility with old MessagePack messages, 176 | // you should call this with the appropriate flag. 177 | def unpackRaw(size: Int, in: DataInputStream, options: Int): Any = { 178 | if (size < 0) 179 | throw new InvalidMsgPackDataException("byte[] to unpack too large for Java (more than 2^31 elements)!") 180 | 181 | val data = new Array[Byte](size) 182 | // Don't use the standard read() method, it's not guaranteed to read back all the bytes! 183 | in.readFully(data) 184 | 185 | if ((options & UNPACK_RAW_AS_BYTE_BUFFER) != 0) { 186 | ByteBuffer.wrap(data) 187 | } else if ((options & UNPACK_RAW_AS_STRING) != 0) { 188 | new String(data, "UTF-8") 189 | } else data 190 | } 191 | 192 | def unpackString(size: Int, in: DataInputStream): String = 193 | unpackRaw(size, in, UNPACK_RAW_AS_STRING).asInstanceOf[String] 194 | 195 | def unpackByteArray(size: Int, in: DataInputStream): Array[Byte] = 196 | unpackRaw(size, in, 0).asInstanceOf[Array[Byte]] 197 | 198 | def unpackSeq[T: Codec](size: Int, in: DataInputStream): Seq[T] = { 199 | val unpacker = implicitly[Codec[T]] 200 | if (size < 0) 201 | throw new InvalidMsgPackDataException("Array to unpack too large for Java (more than 2^31 elements)!") 202 | val vec = Vector.newBuilder[T] 203 | var i = 0 204 | while (i < size) { 205 | vec += unpacker.unpack(in) 206 | i += 1 207 | } 208 | vec.result 209 | } 210 | 211 | def unpackMap[K: Codec, V: Codec](size: Int, in: DataInputStream): Map[K, V] = { 212 | val keyCodec = implicitly[Codec[K]] 213 | val valCodec = implicitly[Codec[V]] 214 | if (size < 0) 215 | throw new InvalidMsgPackDataException("Map to unpack too large for Java (more than 2^31 elements)!") 216 | var map = Map.newBuilder[K, V] 217 | for { i <- 0 until size } { 218 | map += keyCodec.unpack(in) -> valCodec.unpack(in) 219 | } 220 | map.result 221 | } 222 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/Json4sCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream} 4 | import org.json4s.JsonAST._ 5 | 6 | /** 7 | * Codecs for serializing between MessagePack and Json4s AST 8 | */ 9 | object Json4sCodecs { 10 | import Format._ 11 | import SimpleCodecs._ 12 | import RawStringCodecs.StringCodec 13 | import ExtraCodecs._ 14 | 15 | implicit object JBoolCodec extends Codec[JBool] { 16 | def pack(out: DataOutputStream, item: JBool) { BooleanCodec.pack(out, item.value) } 17 | val unpackFuncMap = BooleanCodec.unpackFuncMap.mapValues(_.andThen(JBool(_))) 18 | } 19 | 20 | implicit object JStringCodec extends Codec[JString] { 21 | def pack(out: DataOutputStream, item: JString) { StringCodec.pack(out, item.s) } 22 | val unpackFuncMap = StringCodec.unpackFuncMap.mapValues(_.andThen(JString(_))) 23 | } 24 | 25 | implicit object JDecimalCodec extends Codec[JDecimal] { 26 | def pack(out: DataOutputStream, item: JDecimal) { BigDecimalCodec.pack(out, item.num.underlying) } 27 | val unpackFuncMap = BigDecimalCodec.unpackFuncMap.mapValues(_.andThen(b => JDecimal(BigDecimal(b)))) 28 | } 29 | 30 | implicit object JDoubleCodec extends Codec[JDouble] { 31 | def pack(out: DataOutputStream, item: JDouble) { DoubleCodec.pack(out, item.num) } 32 | val unpackFuncMap = DoubleCodec.unpackFuncMap.mapValues(_.andThen(JDouble(_))) 33 | } 34 | 35 | implicit object JIntCodec extends Codec[JInt] { 36 | def pack(out: DataOutputStream, item: JInt) { BigIntegerCodec.pack(out, item.num.underlying) } 37 | val unpackFuncMap = BigIntegerCodec.unpackFuncMap.mapValues(_.andThen(JInt(_))) 38 | } 39 | 40 | implicit object JLongCodec extends Codec[JLong] { 41 | def pack(out: DataOutputStream, item: JLong) { LongCodec.pack(out, item.num) } 42 | val unpackFuncMap = LongCodec.unpackFuncMap.mapValues(_.andThen(JLong(_))) 43 | } 44 | 45 | implicit object JArrayCodec extends Codec[JArray] { 46 | lazy val seqCodec = new CollectionCodecs.SeqCodec()(JValueCodec) 47 | def pack(out: DataOutputStream, a: JArray) { seqCodec.pack(out, a.arr) } 48 | lazy val unpackFuncMap = seqCodec.unpackFuncMap.mapValues(_.andThen(s => JArray(s.toList))) 49 | } 50 | 51 | implicit object JSetCodec extends Codec[JSet] { 52 | lazy val seqCodec = new CollectionCodecs.SeqCodec()(JValueCodec) 53 | def pack(out: DataOutputStream, a: JSet) { seqCodec.pack(out, a.set.toSeq) } 54 | lazy val unpackFuncMap = seqCodec.unpackFuncMap.mapValues(_.andThen(s => JSet(s.toSet))) 55 | } 56 | 57 | implicit object JObjectCodec extends Codec[JObject] { 58 | lazy val mapCodec = new CollectionCodecs.MapCodec()(StringCodec, JValueCodec) 59 | def pack(out: DataOutputStream, m: JObject) { packMapSeq[String, JValue](m.obj, out) } 60 | lazy val unpackFuncMap = mapCodec.unpackFuncMap.mapValues(_.andThen(m => JObject(m.toList))) 61 | } 62 | 63 | implicit object JValueCodec extends Codec[JValue] { 64 | def pack(out: DataOutputStream, item: JValue) { 65 | item match { 66 | case j: JString => JStringCodec.pack(out, j) 67 | case j: JObject => JObjectCodec.pack(out, j) 68 | case j: JDouble => JDoubleCodec.pack(out, j) 69 | case j: JInt => JIntCodec.pack(out, j) 70 | case j: JLong => JLongCodec.pack(out, j) 71 | case j: JSet => JSetCodec.pack(out, j) 72 | case j: JArray => JArrayCodec.pack(out, j) 73 | case j: JDecimal => JDecimalCodec.pack(out, j) 74 | case j: JBool => JBoolCodec.pack(out, j) 75 | case JNull | JNothing => out.write(MP_NULL) 76 | } 77 | } 78 | val unpackFuncMap = FastByteMap[UnpackFunc]( 79 | MP_NULL -> { in: DIS => JNull } 80 | ) ++ 81 | JBoolCodec.unpackFuncMap.mapAs[JValue] ++ 82 | JStringCodec.unpackFuncMap.mapAs[JValue] ++ 83 | JDecimalCodec.unpackFuncMap.mapAs[JValue] ++ 84 | JDoubleCodec.unpackFuncMap.mapAs[JValue] ++ 85 | JIntCodec.unpackFuncMap.mapAs[JValue] ++ 86 | JLongCodec.unpackFuncMap.mapAs[JValue] ++ 87 | JSetCodec.unpackFuncMap.mapAs[JValue] ++ 88 | JArrayCodec.unpackFuncMap.mapAs[JValue] ++ 89 | JObjectCodec.unpackFuncMap.mapAs[JValue] 90 | } 91 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/PlayJsonCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{ DataInputStream ⇒ DIS, DataOutputStream } 4 | 5 | import play.api.libs.json._ 6 | import org.velvia.msgpack._ 7 | 8 | object PlayJsonCodecs { 9 | import org.velvia.msgpack.Format._ 10 | import SimpleCodecs._ 11 | import RawStringCodecs.StringCodec 12 | import ExtraCodecs._ 13 | 14 | implicit object JsNullCodec extends Codec[JsNull.type] { 15 | def pack(out: DataOutputStream, item: JsNull.type): Unit = { out.write(MP_NULL) } 16 | val unpackFuncMap = FastByteMap[UnpackFunc]( 17 | MP_NULL -> { in: DIS ⇒ JsNull }) 18 | } 19 | 20 | implicit object JsBooleanCodec extends Codec[JsBoolean] { 21 | def pack(out: DataOutputStream, item: JsBoolean): Unit = { BooleanCodec.pack(out, item.value) } 22 | val unpackFuncMap: FastByteMap[(DIS) => JsBoolean] = BooleanCodec.unpackFuncMap.mapValues(_.andThen(JsBoolean(_))) 23 | } 24 | 25 | implicit object JsStringCodec extends Codec[JsString] { 26 | def pack(out: DataOutputStream, item: JsString): Unit = { StringCodec.pack(out, item.value) } 27 | val unpackFuncMap = StringCodec.unpackFuncMap.mapValues(_.andThen(JsString)) 28 | } 29 | 30 | implicit object JsNumberCodec extends Codec[JsNumber] { 31 | def pack(out: DataOutputStream, item: JsNumber): Unit = { BigDecimalCodec.pack(out, item.value.underlying) } 32 | val unpackFuncMap = BigDecimalCodec.unpackFuncMap.mapValues(_.andThen(b ⇒ JsNumber(BigDecimal(b)))) 33 | } 34 | 35 | implicit object JsArrayCodec extends Codec[JsArray] { 36 | lazy val seqCodec = new CollectionCodecs.SeqCodec()(JsValueCodec) 37 | def pack(out: DataOutputStream, a: JsArray): Unit = { seqCodec.pack(out, a.value) } 38 | lazy val unpackFuncMap = seqCodec 39 | .unpackFuncMap 40 | .mapValues(_.andThen(JsArray)) 41 | } 42 | 43 | implicit object JsObjectCodec extends Codec[JsObject] { 44 | lazy val mapCodec = new CollectionCodecs.CMapCodec()(StringCodec, JsValueCodec) 45 | def pack(out: DataOutputStream, m: JsObject): Unit = { mapCodec.pack(out, m.value) } 46 | lazy val unpackFuncMap = mapCodec.unpackFuncMap.mapValues(_.andThen(m ⇒ JsObject(m.toList))) 47 | } 48 | 49 | implicit object JsValueCodec extends Codec[JsValue] { 50 | def pack(out: DataOutputStream, item: JsValue): Unit = { 51 | item match { 52 | case j: JsString ⇒ JsStringCodec.pack(out, j) 53 | case j: JsNumber ⇒ JsNumberCodec.pack(out, j) 54 | case j: JsBoolean ⇒ JsBooleanCodec.pack(out, j) 55 | case j: JsObject ⇒ JsObjectCodec.pack(out, j) 56 | case j: JsArray ⇒ JsArrayCodec.pack(out, j) 57 | case j: JsNull.type ⇒ JsNullCodec.pack(out, j) 58 | } 59 | } 60 | 61 | val unpackFuncMap = 62 | JsNullCodec.unpackFuncMap.mapAs[JsValue] ++ 63 | JsBooleanCodec.unpackFuncMap.mapAs[JsValue] ++ 64 | JsStringCodec.unpackFuncMap.mapAs[JsValue] ++ 65 | JsNumberCodec.unpackFuncMap.mapAs[JsValue] ++ 66 | JsObjectCodec.unpackFuncMap.mapAs[JsValue] ++ 67 | JsArrayCodec.unpackFuncMap.mapAs[JsValue] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/org.velvia/msgpack/RojomaJsonCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import com.rojoma.json.v3.ast._ 4 | import java.io.{DataInputStream => DIS, DataOutputStream} 5 | 6 | /** 7 | * Codecs for serializing between MessagePack and rojoma-json AST 8 | */ 9 | object RojomaJsonCodecs { 10 | import Format._ 11 | import SimpleCodecs._ 12 | import RawStringCodecs.StringCodec 13 | import ExtraCodecs.BigDecimalCodec 14 | 15 | implicit object JNullCodec extends Codec[JNull] { 16 | def pack(out: DataOutputStream, item: JNull) { out.write(MP_NULL) } 17 | val unpackFuncMap = FastByteMap[UnpackFunc]( 18 | MP_NULL -> { in: DIS => JNull } 19 | ) 20 | } 21 | 22 | implicit object JBooleanCodec extends Codec[JBoolean] { 23 | def pack(out: DataOutputStream, item: JBoolean) { BooleanCodec.pack(out, item.boolean) } 24 | val unpackFuncMap = BooleanCodec.unpackFuncMap.mapValues(_.andThen(JBoolean(_))) 25 | } 26 | 27 | implicit object JStringCodec extends Codec[JString] { 28 | def pack(out: DataOutputStream, item: JString) { StringCodec.pack(out, item.string) } 29 | val unpackFuncMap = StringCodec.unpackFuncMap.mapValues(_.andThen(JString(_))) 30 | } 31 | 32 | implicit object JNumberCodec extends Codec[JNumber] { 33 | def pack(out: DataOutputStream, item: JNumber) { BigDecimalCodec.pack(out, item.toJBigDecimal) } 34 | val unpackFuncMap = BigDecimalCodec.unpackFuncMap.mapValues(_.andThen(JNumber(_))) 35 | } 36 | 37 | implicit object JArrayCodec extends Codec[JArray] { 38 | lazy val seqCodec = new CollectionCodecs.SeqCodec()(JValueCodec) 39 | def pack(out: DataOutputStream, a: JArray) { seqCodec.pack(out, a.toSeq) } 40 | lazy val unpackFuncMap = seqCodec.unpackFuncMap.mapValues(_.andThen(JArray(_))) 41 | } 42 | 43 | implicit object JObjectCodec extends Codec[JObject] { 44 | lazy val mapCodec = new CollectionCodecs.CMapCodec()(StringCodec, JValueCodec) 45 | def pack(out: DataOutputStream, m: JObject) { mapCodec.pack(out, m.fields) } 46 | lazy val unpackFuncMap = mapCodec.unpackFuncMap.mapValues(_.andThen(JObject(_))) 47 | } 48 | 49 | implicit object JValueCodec extends Codec[JValue] { 50 | def pack(out: DataOutputStream, item: JValue) { 51 | item match { 52 | case j: JString => JStringCodec.pack(out, j) 53 | case j: JObject => JObjectCodec.pack(out, j) 54 | case j: JNumber => JNumberCodec.pack(out, j) 55 | case j: JArray => JArrayCodec.pack(out, j) 56 | case j: JBoolean => JBooleanCodec.pack(out, j) 57 | case j: JNull => JNullCodec.pack(out, j) 58 | } 59 | } 60 | val unpackFuncMap = JNullCodec.unpackFuncMap.mapAs[JValue] ++ 61 | JBooleanCodec.unpackFuncMap.mapAs[JValue] ++ 62 | JStringCodec.unpackFuncMap.mapAs[JValue] ++ 63 | JNumberCodec.unpackFuncMap.mapAs[JValue] ++ 64 | JArrayCodec.unpackFuncMap.mapAs[JValue] ++ 65 | JObjectCodec.unpackFuncMap.mapAs[JValue] 66 | } 67 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/TransformCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.DataOutputStream 4 | 5 | 6 | object TransformCodecs { 7 | 8 | /** Defines a codec for `A` given one for `B` and a bijection `A` <=> `B`. 9 | * 10 | * An example would be (de)serializing a `Date` by storing its timestamp, 11 | * which is just a `Long`. This would look like: 12 | * 13 | * import java.util.Date 14 | * val c: Codec[Date] = new TransformCodec[Date, Long](_.getTime, new Date(_)) 15 | */ 16 | class TransformCodec[A, B: Codec]( 17 | forward: A => B, 18 | backward: B => A 19 | ) extends Codec[A] { 20 | 21 | private val codec1 = implicitly[Codec[B]] 22 | 23 | override def pack(out: DataOutputStream, item: A): Unit = 24 | codec1.pack(out, forward(item)) 25 | 26 | val unpackFuncMap = { 27 | val things = codec1.unpackFuncMap.things map { case (byte, func) => 28 | byte -> func.andThen(backward) 29 | } 30 | 31 | FastByteMap[UnpackFunc](things: _*) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/org.velvia/msgpack/TupleCodecs.scala: -------------------------------------------------------------------------------- 1 | package org.velvia.msgpack 2 | 3 | import java.io.{DataInputStream => DIS, DataOutputStream} 4 | 5 | object TupleCodecs { 6 | class TupleCodec2[A1: Codec, A2: Codec] extends Codec[(A1, A2)] { 7 | 8 | private val codec1 = implicitly[Codec[A1]] 9 | private val codec2 = implicitly[Codec[A2]] 10 | 11 | def pack(out: DataOutputStream, item: (A1, A2)): Unit = { 12 | out.write(0x02 | Format.MP_FIXARRAY) 13 | codec1.pack(out, item._1) 14 | codec2.pack(out, item._2) 15 | } 16 | 17 | val unpackFuncMap = FastByteMap[UnpackFunc]( 18 | (0x02 | Format.MP_FIXARRAY).toByte -> { in: DIS => 19 | val r1 = codec1.unpack(in) 20 | val r2 = codec2.unpack(in) 21 | (r1, r2) 22 | } 23 | ) 24 | } 25 | 26 | class TupleCodec3[A1: Codec, A2: Codec, A3: Codec] 27 | extends Codec[(A1, A2, A3)] { 28 | 29 | private val codec1 = implicitly[Codec[A1]] 30 | private val codec2 = implicitly[Codec[A2]] 31 | private val codec3 = implicitly[Codec[A3]] 32 | 33 | def pack(out: DataOutputStream, item: (A1, A2, A3)): Unit = { 34 | out.write(0x03 | Format.MP_FIXARRAY) 35 | codec1.pack(out, item._1) 36 | codec2.pack(out, item._2) 37 | codec3.pack(out, item._3) 38 | } 39 | 40 | val unpackFuncMap = FastByteMap[UnpackFunc]( 41 | (0x03 | Format.MP_FIXARRAY).toByte -> { in: DIS => 42 | val r1 = codec1.unpack(in) 43 | val r2 = codec2.unpack(in) 44 | val r3 = codec3.unpack(in) 45 | (r1, r2, r3) 46 | } 47 | ) 48 | } 49 | 50 | class TupleCodec4[A1: Codec, A2: Codec, A3: Codec, A4: Codec] 51 | extends Codec[(A1, A2, A3, A4)] { 52 | 53 | private val codec1 = implicitly[Codec[A1]] 54 | private val codec2 = implicitly[Codec[A2]] 55 | private val codec3 = implicitly[Codec[A3]] 56 | private val codec4 = implicitly[Codec[A4]] 57 | 58 | def pack(out: DataOutputStream, item: (A1, A2, A3, A4)): Unit = { 59 | out.write(0x04 | Format.MP_FIXARRAY) 60 | codec1.pack(out, item._1) 61 | codec2.pack(out, item._2) 62 | codec3.pack(out, item._3) 63 | codec4.pack(out, item._4) 64 | } 65 | 66 | val unpackFuncMap = FastByteMap[UnpackFunc]( 67 | (0x04 | Format.MP_FIXARRAY).toByte -> { in: DIS => 68 | val r1 = codec1.unpack(in) 69 | val r2 = codec2.unpack(in) 70 | val r3 = codec3.unpack(in) 71 | val r4 = codec4.unpack(in) 72 | (r1, r2, r3, r4) 73 | } 74 | ) 75 | } 76 | 77 | // TODO: add TupleCodec5, TupleCodec6 ... and so on 78 | } 79 | -------------------------------------------------------------------------------- /src/org.velvia/package.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import java.io.{DataInputStream, DataOutputStream, ByteArrayInputStream, ByteArrayOutputStream} 4 | import java.io.IOException 5 | import org.velvia.msgpack.Codec 6 | 7 | /** 8 | * Contains newer typeclass-based APIs 9 | */ 10 | package object msgpack { 11 | def pack[A: Codec](item: A, out: DataOutputStream) { implicitly[Codec[A]].pack(out, item) } 12 | def unpack[A: Codec](in: DataInputStream): A = { implicitly[Codec[A]].unpack(in) } 13 | 14 | /** 15 | * Packs an item using the msgpack protocol and codec typeclasses 16 | * 17 | * Warning: this does not do any recursion checks. If you pass a cyclic object, 18 | * you will run in an infinite loop until you run out of memory. 19 | * 20 | * NOTE: Please do not use this method for performance sensitive apps, but use the streaming 21 | * API instead. ByteArrayOutputStream is known to be very slow. Anyways if you care about 22 | * lots of data you would be streaming, right? right? :) 23 | * 24 | * @param item the item to serialize 25 | * @param initSize the default initial size of the ByteArray buffer. If you write 26 | * a large object this must be raised or else your app will spend lots of time 27 | * growing the ByteArray. 28 | * @return the packed data 29 | * @throws UnpackableItemException If the given data cannot be packed. 30 | */ 31 | def pack[A: Codec](item: A, initSize: Int = 512): Array[Byte] = { 32 | val out = new ByteArrayOutputStream(initSize) 33 | try { 34 | pack(item, new DataOutputStream(out)) 35 | } catch { 36 | case e: IOException => 37 | //this shouldn't happen 38 | throw new RuntimeException("ByteArrayOutputStream threw an IOException!", e) 39 | } 40 | out.toByteArray() 41 | } 42 | 43 | /** 44 | * Unpacks the given data using a typeclass 45 | * 46 | * @param data the byte array to unpack 47 | * @return the unpacked data 48 | * @throws InvalidMsgPackDataException If the given data cannot be unpacked. 49 | */ 50 | def unpack[A: Codec](data: Array[Byte]): A = { 51 | val in = new ByteArrayInputStream(data) 52 | try { 53 | unpack(new DataInputStream(in)) 54 | } catch { 55 | case ex: InvalidMsgPackDataException => throw ex 56 | case ex: IOException => //this shouldn't happen 57 | throw new RuntimeException("ByteArrayInStream threw an IOException!", ex); 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /test/org.velvia/MsgPackSpec.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import util.Random 4 | import org.scalatest.FunSpec 5 | import org.scalatest.Matchers 6 | 7 | class MsgPackSpec extends FunSpec with Matchers { 8 | describe("MsgPack pack and unpack") { 9 | it("should serialize and deserialize integers") { 10 | val numbers = Seq(0, 2, -3, 31, 32, 33, -32, -33, 127, 128, 129, -127, -128, -129, 11 | 255, 256, 257, -255, -256, -257, 12 | 0xfffe, 0xffff, 0x10000, -0xffff, -0x10000, 0xffffffff, 0x100000000L) 13 | numbers foreach { n => 14 | val packed = MsgPack.pack(n) 15 | MsgPack.unpack(packed) should equal (n) 16 | } 17 | } 18 | 19 | it("should serialize and deserialize floats and doubles") { 20 | val numbers = Seq[Number]( 21 | 0.0, 1.0, -1.0, 22 | 0.0f, 1.0f, -1.0f, 23 | Double.NaN, Double.NegativeInfinity, Double.PositiveInfinity, 24 | Double.MinValue, Double.MaxValue, Double.MinPositiveValue, 25 | Float.NaN, Float.NegativeInfinity, Float.PositiveInfinity, 26 | Float.MinValue, Float.MaxValue, Float.MinPositiveValue) 27 | numbers foreach { n => 28 | val unpacked = MsgPack.unpack(MsgPack.pack(n)) 29 | assert(unpacked.equals(n)) /// For some reason, should equal or === does not work 30 | } 31 | } 32 | 33 | it("should serialize and deserialize strings") { 34 | MsgPack.unpack(MsgPack.pack("abcDEF")) should equal ("abcDEF") 35 | } 36 | 37 | it("should deserialize older MsgPack messages as raw bytes in compatibility mode") { 38 | val bais = new java.io.ByteArrayInputStream(MsgPack.pack("abcDEF")) 39 | MsgPack.unpack(new java.io.DataInputStream(bais), true) should equal ("abcDEF".getBytes("UTF-8")) 40 | } 41 | 42 | it("should serialize and deserialize Bools, null") { 43 | MsgPack.unpack(MsgPack.pack(true)) should equal (true) 44 | MsgPack.unpack(MsgPack.pack(false)) should equal (false) 45 | MsgPack.unpack(MsgPack.pack(null)) should equal (null.asInstanceOf[AnyRef]) 46 | } 47 | 48 | it("should serialize and deserialize sequences") { 49 | val lengths = Seq(1, 2, 15, 16, 31, 32, 0xffff, 0x10000) 50 | lengths foreach { len => 51 | val list = Seq.fill(len)(Random.nextInt(100)) 52 | val unpacked = MsgPack.unpack(MsgPack.pack(list)).asInstanceOf[Seq[Int]] 53 | unpacked.getClass should equal (classOf[Vector[Int]]) 54 | unpacked should have length (len) 55 | unpacked should equal (list) 56 | } 57 | } 58 | 59 | it("should serialize and deserialize arrays, Nil, mixed sequences") { 60 | val array = Array(1, 2, -3) 61 | val unpacked = MsgPack.unpack(MsgPack.pack(array)).asInstanceOf[Seq[Any]] 62 | unpacked.getClass should equal (classOf[Vector[Int]]) 63 | unpacked should have length (array.size) 64 | assert(unpacked.sameElements(array)) 65 | 66 | val nil = Nil 67 | val unpacked1 = MsgPack.unpack(MsgPack.pack(nil)).asInstanceOf[Seq[Any]] 68 | unpacked1 should have length (0) 69 | 70 | val vec = Vector("a", 4, true, null) 71 | val unpacked2 = MsgPack.unpack(MsgPack.pack(vec)).asInstanceOf[Seq[Any]] 72 | unpacked2 should have length (vec.length) 73 | unpacked2 should equal (vec) 74 | } 75 | 76 | it("should serialize and deserialize Maps") { 77 | val map = Map("type" -> 9, "owner" -> "ev", "stats" -> Map(29 -> 1, "those" -> Seq(1, 2))) 78 | val unpacked = MsgPack.unpack(MsgPack.pack(map)).asInstanceOf[Map[_, _]] 79 | unpacked should equal (map) 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /test/org.velvia/MsgPackTypeClassSpec.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import java.math.{BigDecimal, BigInteger} 4 | import org.scalatest.FunSpec 5 | import org.scalatest.Matchers 6 | 7 | import scala.util.Random 8 | 9 | class MsgPackTypeClassSpec extends FunSpec with Matchers { 10 | import org.velvia.msgpack._ 11 | import org.velvia.msgpack.SimpleCodecs._ 12 | import org.velvia.msgpack.RawStringCodecs._ 13 | 14 | describe("basic types unpacking and packing") { 15 | it("should unpack and pack bool values") { 16 | unpack[Boolean](pack(true)) should equal (true) 17 | unpack[Boolean](pack(false)) should equal (false) 18 | } 19 | 20 | it("should unpack and pack primitive numbers") { 21 | unpack[Int](pack(Int.MaxValue)) should equal (Int.MaxValue) 22 | unpack[Int](pack(-1)) should equal (-1) 23 | unpack[Int](pack(-33)) should equal (-33) 24 | 25 | unpack[Byte](pack(-34.toByte)) should equal (-34.toByte) 26 | unpack[Short](pack(128.toShort)) should equal (128.toShort) 27 | 28 | unpack[Long](pack(-35L)) should equal (-35L) 29 | unpack[Long](pack(-1000L)) should equal (-1000L) 30 | } 31 | 32 | it("should unpack and pack floats and doubles") { 33 | unpack[Float](pack(3.141F)) should equal (3.141F) 34 | unpack[Double](pack(Math.E)) should equal (Math.E) 35 | } 36 | 37 | it("should unpack and pack strings") { 38 | unpack[String](pack("abcde")) should equal ("abcde") 39 | val longerStr = "The quick brown fox jumped over the lazy fence" 40 | unpack[String](pack(longerStr)) should equal (longerStr) 41 | } 42 | 43 | it("should unpack and pack Unicode strings") { 44 | val korean = "안녕" 45 | unpack[String](pack(korean)) should equal (korean) 46 | } 47 | } 48 | 49 | describe("collections packing and unpacking") { 50 | import org.velvia.msgpack.CollectionCodecs._ 51 | 52 | val intSeqCodec = new SeqCodec[Int] 53 | val strIntMapCodec = new MapCodec[String, Int] 54 | val intSetCodec = new SetCodec[Int] 55 | 56 | it("should pack and unpack Seqs and Arrays") { 57 | val seq1 = Seq(1, 2, 3, 4, 5) 58 | unpack(pack(seq1)(intSeqCodec))(intSeqCodec) should equal (seq1) 59 | 60 | val array = (-3 to 24).toArray // Force to be longer than 16 values 61 | unpack(pack(array.toSeq)(intSeqCodec))(intSeqCodec) should equal (array.toSeq) 62 | } 63 | 64 | it("should pack and unpack Maps") { 65 | val map = Map("apples" -> 1, "bears" -> -5, "oranges" -> 100) 66 | unpack(pack(map)(strIntMapCodec))(strIntMapCodec) should equal (map) 67 | } 68 | 69 | it("should pack and unpack Sets") { 70 | val set = Set(1, 2, 3, 4, 5) 71 | unpack(pack(set)(intSetCodec))(intSetCodec) should equal (set) 72 | } 73 | } 74 | 75 | describe("extras packing and unpacking") { 76 | import org.velvia.msgpack.ExtraCodecs._ 77 | 78 | it("should pack and unpack BigDecimal") { 79 | unpack[BigDecimal](pack(new BigDecimal(1000))) should equal (new BigDecimal(1000)) 80 | val bdPi = new BigDecimal(Math.PI) 81 | unpack[BigDecimal](pack(bdPi)) should equal (bdPi) 82 | } 83 | 84 | it("should pack and unpack BigInteger") { 85 | unpack[BigInteger](pack(new BigInteger("1000"))) should equal (new BigInteger("1000")) 86 | } 87 | } 88 | 89 | describe("Json4S AST packing and unpacking") { 90 | import org.json4s.native.JsonMethods._ 91 | import org.json4s._ 92 | import org.json4s.JsonAST._ 93 | import msgpack.Json4sCodecs._ 94 | 95 | val aray = parse("""[1, 2.5, null, "Streater"]""") 96 | val map = parse("""{"bool": true, "aray": [3, -4], "map": {"inner": "me"}}""") 97 | 98 | it("should pack and unpack Json4S AST") { 99 | unpack[JArray](pack(aray)) should equal (aray) 100 | unpack[JValue](pack(aray)) should equal (aray) 101 | unpack[JValue](pack(map)) should equal (map) 102 | } 103 | } 104 | 105 | describe("tuple packing and unpacking") { 106 | import org.velvia.msgpack.TupleCodecs._ 107 | 108 | it("should pack and unpack Tuple2") { 109 | val codec2 = new TupleCodec2[Int, Int] 110 | val tuple2 = (Random.nextInt(), Random.nextInt()) 111 | val unpacked2 = unpack(pack(tuple2)(codec2))(codec2) 112 | unpacked2.getClass should equal (classOf[(Int, Int)]) 113 | unpacked2 should equal (tuple2) 114 | } 115 | 116 | it("should pack and unpack Tuple3") { 117 | val codec3 = new TupleCodec3[Int, Int, Int] 118 | val tuple3 = (Random.nextInt(), Random.nextInt(), Random.nextInt()) 119 | val unpacked3 = unpack(pack(tuple3)(codec3))(codec3) 120 | unpacked3.getClass should equal (classOf[(Int, Int, Int)]) 121 | unpacked3 should equal (tuple3) 122 | } 123 | 124 | it("should pack and unpack Tuple4") { 125 | val codec4 = new TupleCodec4[Int, Int, Int, Int] 126 | val tuple4 = (Random.nextInt(), Random.nextInt(), Random.nextInt(), Random.nextInt()) 127 | val unpacked4 = unpack(pack(tuple4)(codec4))(codec4) 128 | unpacked4.getClass should equal (classOf[(Int, Int, Int, Int)]) 129 | unpacked4 should equal (tuple4) 130 | } 131 | } 132 | 133 | describe("case class packing and unpacking") { 134 | import org.velvia.msgpack.CaseClassCodecs._ 135 | 136 | it("should pack and unpack case class of 1 parameter") { 137 | case class C1(a: Int) 138 | val codec = new CaseClassCodec1[C1, Int](C1.apply, C1.unapply) 139 | val c = C1(Random.nextInt()) 140 | val unpacked = unpack(pack(c)(codec))(codec) 141 | unpacked.getClass should equal (classOf[C1]) 142 | unpacked should equal (c) 143 | } 144 | 145 | it("should pack and unpack case class of 2 parameters") { 146 | case class C2(a1: Int, a2: Int) 147 | val codec2 = new CaseClassCodec2[C2, Int, Int](C2.apply, C2.unapply) 148 | val c = C2(Random.nextInt(), Random.nextInt()) 149 | val unpacked = unpack(pack(c)(codec2))(codec2) 150 | unpacked.getClass should equal (classOf[C2]) 151 | unpacked should equal (c) 152 | } 153 | 154 | it("should pack and unpack case class of 3 parameters") { 155 | case class C3(a1: Int, a2: Int, a3: Int) 156 | val codec = new CaseClassCodec3[C3, Int, Int, Int](C3.apply, C3.unapply) 157 | val c = C3(Random.nextInt(), Random.nextInt(), Random.nextInt()) 158 | val unpacked = unpack(pack(c)(codec))(codec) 159 | unpacked.getClass should equal (classOf[C3]) 160 | unpacked should equal (c) 161 | } 162 | 163 | it("should pack and unpack case class of 4 parameters") { 164 | case class C4(a1: Int, a2: Int, a3: Int, a4: Int) 165 | val codec = new CaseClassCodec4[C4, Int, Int, Int, Int](C4.apply, C4.unapply) 166 | val c = C4(Random.nextInt(), Random.nextInt(), Random.nextInt(), Random.nextInt()) 167 | val unpacked = unpack(pack(c)(codec))(codec) 168 | unpacked.getClass should equal (classOf[C4]) 169 | unpacked should equal (c) 170 | } 171 | } 172 | 173 | describe("bijection codecs") { 174 | import org.velvia.msgpack.TransformCodecs._ 175 | import java.util.Date 176 | 177 | it("should pack and unpack types that are in bijection with those having a codec") { 178 | val codec = new TransformCodec[Date, Long](_.getTime, new Date(_)) 179 | val c = new Date 180 | val unpacked = unpack(pack(c)(codec))(codec) 181 | unpacked.getClass should equal (classOf[Date]) 182 | unpacked should equal (c) 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /test/org.velvia/MsgPackUtilsSpec.scala: -------------------------------------------------------------------------------- 1 | package org.velvia 2 | 3 | import java.io.{ByteArrayInputStream, DataInputStream} 4 | import org.scalatest.Matchers 5 | import org.scalatest.FunSpec 6 | 7 | class MsgPackUtilsSpec extends FunSpec with Matchers { 8 | 9 | val seqIntRow = (Seq(10, "AZ", "mobile"), 8) 10 | 11 | describe("Test MsgPackUtils Array[Byte] methods") { 12 | it("serialize a Int and de-serialize it back") { 13 | val number = 16 14 | MsgPackUtils.unpackInt(MsgPack.pack(number)) should equal (number) 15 | } 16 | 17 | it("serialize a Long and de-serialize it back") { 18 | val number:Long = 100000000000L 19 | MsgPackUtils.unpackLong(MsgPack.pack(number)) should equal (number) 20 | } 21 | 22 | it("serialize a Seq[Any] and de-serialize it back") { 23 | val seq = Seq(10, "AZ", "mobile") 24 | val serializedBytes = MsgPack.pack(seq) 25 | 26 | // unpack 27 | MsgPack.unpack(serializedBytes) should equal (seq) 28 | 29 | // unpackSeq 30 | val deserialzied = MsgPackUtils.unpackSeq(serializedBytes) 31 | deserialzied(0) should equal (10) 32 | deserialzied(1) should equal ("AZ") 33 | deserialzied(2) should equal ("mobile") 34 | } 35 | 36 | it("should deserialize maps and be able to access fields conveniently") { 37 | import MsgPackUtils._ 38 | 39 | val map = Map("int" -> 1, "str" -> "Kelvin") 40 | val map1 = unpackMap(MsgPack.pack(map)) 41 | 42 | map1 should equal (map) 43 | (map1.asInt("int") + 1) should equal (2) 44 | (map1.asLong("int") + 2) should equal (3L) 45 | map1.asOpt[Int]("doo") should equal (None) 46 | map1.as[String]("str") should equal ("Kelvin") 47 | } 48 | } 49 | 50 | describe("Test MsgPackUtils DataInputStream methods") { 51 | it("serialize a Int and de-serialize it back") { 52 | val number = 16 53 | val dis = new DataInputStream(new ByteArrayInputStream(MsgPack.pack(number))) 54 | 55 | MsgPackUtils.unpackInt(dis) should equal (number) 56 | } 57 | 58 | it("serialize a Long and de-serialize it back") { 59 | val number:Long = 100000000000L 60 | val dis = new DataInputStream(new ByteArrayInputStream(MsgPack.pack(number))) 61 | 62 | MsgPackUtils.unpackLong(dis) should equal (number) 63 | } 64 | 65 | it("serialize a Seq[Any] and de-serialize it back") { 66 | val seq = Seq(10, "AZ", "mobile") 67 | val dis = new DataInputStream(new ByteArrayInputStream(MsgPack.pack(seq))) 68 | 69 | val deserialized = MsgPackUtils.unpackSeq(dis) 70 | 71 | deserialized should equal (seq) 72 | deserialized(0) should equal (10) 73 | deserialized(1) should equal ("AZ") 74 | deserialized(2) should equal ("mobile") 75 | } 76 | 77 | it("serialize multiple types and de-serialize them back") { 78 | val intNumber = 16 79 | val seq = Seq(10, "AZ", "mobile") 80 | val longNumber = 100000000000L 81 | 82 | val rawBytes = MsgPack.pack(intNumber) ++ 83 | MsgPack.pack(seq) ++ 84 | MsgPack.pack(longNumber) 85 | val dis = new DataInputStream(new ByteArrayInputStream(rawBytes)) 86 | 87 | MsgPackUtils.unpackInt(dis) should equal (16) 88 | MsgPackUtils.unpackSeq(dis) should equal (seq) 89 | MsgPackUtils.unpackLong(dis) should equal (100000000000L) 90 | } 91 | } 92 | 93 | describe("Map as* implicit functions") { 94 | import MsgPackUtils._ 95 | 96 | it("should read back ints") { 97 | val map = Map("Kelvin" -> 9.toByte, "Evan" -> 1000, "along" -> 1234L, "str" -> "foo") 98 | map.asInt("Kelvin") should equal (9) 99 | map.asInt("Evan") should equal (1000) 100 | intercept[ClassCastException] { map.asInt("along") } 101 | intercept[ClassCastException] { map.asInt("str") } 102 | } 103 | 104 | it("should read back longs") { 105 | val map = Map("Kelvin" -> 9.toByte, "Evan" -> 1000, "along" -> 1234L, "str" -> "foo") 106 | map.asLong("Kelvin") should equal (9L) 107 | map.asLong("Evan") should equal (1000L) 108 | map.asLong("along") should equal (1234L) 109 | intercept[ClassCastException] { map.asInt("str") } 110 | } 111 | 112 | it("should read back maps and options from mutable maps") { 113 | val map = new collection.mutable.HashMap[String, Any] 114 | map ++= Map("map1" -> Map("option" -> 5)) 115 | map.asMap("map1").asOpt[Int]("option").get should equal (5) 116 | map.asMap("map1").asOpt[Int]("no-key") should equal (None) 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.6.1-SNAPSHOT" --------------------------------------------------------------------------------