Repository: codahale/jerkson Branch: master Commit: e64805642883 Files: 66 Total size: 165.2 KB Directory structure: gitextract_g7p0_a10/ ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── codahale/ │ │ └── jerkson/ │ │ └── JsonSnakeCase.java │ └── scala/ │ └── com/ │ └── codahale/ │ └── jerkson/ │ ├── AST.scala │ ├── Factory.scala │ ├── Generator.scala │ ├── Json.scala │ ├── Parser.scala │ ├── ParsingException.scala │ ├── ScalaModule.scala │ ├── StreamingIterator.scala │ ├── Types.scala │ ├── Util.scala │ ├── deser/ │ │ ├── BigDecimalDeserializer.scala │ │ ├── BigIntDeserializer.scala │ │ ├── BitSetDeserializer.scala │ │ ├── CaseClassDeserializer.scala │ │ ├── EitherDeserializer.scala │ │ ├── ImmutableMapDeserializer.scala │ │ ├── IntMapDeserializer.scala │ │ ├── IteratorDeserializer.scala │ │ ├── JValueDeserializer.scala │ │ ├── LongMapDeserializer.scala │ │ ├── MutableLinkedHashMapDeserializer.scala │ │ ├── MutableMapDeserializer.scala │ │ ├── OptionDeserializer.scala │ │ ├── RangeDeserializer.scala │ │ ├── ScalaDeserializers.scala │ │ ├── SeqDeserializer.scala │ │ └── StringBuilderDeserializer.scala │ ├── ser/ │ │ ├── CaseClassSerializer.scala │ │ ├── EitherSerializer.scala │ │ ├── IterableSerializer.scala │ │ ├── IteratorSerializer.scala │ │ ├── JValueSerializer.scala │ │ ├── MapSerializer.scala │ │ ├── OptionSerializer.scala │ │ ├── RangeSerializer.scala │ │ ├── ScalaSerializers.scala │ │ └── StringBuilderSerializer.scala │ └── util/ │ ├── CaseClassSigParser.scala │ └── scalax/ │ └── rules/ │ ├── Memoisable.scala │ ├── Result.scala │ ├── Rule.scala │ ├── Rules.scala │ ├── SeqRule.scala │ └── scalasig/ │ ├── ClassFileParser.scala │ ├── Flags.scala │ ├── ScalaSig.scala │ ├── Symbol.scala │ └── Type.scala └── test/ └── scala/ └── com/ └── codahale/ └── jerkson/ └── tests/ ├── ASTTypeSupportSpec.scala ├── BasicTypeSupportSpec.scala ├── CaseClassSupportSpec.scala ├── CollectionSupportSpec.scala ├── DefaultCollectionSupportSpec.scala ├── EdgeCaseSpec.scala ├── ExampleCaseClasses.scala ├── FancyTypeSupportSpec.scala ├── ImmutableCollectionSupportSpec.scala ├── JValueSpec.scala ├── MutableCollectionSupportSpec.scala └── StreamingSpec.scala ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea* *.iml target ================================================ FILE: CHANGELOG.md ================================================ v0.5.0: Oct 07 2011 =================== * Added `canSerialize` and `canDeserialize`. * Upgraded Jackson to 1.9.0. * Dropped support for Scala 2.8.1, added support for 2.8.2. Upgrade. * Dropped support for 2.9.0-1. Upgrade. v0.4.2: Sep 16 2011 =================== * Added support for case classes with multiple constructors. * Added support for `snake_cased` JSON field names via the `@JsonSnakeCase` annotation. v0.4.1: Sep 13 2011 =================== * Added `@JsonIgnoreProperties` support. * No longer serializing `transient` members of case classes. * Upgraded to Jackson 1.8.x. (Jerkson will now track the highest available version in the 1.8.x series.) v0.4.0: Jul 25 2011 =================== * Upgraded to Jackson 1.8.3. * Fixed deserialization of empty JSON objects as `JValue` instances. * Fixed deserialization of `Map[java.lang.Integer, A]` and `Map[java.lang.Long, A]` instances. * Fixed deserialization of case classes in weird bytecode environments like Play. * Added support for case classes nested in objects. * Allowed for deserializing `BigInt` and `BigDecimal` instances from anything those classes can parse as text. * Added a cache for type manifests. v0.3.2: Jun 09 2011 =================== * Added `Json.stream[A](Reader)`. * Fix `NullPointerException` when deserializing `Map` instances from weird JSON values. v0.3.1: Jun 05 2011 =================== * Added support for deserializing `Map[Int, _]` and `Map[Long, _]` instances. v0.3.0: Jun 04 2011 =================== * Added a very comprehensive set of tests, refactored around support for various types. (h/t Daniel Brown) * Added support for `StringBuilder`, `Array[A]`, `immutable._`, `mutable._`, `collection._` classes, `AST` classes, and others. * Fixed error messages when parsing empty JSON objects as case classes. * Enabled caching of all serializers and deserializers. * Switched to Maven for builds. * Removed the deprecated `Parser#parseStreamOf`. v0.2.2: May 18 2011 =================== * Upgraded to Jackson 1.7.7. * Fixed bugs in parsing case classes with other specially-namespaced types. v0.2.1: May 12 2011 =================== * Fixed bug in parsing case classes with `List[A]` members (and anything else which is typed in the `scala` package. v0.2.0: May 12 2011 =================== * Now cross-built for Scala 2.9.0. * Changed to parse the embedded Scala signature in case classes by using an embedded version of `scalap`. No longer depends on Paranamer. * Serializing a case class with a `None` field now elides the entire field instead of serializing the `Option[A]` as `null`. * Removed explicit flushes to output. v0.1.8: May 05 2011 =================== * Upgraded to Jackson 1.7.6. * Added selectors to `JValue` and friends. * Extracted out the `Json` trait for extensibility. * Added support for `Iterator` and `Set` instances. * Fixed deserialization of empty `Map`s. v0.1.7: Mar 31 2011 =================== * Upgraded to Jackson 1.7.4. v0.1.6: Feb 18 2011 =================== * Serialize `None` instances to `null`. (h/t Alex Cruise again) v0.1.5: Feb 18 2011 =================== * Added ability to actually serialize `Option` instances. (h/t Alex Cruise) v0.1.4: Jan 17 2011 =================== * Upgraded to Jackson 1.7.1, which fixes the buffer overruns * Handle empty JSON documents w/o resorting to `EOFException` v0.1.3: Jan 12 2011 =================== * Quick fix for potential buffer overrun errors in huge case classes (JACKSON-462). v0.1.2: Jan 07 2011 =================== * Upgraded to [Jackson 1.7.0](http://jackson.codehaus.org/1.7.0/release-notes/VERSION). * Added support for `Either[A, B]` instances. * Internal refactoring of `Json`. v0.1.1: Dec 09 2010 =================== * Upgraded to [Jackson 1.6.3](http://jackson.codehaus.org/1.6.3/release-notes/VERSION). * Added `StreamingIterator`, `Json.stream`, and deprecated `Json.parseStreamOf`. * Fixed support for `lazy` `val` and `var` members of case classes. * Added support for `@JsonIgnore` for case classes. v0.1.0: Dec 03 2010 =================== * Initial release. Totally awesome. ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2010-2011 Coda Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Jerkson ------- *Because I think you should use JSON.* Jerkson is a Scala wrapper for [Jackson](http://jackson.codehaus.org/) which brings Scala's ease-of-use to Jackson's features. Requirements ------------ * Scala 2.8.2 or 2.9.1 * Jackson 1.9.x Setting Up Your Project ----------------------- Go ahead and add Jerkson as a dependency: ```xml repo.codahale.com http://repo.codahale.com com.codahale jerkson_${scala.version} 0.5.0 ``` Parsing JSON ------------ ```scala import com.codahale.jerkson.Json._ // Parse JSON arrays parse[List[Int]]("[1,2,3]") //=> List(1,2,3) // Parse JSON objects parse[Map[String, Int]]("""{"one":1,"two":2}""") //=> Map("one"->1,"two"->2) // Parse JSON objects as case classes // (Parsing case classes isn't supported in the REPL.) case class Person(id: Long, name: String) parse[Person]("""{"id":1,"name":"Coda"}""") //=> Person(1,"Coda") // Parse streaming arrays of things for (person <- stream[Person](inputStream)) { println("New person: " + person) } ``` For more examples, check out the [specs](https://github.com/codahale/jerkson/blob/master/src/test/scala/com/codahale/jerkson/tests/). Generating JSON --------------- ```scala // Generate JSON arrays generate(List(1, 2, 3)) //=> [1,2,3] // Generate JSON objects generate(Map("one"->1, "two"->"dos")) //=> {"one":1,"two":"dos"} ``` For more examples, check out the [specs](https://github.com/codahale/jerkson/blob/master/src/test/scala/com/codahale/jerkson/tests/). Handling `snake_case` Field Names ================================= ```scala case class Person(firstName: String, lastName: String) @JsonSnakeCase case class Snake(firstName: String, lastName: String) generate(Person("Coda", "Hale")) //=> {"firstName": "Coda","lastName":"Hale"} generate(Snake("Windey", "Mover")) //=> {"first_name": "Windey","last_name":"Mover"} ``` License ------- Copyright (c) 2010-2011 Coda Hale Published under The MIT License, see LICENSE ================================================ FILE: pom.xml ================================================ 4.0.0 3.0.0 com.codahale jerkson_2.9.1 0.6.0-SNAPSHOT Jerkson for Scala 2.9.1 2.0.2 Coda Hale coda.hale@gmail.com -8 http://codahale.com/mit.txt The MIT License repo repo.codahale.com http://repo.codahale.com nativelibs4java http://nativelibs4java.sourceforge.net/maven repo.codahale.com scp://codahale.com/home/codahale/repo.codahale.com org.scala-lang scala-library ${scala.version} com.fasterxml.jackson.core jackson-core ${jackson.version} com.fasterxml.jackson.core jackson-databind ${jackson.version} com.codahale simplespec_${scala.version} 0.5.2 test release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin 1.2 sign-artifacts verify sign org.scala-tools maven-scala-plugin 2.15.2 compile testCompile -optimise -unchecked -deprecation com.nativelibs4java scalacl-compiler-plugin 0.2 UTF-8 org.apache.maven.plugins maven-compiler-plugin 2.3.2 1.6 1.6 UTF-8 org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources jar org.apache.maven.plugins maven-resources-plugin 2.5 UTF-8 org.apache.maven.plugins maven-surefire-plugin 2.8.1 false -Xmx1024m **/*Spec.java **/*Test.java org.apache.maven.plugins maven-release-plugin 2.2.1 true forked-path v@{project.version} clean test org.apache.maven.wagon wagon-ssh 2.2 ================================================ FILE: src/main/java/com/codahale/jerkson/JsonSnakeCase.java ================================================ package com.codahale.jerkson; import com.fasterxml.jackson.annotation.JacksonAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marker annotation which indicates that the annotated case class should be * serialized and deserialized using {@code snake_case} JSON field names instead * of {@code camelCase} field names. */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation public @interface JsonSnakeCase { } ================================================ FILE: src/main/scala/com/codahale/jerkson/AST.scala ================================================ package com.codahale.jerkson object AST { sealed trait JValue { def value: Any def valueAs[A]: A = value.asInstanceOf[A] def \(fieldName: String): JValue = JNull def apply(idx: Int): JValue = JNull def \\(fieldName: String): Seq[JValue] = Nil } case object JNull extends JValue { def value = null } case class JBoolean(value: Boolean) extends JValue case class JInt(value: BigInt) extends JValue case class JFloat(value: Double) extends JValue case class JString(value: String) extends JValue case class JArray(elements: List[JValue]) extends JValue { def value = null override def apply(index: Int): JValue = { try { elements(index) } catch { case _ => JNull } } } case class JField(name: String, value: JValue) extends JValue case class JObject(fields: List[JField]) extends JValue { def value = null override def \(fieldName: String): JValue = { fields.find { case JField(name, _) => name == fieldName }.map { case JField(_, value) => value }.getOrElse(JNull) } override def \\(fieldName: String): Seq[JValue] = { fields.flatMap { case JField(name, value) if name == fieldName => Seq(value) ++ (value \\ fieldName) case JField(_, value) => value \\ fieldName } } } } ================================================ FILE: src/main/scala/com/codahale/jerkson/Factory.scala ================================================ package com.codahale.jerkson import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.databind.ObjectMapper trait Factory { /** * The ObjectMapper to be used by {@link Parser} and {@link Generator}. */ protected val mapper: ObjectMapper /** * The JsonFactory to be used by {@link Parser} and {@link Generator}. */ protected val factory: JsonFactory } ================================================ FILE: src/main/scala/com/codahale/jerkson/Generator.scala ================================================ package com.codahale.jerkson import java.io.{File, OutputStream, Writer, StringWriter} import com.fasterxml.jackson.core.{JsonGenerator, JsonEncoding} trait Generator extends Factory { /** * Generate JSON from the given object and return it as a string. */ def generate[A](obj: A): String = { val writer = new StringWriter generate(obj, writer) writer.toString } /** * Generate JSON from the given object and write to the given Writer. */ def generate[A](obj: A, output: Writer) { generate(obj, factory.createJsonGenerator(output)) } /** * Generate JSON from the given object and write it to the given OutputStream. */ def generate[A](obj: A, output: OutputStream) { generate(obj, factory.createJsonGenerator(output, JsonEncoding.UTF8)) } /** * Generate JSON from the given object and write it to the given File. */ def generate[A](obj: A, output: File) { generate(obj, factory.createJsonGenerator(output, JsonEncoding.UTF8)) } /** * Returns true if the given class is serializable. */ def canSerialize[A](implicit mf: Manifest[A]) = mapper.canSerialize(mf.erasure) private def generate[A](obj: A, generator: JsonGenerator) { generator.writeObject(obj) generator.close() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/Json.scala ================================================ package com.codahale.jerkson import com.fasterxml.jackson.databind.{MappingJsonFactory, ObjectMapper} import com.fasterxml.jackson.core.{JsonGenerator, JsonParser => JacksonParser} object Json extends Json trait Json extends Parser with Generator { protected val classLoader = Thread.currentThread().getContextClassLoader protected val mapper = new ObjectMapper mapper.registerModule(new ScalaModule(classLoader)) protected val factory = new MappingJsonFactory(mapper) factory.enable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT) factory.enable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) factory.enable(JsonGenerator.Feature.QUOTE_FIELD_NAMES) factory.enable(JacksonParser.Feature.ALLOW_COMMENTS) factory.enable(JacksonParser.Feature.AUTO_CLOSE_SOURCE) } ================================================ FILE: src/main/scala/com/codahale/jerkson/Parser.scala ================================================ package com.codahale.jerkson import io.Source import java.net.URL import com.codahale.jerkson.AST.{JValue, JNull} import com.fasterxml.jackson.core.{JsonParser, JsonProcessingException} import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.TreeTraversingParser import java.io.{EOFException, Reader, File, InputStream} trait Parser extends Factory { /** * Parse a JSON string as a particular type. */ def parse[A](input: String)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON input stream as a particular type. */ def parse[A](input: InputStream)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON file as a particular type. */ def parse[A](input: File)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON URL as a particular type. */ def parse[A](input: URL)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON Reader as a particular type. */ def parse[A](input: Reader)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON byte array as a particular type. */ def parse[A](input: Array[Byte])(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf) /** * Parse a JSON Source as a particular type. */ def parse[A](input: Source)(implicit mf: Manifest[A]): A = parse[A](input.mkString) /** * Parse a JSON node as a particular type. */ def parse[A](input: JsonNode)(implicit mf: Manifest[A]): A = { val parser = new TreeTraversingParser(input, mapper) parse(parser, mf) } /** * Parse a streaming JSON array of particular types, returning an iterator * of the elements of the stream. */ def stream[A](input: InputStream)(implicit mf: Manifest[A]): Iterator[A] = { val parser = factory.createJsonParser(input) new StreamingIterator[A](parser, mf) } /** * Parse a streaming JSON array of particular types, returning an iterator * of the elements of the stream. */ def stream[A](input: Reader)(implicit mf: Manifest[A]): Iterator[A] = { val parser = factory.createJsonParser(input) new StreamingIterator[A](parser, mf) } /** * Returns true if the given class is deserializable. */ def canDeserialize[A](implicit mf: Manifest[A]) = mapper.canDeserialize(Types.build(mapper.getTypeFactory, mf)) private[jerkson] def parse[A](parser: JsonParser, mf: Manifest[A]): A = { try { if (mf.erasure == classOf[JValue] || mf.erasure == JNull.getClass) { val value: A = parser.getCodec.readValue(parser, Types.build(mapper.getTypeFactory, mf)) if (value == null) JNull.asInstanceOf[A] else value } else { parser.getCodec.readValue(parser, Types.build(mapper.getTypeFactory, mf)) } } catch { case e: JsonProcessingException => throw ParsingException(e) case e: EOFException => throw new ParsingException("JSON document ended unexpectedly.", e) } } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ParsingException.scala ================================================ package com.codahale.jerkson import java.io.IOException import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.core.{JsonParseException, JsonProcessingException} object ParsingException { def apply(cause: JsonProcessingException): ParsingException = { val message = cause match { case e: JsonMappingException => e.getMessage case e: JsonParseException => { val fake = new JsonParseException("", e.getLocation) val msg = e.getMessage.replace(fake.getMessage, "").replaceAll(""" (\(from.*\))""", "") "Malformed JSON. %s at character offset %d.".format(msg, e.getLocation.getCharOffset) } } new ParsingException(message, cause) } } class ParsingException(message: String, cause: Throwable) extends IOException(message, cause) ================================================ FILE: src/main/scala/com/codahale/jerkson/ScalaModule.scala ================================================ package com.codahale.jerkson import deser.ScalaDeserializers import com.fasterxml.jackson.databind.Module.SetupContext import com.fasterxml.jackson.core.Version import com.fasterxml.jackson.databind.Module import ser.ScalaSerializers class ScalaModule(classLoader: ClassLoader) extends Module { def version = new Version(0, 6, 0, "SNAPSHOT", "com.codahale", "jerkson") def getModuleName = "jerkson" def setupModule(context: SetupContext) { context.addDeserializers(new ScalaDeserializers(classLoader, context)) context.addSerializers(new ScalaSerializers) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/StreamingIterator.scala ================================================ package com.codahale.jerkson import com.fasterxml.jackson.core.{JsonToken, JsonParser} class StreamingIterator[A](parser: JsonParser, mf: Manifest[A]) extends Iterator[A] { import Json._ if (parser.getCurrentToken == null) { parser.nextToken() } require(parser.getCurrentToken == JsonToken.START_ARRAY) parser.nextToken() def hasNext = parser.getCurrentToken != JsonToken.END_ARRAY && !parser.isClosed def next() = if (hasNext) { val value = parse[A](parser, mf) parser.nextToken() value } else Iterator.empty.next() } ================================================ FILE: src/main/scala/com/codahale/jerkson/Types.scala ================================================ package com.codahale.jerkson import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.`type`.{TypeFactory, ArrayType} import scala.collection.JavaConversions.asScalaConcurrentMap import java.util.concurrent.ConcurrentHashMap private[jerkson] object Types { private val cachedTypes = asScalaConcurrentMap(new ConcurrentHashMap[Manifest[_], JavaType]()) def build(factory: TypeFactory, manifest: Manifest[_]): JavaType = cachedTypes.getOrElseUpdate(manifest, constructType(factory, manifest)) private def constructType(factory: TypeFactory, manifest: Manifest[_]): JavaType = { if (manifest.erasure.isArray) { ArrayType.construct(factory.constructType(manifest.erasure.getComponentType), null, null) } else { factory.constructParametricType( manifest.erasure, manifest.typeArguments.map {m => build(factory, m)}.toArray: _*) } } } ================================================ FILE: src/main/scala/com/codahale/jerkson/Util.scala ================================================ package com.codahale.jerkson private[jerkson] object Util { /** * Convert a string from camelCase to snake_case. */ def snakeCase(word: String) = { val len = word.length() val builder = new StringBuilder(len) if (len > 0) { var idx = if (word(0) == '_') { // preserve the first underscore builder += '_' 1 } else 0 while (idx < len) { val char = word(idx) if (Character.isUpperCase(char)) { builder += '_' builder += Character.toLowerCase(char) } else { builder += char } idx += 1 } } builder.toString() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/BigDecimalDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.JsonParser class BigDecimalDeserializer extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { if (jp.getCurrentToken == null) { jp.nextToken() } try { BigDecimal(jp.getText) } catch { case e: NumberFormatException => throw ctxt.mappingException(classOf[BigDecimal]) } } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/BigIntDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.JsonParser class BigIntDeserializer extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { if (jp.getCurrentToken == null) { jp.nextToken() } try { BigInt(jp.getText) } catch { case e: NumberFormatException => throw ctxt.mappingException(classOf[BigInt]) } } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/BitSetDeserializer.scala ================================================ package com.codahale.jerkson.deser import scala.collection.generic.BitSetFactory import scala.collection.{BitSetLike, BitSet} import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} class BitSetDeserializer[Coll <: BitSet with BitSetLike[Coll]](factory: BitSetFactory[Coll]) extends JsonDeserializer[Coll] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { val builder = factory.newBuilder if (jp.getCurrentToken != JsonToken.START_ARRAY) { throw ctxt.mappingException(classOf[BitSet]) } while (jp.nextToken() != JsonToken.END_ARRAY) { builder += jp.getIntValue } builder.result() } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala ================================================ package com.codahale.jerkson.deser import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer import com.codahale.jerkson.JsonSnakeCase import com.codahale.jerkson.util._ import com.codahale.jerkson.Util._ import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.node.{ObjectNode, NullNode, TreeTraversingParser} import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.core.{JsonToken, JsonParser} class CaseClassDeserializer(config: DeserializationConfig, javaType: JavaType, classLoader: ClassLoader) extends JsonDeserializer[Object] { private val isSnakeCase = javaType.getRawClass.isAnnotationPresent(classOf[JsonSnakeCase]) private val params = CaseClassSigParser.parse(javaType.getRawClass, config.getTypeFactory, classLoader).map { case (name, jt) => (if (isSnakeCase) snakeCase(name) else name, jt) }.toArray private val paramTypes = params.map { _._2.getRawClass }.toList private val constructor = javaType.getRawClass.getConstructors.find { c => val constructorTypes = c.getParameterTypes.toList.map { t => t.toString match { case "byte" => classOf[java.lang.Byte] case "short" => classOf[java.lang.Short] case "int" => classOf[java.lang.Integer] case "long" => classOf[java.lang.Long] case "float" => classOf[java.lang.Float] case "double" => classOf[java.lang.Double] case "char" => classOf[java.lang.Character] case "boolean" => classOf[java.lang.Boolean] case _ => t } } constructorTypes == paramTypes }.getOrElse { throw new JsonMappingException("Unable to find a case accessor for " + javaType.getRawClass.getName) } def deserialize(jp: JsonParser, ctxt: DeserializationContext): Object = { if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(javaType.getRawClass) } val node = jp.readValueAsTree[JsonNode] val values = new ArrayBuffer[AnyRef] for ((paramName, paramType) <- params) { val field = node.get(paramName) val tp = new TreeTraversingParser(if (field == null) NullNode.getInstance else field, jp.getCodec) val value = if (paramType.getRawClass == classOf[Option[_]]) { // thanks again for special-casing VALUE_NULL Option(tp.getCodec.readValue[Object](tp, paramType.containedType(0))) } else { tp.getCodec.readValue[Object](tp, paramType) } if (field != null || value != null) { values += value } if (values.size == params.size) { return constructor.newInstance(values.toArray: _*).asInstanceOf[Object] } } throw new JsonMappingException(errorMessage(node)) } private def errorMessage(node: JsonNode) = { val names = params.map { _._1 }.mkString("[", ", ", "]") val existing = node match { case obj: ObjectNode => obj.fieldNames.mkString("[", ", ", "]") case _: NullNode => "[]" // this is what Jackson deserializes the inside of an empty object to case unknown => "a non-object" } "Invalid JSON. Needed %s, but found %s.".format(names, existing) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/EitherDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.node.TreeTraversingParser import com.fasterxml.jackson.databind._ class EitherDeserializer(config: DeserializationConfig, javaType: JavaType) extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { val node = jp.readValueAsTree[JsonNode] val tp = new TreeTraversingParser(node, jp.getCodec) try { Left(tp.getCodec.readValue[Object](tp, javaType.containedType(0))) } catch { case _ => Right(tp.getCodec.readValue[Object](tp, javaType.containedType(1))) } } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/ImmutableMapDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import collection.generic.MapFactory import collection.MapLike import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class ImmutableMapDeserializer[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](companion: MapFactory[CC], valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var valueDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext): CC[String, Object] = { val builder = companion.newBuilder[String, Object] if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(valueType.getRawClass) } while (jp.getCurrentToken != JsonToken.END_OBJECT) { val name = jp.getCurrentName jp.nextToken() builder += ((name, valueDeserializer.deserialize(jp, ctxt))) jp.nextToken() } builder.result() } def resolve(ctxt: DeserializationContext) { valueDeserializer = ctxt.findRootValueDeserializer(valueType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/IntMapDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.JavaType import scala.collection.immutable.IntMap import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class IntMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var valueDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { var map = IntMap.empty[Object] if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(valueType.getRawClass) } while (jp.getCurrentToken != JsonToken.END_OBJECT) { try { val name = jp.getCurrentName.toInt jp.nextToken() map += ((name, valueDeserializer.deserialize(jp, ctxt))) jp.nextToken() } catch { case e: IllegalArgumentException => throw ctxt.mappingException(classOf[IntMap[_]]) } } map } def resolve(ctxt: DeserializationContext) { valueDeserializer = ctxt.findRootValueDeserializer(valueType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/IteratorDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class IteratorDeserializer(elementType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var elementDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { val builder = Seq.newBuilder[Object] if (jp.getCurrentToken != JsonToken.START_ARRAY) { throw ctxt.mappingException(elementType.getRawClass) } while (jp.nextToken() != JsonToken.END_ARRAY) { builder += elementDeserializer.deserialize(jp, ctxt) } builder.result().iterator.buffered } def resolve(ctxt: DeserializationContext) { elementDeserializer = ctxt.findRootValueDeserializer(elementType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/JValueDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.codahale.jerkson.AST._ import collection.mutable.ArrayBuffer import com.fasterxml.jackson.databind.`type`.TypeFactory import com.codahale.jerkson.Types class JValueDeserializer(factory: TypeFactory, klass: Class[_]) extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext): Object = { if (jp.getCurrentToken == null) { jp.nextToken() } val value = jp.getCurrentToken match { case JsonToken.VALUE_NUMBER_INT => JInt(BigInt(jp.getText)) case JsonToken.VALUE_NUMBER_FLOAT => JFloat(jp.getDoubleValue) case JsonToken.VALUE_STRING => JString(jp.getText) case JsonToken.VALUE_TRUE => JBoolean(true) case JsonToken.VALUE_FALSE => JBoolean(false) case JsonToken.START_ARRAY => { JArray(jp.getCodec.readValue(jp, Types.build(factory, manifest[List[JValue]]))) } case JsonToken.START_OBJECT => { jp.nextToken() deserialize(jp, ctxt) } case JsonToken.FIELD_NAME | JsonToken.END_OBJECT => { val fields = new ArrayBuffer[JField] while (jp.getCurrentToken != JsonToken.END_OBJECT) { val name = jp.getCurrentName jp.nextToken() fields += JField(name, jp.getCodec.readValue(jp, Types.build(factory, manifest[JValue]))) jp.nextToken() } JObject(fields.toList) } case _ => throw ctxt.mappingException(classOf[JValue]) } if (!klass.isAssignableFrom(value.getClass)) { throw ctxt.mappingException(klass) } value } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/LongMapDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.JavaType import scala.collection.immutable.LongMap import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class LongMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var valueDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { var map = LongMap.empty[Object] if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(valueType.getRawClass) } while (jp.getCurrentToken != JsonToken.END_OBJECT) { try { val name = jp.getCurrentName.toLong jp.nextToken() map += ((name, valueDeserializer.deserialize(jp, ctxt))) jp.nextToken() } catch { case e: IllegalArgumentException => throw ctxt.mappingException(classOf[LongMap[_]]) } } map } def resolve(ctxt: DeserializationContext) { valueDeserializer = ctxt.findRootValueDeserializer(valueType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/MutableLinkedHashMapDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import scala.collection.mutable import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class MutableLinkedHashMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var valueDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { val builder = mutable.LinkedHashMap.newBuilder[String, Object] if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } while (jp.getCurrentToken != JsonToken.END_OBJECT) { val name = jp.getCurrentName jp.nextToken() builder += ((name, valueDeserializer.deserialize(jp, ctxt))) jp.nextToken() } builder.result() } def resolve(ctxt: DeserializationContext) { valueDeserializer = ctxt.findRootValueDeserializer(valueType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/MutableMapDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import scala.collection.mutable import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class MutableMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var valueDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { val builder = mutable.HashMap.newBuilder[String, Object] if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(valueType.getRawClass) } while (jp.getCurrentToken != JsonToken.END_OBJECT) { val name = jp.getCurrentName jp.nextToken() builder += ((name, valueDeserializer.deserialize(jp, ctxt))) jp.nextToken() } builder.result() } def resolve(ctxt: DeserializationContext) { valueDeserializer = ctxt.findRootValueDeserializer(valueType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/OptionDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class OptionDeserializer(elementType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var elementDeserializer: JsonDeserializer[Object] = _ override def getEmptyValue = None override def getNullValue = None def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { if (jp.getCurrentToken == JsonToken.VALUE_NULL) { None } else { Some(elementDeserializer.deserialize(jp, ctxt)) } } def resolve(ctxt: DeserializationContext) { elementDeserializer = ctxt.findRootValueDeserializer(elementType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/RangeDeserializer.scala ================================================ package com.codahale.jerkson.deser import collection.JavaConversions._ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.{JsonMappingException, DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.databind.node.{IntNode, BooleanNode, NullNode, ObjectNode} class RangeDeserializer extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { if (jp.getCurrentToken == JsonToken.START_OBJECT) { jp.nextToken() } if (jp.getCurrentToken != JsonToken.FIELD_NAME && jp.getCurrentToken != JsonToken.END_OBJECT) { throw ctxt.mappingException(classOf[Range]) } val node = jp.readValueAsTree[JsonNode] val inclusiveNode = node.get("inclusive") val stepNode = node.get("step") val startNode = node.get("start") val endNode = node.get("end") if (startNode == null || !classOf[IntNode].isAssignableFrom(startNode.getClass) || endNode == null || !classOf[IntNode].isAssignableFrom(endNode.getClass)) { throw new JsonMappingException(errorMessage(node)) } val step = if (stepNode == null || !classOf[IntNode].isAssignableFrom(stepNode.getClass)) { 1 } else { stepNode.intValue } val start = startNode.asInstanceOf[IntNode].intValue val end = endNode.asInstanceOf[IntNode].intValue if (inclusiveNode == null || inclusiveNode == BooleanNode.FALSE) { Range(start, end, step) } else { Range.inclusive(start, end, step) } } private def errorMessage(node: JsonNode) = { val existing = node match { case obj: ObjectNode => obj.fieldNames.mkString("[", ", ", "]") case _: NullNode => "[]" // this is what Jackson deserializes the inside of an empty object to case unknown => "a non-object" } "Invalid JSON. Needed [start, end, , ], but found %s.".format(existing) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind._ import scala.collection.{Traversable, MapLike, immutable, mutable} import com.codahale.jerkson.AST.{JNull, JValue} import scala.collection.generic.{MapFactory, GenericCompanion} import com.fasterxml.jackson.databind.deser.Deserializers import com.fasterxml.jackson.databind.Module.SetupContext class ScalaDeserializers(classLoader: ClassLoader, context: SetupContext) extends Deserializers.Base { override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig, beanDesc: BeanDescription) = { val klass = javaType.getRawClass if (klass == classOf[Range] || klass == classOf[immutable.Range]) { new RangeDeserializer } else if (klass == classOf[StringBuilder]) { new StringBuilderDeserializer } else if (klass == classOf[List[_]] || klass == classOf[immutable.List[_]]) { createSeqDeserializer(config, javaType, List) } else if (klass == classOf[Seq[_]] || klass == classOf[immutable.Seq[_]] || klass == classOf[Iterable[_]] || klass == classOf[Traversable[_]] || klass == classOf[immutable.Traversable[_]]) { createSeqDeserializer(config, javaType, Seq) } else if (klass == classOf[Stream[_]] || klass == classOf[immutable.Stream[_]]) { createSeqDeserializer(config, javaType, Stream) } else if (klass == classOf[immutable.Queue[_]]) { createSeqDeserializer(config, javaType, immutable.Queue) } else if (klass == classOf[Vector[_]]) { createSeqDeserializer(config, javaType, Vector) } else if (klass == classOf[IndexedSeq[_]] || klass == classOf[immutable.IndexedSeq[_]]) { createSeqDeserializer(config, javaType, IndexedSeq) } else if (klass == classOf[mutable.ResizableArray[_]]) { createSeqDeserializer(config, javaType, mutable.ResizableArray) } else if (klass == classOf[mutable.ArraySeq[_]]) { createSeqDeserializer(config, javaType, mutable.ArraySeq) } else if (klass == classOf[mutable.MutableList[_]]) { createSeqDeserializer(config, javaType, mutable.MutableList) } else if (klass == classOf[mutable.Queue[_]]) { createSeqDeserializer(config, javaType, mutable.Queue) } else if (klass == classOf[mutable.ListBuffer[_]]) { createSeqDeserializer(config, javaType, mutable.ListBuffer) } else if (klass == classOf[mutable.ArrayBuffer[_]] || klass == classOf[mutable.Traversable[_]]) { createSeqDeserializer(config, javaType, mutable.ArrayBuffer) } else if (klass == classOf[collection.BitSet] || klass == classOf[immutable.BitSet]) { new BitSetDeserializer(immutable.BitSet) } else if (klass == classOf[mutable.BitSet]) { new BitSetDeserializer(mutable.BitSet) } else if (klass == classOf[immutable.HashSet[_]]) { createSeqDeserializer(config, javaType, immutable.HashSet) } else if (klass == classOf[Set[_]] || klass == classOf[immutable.Set[_]] || klass == classOf[collection.Set[_]]) { createSeqDeserializer(config, javaType, Set) } else if (klass == classOf[mutable.HashSet[_]]) { createSeqDeserializer(config, javaType, mutable.HashSet) } else if (klass == classOf[mutable.LinkedHashSet[_]]) { createSeqDeserializer(config, javaType, mutable.LinkedHashSet) } else if (klass == classOf[Iterator[_]] || klass == classOf[BufferedIterator[_]]) { val elementType = javaType.containedType(0) new IteratorDeserializer(elementType) } else if (klass == classOf[immutable.HashMap[_, _]] || klass == classOf[Map[_, _]] || klass == classOf[collection.Map[_, _]]) { createImmutableMapDeserializer(config, javaType, immutable.HashMap) } else if (klass == classOf[immutable.IntMap[_]]) { val valueType = javaType.containedType(0) new IntMapDeserializer(valueType) } else if (klass == classOf[immutable.LongMap[_]]) { val valueType = javaType.containedType(0) new LongMapDeserializer(valueType) } else if (klass == classOf[mutable.HashMap[_, _]] || klass == classOf[mutable.Map[_, _]]) { if (javaType.containedType(0).getRawClass == classOf[String]) { val valueType = javaType.containedType(1) new MutableMapDeserializer(valueType) } else { null } } else if (klass == classOf[mutable.LinkedHashMap[_, _]]) { if (javaType.containedType(0).getRawClass == classOf[String]) { val valueType = javaType.containedType(1) new MutableLinkedHashMapDeserializer(valueType) } else { null } } else if (klass == classOf[Option[_]]) { createOptionDeserializer(config, javaType) } else if (classOf[JValue].isAssignableFrom(klass) || klass == JNull.getClass) { new JValueDeserializer(config.getTypeFactory, klass) } else if (klass == classOf[BigInt]) { new BigIntDeserializer } else if (klass == classOf[BigDecimal]) { new BigDecimalDeserializer } else if (klass == classOf[Either[_,_]]) { new EitherDeserializer(config, javaType) } else if (classOf[Product].isAssignableFrom(klass)) { new CaseClassDeserializer(config, javaType, classLoader) } else null } private def createSeqDeserializer[CC[X] <: Traversable[X]](config: DeserializationConfig, javaType: JavaType, companion: GenericCompanion[CC]) = { val elementType = javaType.containedType(0) new SeqDeserializer[CC](companion, elementType) } private def createOptionDeserializer(config: DeserializationConfig, javaType: JavaType) = { val elementType = javaType.containedType(0) new OptionDeserializer(elementType) } private def createImmutableMapDeserializer[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](config: DeserializationConfig, javaType: JavaType, companion: MapFactory[CC]) = { val keyType = javaType.containedType(0) val valueType = javaType.containedType(1) if (keyType.getRawClass == classOf[String]) { new ImmutableMapDeserializer[CC](companion, valueType) } else if (keyType.getRawClass == classOf[Int] || keyType.getRawClass == classOf[java.lang.Integer]) { new IntMapDeserializer(valueType) } else if (keyType.getRawClass == classOf[Long] || keyType.getRawClass == classOf[java.lang.Long]) { new LongMapDeserializer(valueType) } else { null } } } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/SeqDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.core.{JsonToken, JsonParser} import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.{JsonDeserializer, DeserializationContext} import collection.generic.GenericCompanion import com.fasterxml.jackson.databind.deser.ResolvableDeserializer class SeqDeserializer[+CC[X] <: Traversable[X]](companion: GenericCompanion[CC], elementType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer { var elementDeserializer: JsonDeserializer[Object] = _ def deserialize(jp: JsonParser, ctxt: DeserializationContext): CC[Object] = { val builder = companion.newBuilder[Object] if (jp.getCurrentToken != JsonToken.START_ARRAY) { throw ctxt.mappingException(elementType.getRawClass) } while (jp.nextToken() != JsonToken.END_ARRAY) { builder += elementDeserializer.deserialize(jp, ctxt) } builder.result() } def resolve(ctxt: DeserializationContext) { elementDeserializer = ctxt.findRootValueDeserializer(elementType) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/deser/StringBuilderDeserializer.scala ================================================ package com.codahale.jerkson.deser import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} import com.fasterxml.jackson.core.{JsonToken, JsonParser} class StringBuilderDeserializer extends JsonDeserializer[Object] { def deserialize(jp: JsonParser, ctxt: DeserializationContext) = { if (jp.getCurrentToken != JsonToken.VALUE_STRING) { throw ctxt.mappingException(classOf[StringBuilder]) } new StringBuilder(jp.getText) } override def isCachable = true } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/CaseClassSerializer.scala ================================================ package com.codahale.jerkson.ser import java.lang.reflect.Modifier import com.codahale.jerkson.JsonSnakeCase import com.codahale.jerkson.Util._ import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.annotation.{JsonIgnore, JsonIgnoreProperties} import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class CaseClassSerializer[A <: Product](klass: Class[_]) extends JsonSerializer[A] { private val isSnakeCase = klass.isAnnotationPresent(classOf[JsonSnakeCase]) private val ignoredFields = if (klass.isAnnotationPresent(classOf[JsonIgnoreProperties])) { klass.getAnnotation(classOf[JsonIgnoreProperties]).value().toSet } else Set.empty[String] private val nonIgnoredFields = klass.getDeclaredFields.filterNot { f => f.getAnnotation(classOf[JsonIgnore]) != null || ignoredFields(f.getName) || (f.getModifiers & Modifier.TRANSIENT) != 0 || f.getName.contains("$") } private val methods = klass.getDeclaredMethods .filter { _.getParameterTypes.isEmpty } .map { m => m.getName -> m }.toMap def serialize(value: A, json: JsonGenerator, provider: SerializerProvider) { json.writeStartObject() for (field <- nonIgnoredFields) { val methodOpt = methods.get(field.getName) val fieldValue: Object = methodOpt.map { _.invoke(value) }.getOrElse(field.get(value)) if (fieldValue != None) { val fieldName = methodOpt.map { _.getName }.getOrElse(field.getName) provider.defaultSerializeField(if (isSnakeCase) snakeCase(fieldName) else fieldName, fieldValue, json) } } json.writeEndObject() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/EitherSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class EitherSerializer extends JsonSerializer[Either[_, _]] { def serialize(value: Either[_, _], json: JsonGenerator, provider: SerializerProvider) { provider.defaultSerializeValue(value match { case Left(o) => o.asInstanceOf[Object] case Right(o) => o.asInstanceOf[Object] }, json) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/IterableSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class IterableSerializer extends JsonSerializer[Iterable[_]] { def serialize(value: Iterable[_], json: JsonGenerator, provider: SerializerProvider) { json.writeStartArray() for (element <- value) { provider.defaultSerializeValue(element, json) } json.writeEndArray() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/IteratorSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class IteratorSerializer extends JsonSerializer[Iterator[_]] { def serialize(value: Iterator[_], json: JsonGenerator, provider: SerializerProvider) { json.writeStartArray() for (element <- value) { provider.defaultSerializeValue(element, json) } json.writeEndArray() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/JValueSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} import com.codahale.jerkson.AST._ import java.math.BigInteger class JValueSerializer extends JsonSerializer[JValue] { def serialize(value: JValue, json: JsonGenerator, provider: SerializerProvider) { value match { case JInt(v) => json.writeNumber(new BigInteger(v.toString())) case JFloat(v) => json.writeNumber(v) case JString(v) => json.writeString(v) case JBoolean(v) => json.writeBoolean(v) case JArray(elements) => json.writeObject(elements) case JField(name, value) => { json.writeFieldName(name) json.writeObject(value) } case JObject(fields) => { json.writeStartObject() fields.foreach(json.writeObject) json.writeEndObject() } case JNull => json.writeNull() } } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/MapSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class MapSerializer extends JsonSerializer[collection.Map[_ ,_]] { def serialize(map: collection.Map[_,_], json: JsonGenerator, provider: SerializerProvider) { json.writeStartObject() for ((key, value) <- map) { provider.defaultSerializeField(key.toString, value, json) } json.writeEndObject() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/OptionSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class OptionSerializer extends JsonSerializer[Option[_]] { def serialize(value: Option[_], json: JsonGenerator, provider: SerializerProvider) { provider.defaultSerializeValue(value.orNull, json) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/RangeSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class RangeSerializer extends JsonSerializer[Range] { def serialize(value: Range, json: JsonGenerator, provider: SerializerProvider) { json.writeStartObject() json.writeNumberField("start", value.start) json.writeNumberField("end", value.end) if (value.step != 1) { json.writeNumberField("step", value.step) } if (value.isInclusive) { json.writeBooleanField("inclusive", value.isInclusive) } json.writeEndObject() } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala ================================================ package com.codahale.jerkson.ser import com.codahale.jerkson.AST.JValue import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.ser.Serializers class ScalaSerializers extends Serializers.Base { override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription) = { val ser: Object = if (classOf[Option[_]].isAssignableFrom(beanDesc.getBeanClass)) { new OptionSerializer } else if (classOf[StringBuilder].isAssignableFrom(beanDesc.getBeanClass)) { new StringBuilderSerializer } else if (classOf[collection.Map[_,_]].isAssignableFrom(beanDesc.getBeanClass)) { new MapSerializer } else if (classOf[Range].isAssignableFrom(beanDesc.getBeanClass)) { new RangeSerializer } else if (classOf[Iterable[_]].isAssignableFrom(beanDesc.getBeanClass)) { new IterableSerializer } else if (classOf[Iterator[_]].isAssignableFrom(beanDesc.getBeanClass)) { new IteratorSerializer } else if (classOf[JValue].isAssignableFrom(beanDesc.getBeanClass)) { new JValueSerializer } else if (classOf[Either[_,_]].isAssignableFrom(beanDesc.getBeanClass)) { new EitherSerializer } else if (classOf[Product].isAssignableFrom(beanDesc.getBeanClass)) { new CaseClassSerializer(beanDesc.getBeanClass) } else { null } ser.asInstanceOf[JsonSerializer[Object]] } } ================================================ FILE: src/main/scala/com/codahale/jerkson/ser/StringBuilderSerializer.scala ================================================ package com.codahale.jerkson.ser import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer} class StringBuilderSerializer extends JsonSerializer[StringBuilder] { def serialize(value: StringBuilder, json: JsonGenerator, provider: SerializerProvider) { json.writeString(value.toString()) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala ================================================ package com.codahale.jerkson.util import com.codahale.jerkson.util.scalax.rules.scalasig._ import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.`type`.TypeFactory import scala.reflect.ScalaSignature import scala.reflect.generic.ByteCodecs class MissingPickledSig(clazz: Class[_]) extends Error("Failed to parse pickled Scala signature from: %s".format(clazz)) class MissingExpectedType(clazz: Class[_]) extends Error( "Parsed pickled Scala signature, but no expected type found: %s" .format(clazz) ) object CaseClassSigParser { val SCALA_SIG = "ScalaSig" val SCALA_SIG_ANNOTATION = "Lscala/reflect/ScalaSignature;" val BYTES_VALUE = "bytes" private def parseClassFileFromByteCode(clazz: Class[_]): Option[ClassFile] = try { // taken from ScalaSigParser parse method with the explicit purpose of walking away from NPE val byteCode = ByteCode.forClass(clazz) Option(ClassFileParser.parse(byteCode)) } catch { case e: NullPointerException => None // yes, this is the exception, but it is totally unhelpful to the end user } private def parseByteCodeFromAnnotation(clazz: Class[_]): Option[ByteCode] = { if (clazz.isAnnotationPresent(classOf[ScalaSignature])) { val sig = clazz.getAnnotation(classOf[ScalaSignature]) val bytes = sig.bytes.getBytes("UTF-8") val len = ByteCodecs.decode(bytes) Option(ByteCode(bytes.take(len))) } else { None } } private def parseScalaSig(_clazz: Class[_], classLoader: ClassLoader): Option[ScalaSig] = { val clazz = findRootClass(_clazz, classLoader) parseClassFileFromByteCode(clazz).map(ScalaSigParser.parse(_)).getOrElse(None) orElse parseByteCodeFromAnnotation(clazz).map(ScalaSigAttributeParsers.parse(_)) orElse None } protected def findRootClass(klass: Class[_], classLoader: ClassLoader) = loadClass(klass.getName.split("\\$").head, classLoader) protected def simpleName(klass: Class[_]) = klass.getName.split("\\$").last protected def findSym[A](clazz: Class[A], classLoader: ClassLoader) = { val name = simpleName(clazz) val pss = parseScalaSig(clazz, classLoader) pss match { case Some(x) => { val topLevelClasses = x.topLevelClasses topLevelClasses.headOption match { case Some(tlc) => { tlc } case None => { val topLevelObjects = x.topLevelObjects topLevelObjects.headOption match { case Some(tlo) => { x.symbols.find { s => !s.isModule && s.name == name } match { case Some(s) => s.asInstanceOf[ClassSymbol] case None => throw new MissingExpectedType(clazz) } } case _ => throw new MissingExpectedType(clazz) } } } } case None => throw new MissingPickledSig(clazz) } } def parse[A](clazz: Class[A], factory: TypeFactory, classLoader: ClassLoader) = { findSym(clazz, classLoader).children.filter(c => c.isCaseAccessor && !c.isPrivate) .flatMap { ms => ms.asInstanceOf[MethodSymbol].infoType match { case NullaryMethodType(t: TypeRefType) => ms.name -> typeRef2JavaType(t, factory, classLoader) :: Nil case _ => Nil } } } protected def typeRef2JavaType(ref: TypeRefType, factory: TypeFactory, classLoader: ClassLoader): JavaType = { try { if (ref.symbol.path == "scala.Array") { val elementType = typeRef2JavaType(ref.typeArgs.head.asInstanceOf[TypeRefType], factory, classLoader) val realElementType = elementType.getRawClass.getName match { case "java.lang.Boolean" => classOf[Boolean] case "java.lang.Byte" => classOf[Byte] case "java.lang.Character" => classOf[Char] case "java.lang.Double" => classOf[Double] case "java.lang.Float" => classOf[Float] case "java.lang.Integer" => classOf[Int] case "java.lang.Long" => classOf[Long] case "java.lang.Short" => classOf[Short] case _ => elementType.getRawClass } val array = java.lang.reflect.Array.newInstance(realElementType, 0) factory.constructType(array.getClass) } else { val klass = loadClass(ref.symbol.path, classLoader) factory.constructParametricType( klass, ref.typeArgs.map { t => typeRef2JavaType(t.asInstanceOf[TypeRefType], factory, classLoader) }: _* ) } } catch { case e: Throwable => { e.printStackTrace() null } } } protected def loadClass(path: String, classLoader: ClassLoader) = path match { case "scala.Predef.Map" => classOf[Map[_, _]] case "scala.Predef.Set" => classOf[Set[_]] case "scala.Predef.String" => classOf[String] case "scala.package.List" => classOf[List[_]] case "scala.package.Seq" => classOf[Seq[_]] case "scala.package.Sequence" => classOf[Seq[_]] case "scala.package.Collection" => classOf[Seq[_]] case "scala.package.IndexedSeq" => classOf[IndexedSeq[_]] case "scala.package.RandomAccessSeq" => classOf[IndexedSeq[_]] case "scala.package.Iterable" => classOf[Iterable[_]] case "scala.package.Iterator" => classOf[Iterator[_]] case "scala.package.Vector" => classOf[Vector[_]] case "scala.package.BigDecimal" => classOf[BigDecimal] case "scala.package.BigInt" => classOf[BigInt] case "scala.package.Integer" => classOf[java.lang.Integer] case "scala.package.Character" => classOf[java.lang.Character] case "scala.Long" => classOf[java.lang.Long] case "scala.Int" => classOf[java.lang.Integer] case "scala.Boolean" => classOf[java.lang.Boolean] case "scala.Short" => classOf[java.lang.Short] case "scala.Byte" => classOf[java.lang.Byte] case "scala.Float" => classOf[java.lang.Float] case "scala.Double" => classOf[java.lang.Double] case "scala.Char" => classOf[java.lang.Character] case "scala.Any" => classOf[Any] case "scala.AnyRef" => classOf[AnyRef] case name => classLoader.loadClass(name) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/Memoisable.scala ================================================ package com.codahale.jerkson.util package scalax package rules import scala.collection.mutable.HashMap trait MemoisableRules extends Rules { def memo[In <: Memoisable, Out, A, X](key: AnyRef) (toRule: => In => Result[Out, A, X]) = { lazy val rule = toRule from[In] {in => in.memo(key, rule(in))} } override def ruleWithName[In, Out, A, X](name: String, f: In => rules.Result[Out, A, X]) = super.ruleWithName( name, (in: In) => in match { case s: Memoisable => s.memo(name, f(in)) case _ => f(in) } ) } trait Memoisable { def memo[A](key: AnyRef, a: => A): A } object DefaultMemoisable { var debug = false } trait DefaultMemoisable extends Memoisable { protected val map = new HashMap[AnyRef, Any] def memo[A](key: AnyRef, a: => A) = { map.getOrElseUpdate(key, compute(key, a)).asInstanceOf[A] } protected def compute[A](key: AnyRef, a: => A): Any = a match { case success: Success[_, _] => onSuccess(key, success); success case other => if (DefaultMemoisable.debug) println(key + " -> " + other) other } protected def onSuccess[S, T](key: AnyRef, result: Success[S, T]) { val Success(out, t) = result if (DefaultMemoisable.debug) println(key + " -> " + t + " (" + out + ")") } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/Result.scala ================================================ package com.codahale.jerkson.util package scalax package rules /**Represents the combined value of two rules applied in sequence. * * @see the Scala parser combinator */ case class ~[+A, +B](_1: A, _2: B) { override def toString = "(" + _1 + " ~ " + _2 + ")" } sealed abstract class Result[+Out, +A, +X] { def out: Out def value: A def error: X implicit def toOption: Option[A] def map[B](f: A => B): Result[Out, B, X] def mapOut[Out2](f: Out => Out2): Result[Out2, A, X] def map[Out2, B](f: (Out, A) => (Out2, B)): Result[Out2, B, X] def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, X] def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, X] } case class Success[+Out, +A](out: Out, value: A) extends Result[Out, A, Nothing] { def error = throw new ScalaSigParserError("No error") def toOption = Some(value) def map[B](f: A => B): Result[Out, B, Nothing] = Success(out, f(value)) def mapOut[Out2](f: Out => Out2): Result[Out2, A, Nothing] = Success(f(out), value) def map[Out2, B](f: (Out, A) => (Out2, B)): Success[Out2, B] = f(out, value) match {case (out2, b) => Success(out2, b)} def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = f(out, value) def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = this } sealed abstract class NoSuccess[+X] extends Result[Nothing, Nothing, X] { def out = throw new ScalaSigParserError("No output") def value = throw new ScalaSigParserError("No value") def toOption = None def map[B](f: Nothing => B) = this def mapOut[Out2](f: Nothing => Out2) = this def map[Out2, B](f: (Nothing, Nothing) => (Out2, B)) = this def flatMap[Out2, B](f: (Nothing, Nothing) => Result[Out2, B, Nothing]) = this def orElse[Out2, B](other: => Result[Out2, B, Nothing]) = other } case object Failure extends NoSuccess[Nothing] { def error = throw new ScalaSigParserError("No error") } case class ScalaSigParserError(msg: String) extends RuntimeException(msg) case class Error[+X](error: X) extends NoSuccess[X] { } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/Rule.scala ================================================ package com.codahale.jerkson.util package scalax package rules /**A Rule is a function from some input to a Result. The result may be: * * * @author Andrew Foggin * * Inspired by the Scala parser combinator. */ trait Rule[-In, +Out, +A, +X] extends (In => Result[Out, A, X]) { val factory: Rules import factory._ def as(name: String) = ruleWithName(name, this) def flatMap[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = mapResult { case Success(out, a) => fa2ruleb(a)(out) case Failure => Failure case err@Error(_) => err } def map[B](fa2b: A => B) = flatMap {a => out => Success(out, fa2b(a))} def filter(f: A => Boolean) = flatMap {a => out => if (f(a)) Success(out, a) else Failure } def mapResult[Out2, B, Y](f: Result[Out, A, X] => Result[Out2, B, Y]) = rule { in: In => f(apply(in)) } def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] { val factory = Rule.this.factory lazy val choices = Rule.this :: other :: Nil } def orError[In2 <: In] = this orElse (error[In2]) def |[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]) = orElse(other) def ^^[B](fa2b: A => B) = map(fa2b) def ^^?[B](pf: PartialFunction[A, B]) = filter(pf.isDefinedAt(_)) ^^ pf def ??(pf: PartialFunction[A, Any]) = filter(pf.isDefinedAt(_)) def -^[B](b: B) = map {any => b} /**Maps an Error */ def !^[Y](fx2y: X => Y) = mapResult { case s@Success(_, _) => s case Failure => Failure case Error(x) => Error(fx2y(x)) } def >>[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = flatMap(fa2ruleb) def >->[Out2, B, X2 >: X](fa2resultb: A => Result[Out2, B, X2]) = flatMap { a => any => fa2resultb(a) } def >>?[Out2, B, X2 >: X](pf: PartialFunction[A, Rule[Out, Out2, B, X2]]) = filter(pf isDefinedAt _) flatMap pf def >>&[B, X2 >: X](fa2ruleb: A => Out => Result[Any, B, X2]) = flatMap {a => out => fa2ruleb(a)(out) mapOut {any => out} } def ~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield new ~(a, b) def ~-[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield a def -~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield b def ~++[Out2, B >: A, X2 >: X](next: => Rule[Out, Out2, Seq[B], X2]) = for (a <- this; b <- next) yield a :: b.toList /**Apply the result of this rule to the function returned by the next rule */ def ~>[Out2, B, X2 >: X](next: => Rule[Out, Out2, A => B, X2]) = for (a <- this; fa2b <- next) yield fa2b(a) /**Apply the result of this rule to the function returned by the previous rule */ def <~:[InPrev, B, X2 >: X](prev: => Rule[InPrev, In, A => B, X2]) = for (fa2b <- prev; a <- this) yield fa2b(a) def ~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield new ~(a, b) def ~-![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield a def -~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield b def -[In2 <: In](exclude: => Rule[In2, Any, Any, Any]) = !exclude -~ this /**^~^(f) is equivalent to ^^ { case b1 ~ b2 => f(b1, b2) } */ def ^~^[B1, B2, B >: A <% B1 ~ B2, C](f: (B1, B2) => C) = map {a => (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)} } /**^~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 => f(b1, b2, b3) } */ def ^~~^[B1, B2, B3, B >: A <% B1 ~ B2 ~ B3, C](f: (B1, B2, B3) => C) = map { a => (a: B1 ~ B2 ~ B3) match {case b1 ~ b2 ~ b3 => f(b1, b2, b3)} } /**^~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4) } */ def ^~~~^[B1, B2, B3, B4, B >: A <% B1 ~ B2 ~ B3 ~ B4, C](f: (B1, B2, B3, B4) => C) = map { a => (a: B1 ~ B2 ~ B3 ~ B4) match {case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4)} } /**^~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5) } */ def ^~~~~^[B1, B2, B3, B4, B5, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5, C](f: (B1, B2, B3, B4, B5) => C) = map { a => (a: B1 ~ B2 ~ B3 ~ B4 ~ B5) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5)} } /**^~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) } */ def ^~~~~~^[B1, B2, B3, B4, B5, B6, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6, C](f: (B1, B2, B3, B4, B5, B6) => C) = map { a => (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6)} } /**^~~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) } */ def ^~~~~~~^[B1, B2, B3, B4, B5, B6, B7, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7, C](f: (B1, B2, B3, B4, B5, B6, B7) => C) = map { a => (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 ~ b7 => f(b1, b2, b3, b4, b5, b6, b7)} } /**>~>(f) is equivalent to >> { case b1 ~ b2 => f(b1, b2) } */ def >~>[Out2, B1, B2, B >: A <% B1 ~ B2, C, X2 >: X](f: (B1, B2) => Out => Result[Out2, C, X2]) = flatMap { a => (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)} } /**^-^(f) is equivalent to ^^ { b2 => b1 => f(b1, b2) } */ def ^-^[B1, B2 >: A, C](f: (B1, B2) => C) = map {b2: B2 => b1: B1 => f(b1, b2) } /**^~>~^(f) is equivalent to ^^ { case b2 ~ b3 => b1 => f(b1, b2, b3) } */ def ^~>~^[B1, B2, B3, B >: A <% B2 ~ B3, C](f: (B1, B2, B3) => C) = map {a => (a: B2 ~ B3) match {case b2 ~ b3 => b1: B1 => f(b1, b2, b3)} } } trait Choice[-In, +Out, +A, +X] extends Rule[In, Out, A, X] { def choices: List[Rule[In, Out, A, X]] def apply(in: In) = { def oneOf(list: List[Rule[In, Out, A, X]]): Result[Out, A, X] = list match { case Nil => Failure case first :: rest => first(in) match { case Failure => oneOf(rest) case result => result } } oneOf(choices) } override def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] { val factory = Choice.this.factory lazy val choices = Choice.this.choices ::: other :: Nil } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/Rules.scala ================================================ package com.codahale.jerkson.util package scalax package rules trait Name { def name: String override def toString = name } /**A factory for rules. * * @author Andrew Foggin * * Inspired by the Scala parser combinator. */ trait Rules { implicit def rule[In, Out, A, X](f: In => Result[Out, A, X]): Rule[In, Out, A, X] = new DefaultRule(f) implicit def inRule[In, Out, A, X](rule: Rule[In, Out, A, X]): InRule[In, Out, A, X] = new InRule(rule) implicit def seqRule[In, A, X](rule: Rule[In, In, A, X]): SeqRule[In, A, X] = new SeqRule(rule) def from[In] = new { def apply[Out, A, X](f: In => Result[Out, A, X]) = rule(f) } def state[s] = new StateRules { type S = s val factory = Rules.this } def success[Out, A](out: Out, a: A) = rule {in: Any => Success(out, a)} def failure = rule {in: Any => Failure} def error[In] = rule {in: In => Error(in)} def error[X](err: X) = rule {in: Any => Error(err)} def oneOf[In, Out, A, X](rules: Rule[In, Out, A, X]*): Rule[In, Out, A, X] = new Choice[In, Out, A, X] { val factory = Rules.this val choices = rules.toList } def ruleWithName[In, Out, A, X](_name: String, f: In => Result[Out, A, X]): Rule[In, Out, A, X] with Name = new DefaultRule(f) with Name { val name = _name } class DefaultRule[In, Out, A, X](f: In => Result[Out, A, X]) extends Rule[In, Out, A, X] { val factory = Rules.this def apply(in: In) = f(in) } /**Converts a rule into a function that throws an Exception on failure. */ def expect[In, Out, A, Any](rule: Rule[In, Out, A, Any]): In => A = (in) => rule(in) match { case Success(_, a) => a case Failure => throw new ScalaSigParserError("Unexpected failure") case Error(x) => throw new ScalaSigParserError("Unexpected error: " + x) } } /**A factory for rules that apply to a particular context. * * @requires S the context to which rules apply. * * @author Andrew Foggin * * Inspired by the Scala parser combinator. */ trait StateRules { type S type Rule[+A, +X] = rules.Rule[S, S, A, X] val factory: Rules import factory._ def apply[A, X](f: S => Result[S, A, X]) = rule(f) def unit[A](a: => A) = apply {s => Success(s, a)} def read[A](f: S => A) = apply {s => Success(s, f(s))} def get = apply {s => Success(s, s)} def set(s: => S) = apply {oldS => Success(s, oldS)} def update(f: S => S) = apply {s => Success(s, f(s))} def nil = unit(Nil) def none = unit(None) /**Create a rule that identities if f(in) is true. */ def cond(f: S => Boolean) = get filter f /**Create a rule that succeeds if all of the given rules succeed. @param rules the rules to apply in sequence. */ def allOf[A, X](rules: Seq[Rule[A, X]]) = { def rep(in: S, rules: List[Rule[A, X]], results: List[A]): Result[S, List[A], X] = { rules match { case Nil => Success(in, results.reverse) case rule :: tl => rule(in) match { case Failure => Failure case Error(x) => Error(x) case Success(out, v) => rep(out, tl, v :: results) } } } in: S => rep(in, rules.toList, Nil) } /**Create a rule that succeeds with a list of all the provided rules that succeed. @param rules the rules to apply in sequence. */ def anyOf[A, X](rules: Seq[Rule[A, X]]) = allOf(rules.map(_ ?)) ^^ { opts => opts.flatMap(x => x) } /**Repeatedly apply a rule from initial value until finished condition is met. */ def repeatUntil[T, X](rule: Rule[T => T, X])(finished: T => Boolean) (initial: T) = apply { // more compact using HoF but written this way so it's tail-recursive def rep(in: S, t: T): Result[S, T, X] = { if (finished(t)) Success(in, t) else rule(in) match { case Success(out, f) => rep(out, f(t)) case Failure => Failure case Error(x) => Error(x) } } in => rep(in, initial) } } trait RulesWithState extends Rules with StateRules { val factory = this } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/SeqRule.scala ================================================ package com.codahale.jerkson.util package scalax package rules /** * A workaround for the difficulties of dealing with * a contravariant 'In' parameter type... */ class InRule[In, +Out, +A, +X](rule: Rule[In, Out, A, X]) { def mapRule[Out2, B, Y](f: Result[Out, A, X] => In => Result[Out2, B, Y]): Rule[In, Out2, B, Y] = rule.factory.rule { in: In => f(rule(in))(in) } /**Creates a rule that succeeds only if the original rule would fail on the given context. */ def unary_! : Rule[In, In, Unit, Nothing] = mapRule { case Success(_, _) => in: In => Failure case _ => in: In => Success(in, ()) } /**Creates a rule that succeeds if the original rule succeeds, but returns the original input. */ def & : Rule[In, In, A, X] = mapRule { case Success(_, a) => in: In => Success(in, a) case Failure => in: In => Failure case Error(x) => in: In => Error(x) } } class SeqRule[S, +A, +X](rule: Rule[S, S, A, X]) { import rule.factory._ def ? = rule mapRule { case Success(out, a) => in: S => Success(out, Some(a)) case Failure => in: S => Success(in, None) case Error(x) => in: S => Error(x) } /**Creates a rule that always succeeds with a Boolean value. * Value is 'true' if this rule succeeds, 'false' otherwise */ def -? = ? map {_ isDefined} def * = from[S] { // tail-recursive function with reverse list accumulator def rep(in: S, acc: List[A]): Result[S, List[A], X] = rule(in) match { case Success(out, a) => rep(out, a :: acc) case Failure => Success(in, acc.reverse) case err: Error[_] => err } in => rep(in, Nil) } def + = rule ~++ * def ~>?[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f ?) yield fs.foldLeft[B](a) {(b, f) => f(b) } def ~>*[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f *) yield fs.foldLeft[B](a) {(b, f) => f(b) } def ~*~[B >: A, X2 >: X](join: => Rule[S, S, (B, B) => B, X2]) = { this ~>* (for (f <- join; a <- rule) yield f(_: B, a)) } /**Repeats this rule one or more times with a separator (which is discarded) */ def +/[X2 >: X](sep: => Rule[S, S, Any, X2]) = rule ~++ (sep -~ rule *) /**Repeats this rule zero or more times with a separator (which is discarded) */ def */[X2 >: X](sep: => Rule[S, S, Any, X2]) = +/(sep) | state[S].nil def *~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end *) ~- end def +~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end +) ~- end /**Repeats this rule num times */ def times(num: Int): Rule[S, S, Seq[A], X] = from[S] { val result = new collection.mutable.ArraySeq[A](num) // more compact using HoF but written this way so it's tail-recursive def rep(i: Int, in: S): Result[S, Seq[A], X] = { if (i == num) Success(in, result) else rule(in) match { case Success(out, a) => { result(i) = a rep(i + 1, out) } case Failure => Failure case err: Error[_] => err } } in => rep(0, in) } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/ClassFileParser.scala ================================================ package com.codahale.jerkson.util package scalax package rules package scalasig import java.io.IOException import scala._ import scala.Predef._ object ByteCode { def apply(bytes: Array[Byte]) = new ByteCode(bytes, 0, bytes.length) def forClass(clazz: Class[_]) = { val name = clazz.getName val subPath = name.substring(name.lastIndexOf('.') + 1) + ".class" val in = clazz.getResourceAsStream(subPath) try { var rest = in.available() val bytes = new Array[Byte](rest) while (rest > 0) { val res = in.read(bytes, bytes.length - rest, rest) if (res == -1) throw new IOException("read error") rest -= res } ByteCode(bytes) } finally { in.close() } } } /**Represents a chunk of raw bytecode. Used as input for the parsers */ class ByteCode(val bytes: Array[Byte], val pos: Int, val length: Int) { assert(pos >= 0 && length >= 0 && pos + length <= bytes.length) def nextByte = if (length == 0) Failure else Success(drop(1), bytes(pos)) def next(n: Int) = if (length >= n) Success(drop(n), take(n)) else Failure def take(n: Int) = new ByteCode(bytes, pos, n) def drop(n: Int) = new ByteCode(bytes, pos + n, length - n) def fold[X](x: X)(f: (X, Byte) => X): X = { var result = x var i = pos while (i < pos + length) { result = f(result, bytes(i)) i += 1 } result } override def toString = length + " bytes" def toInt = fold(0) {(x, b) => (x << 8) + (b & 0xFF)} def toLong = fold(0L) {(x, b) => (x << 8) + (b & 0xFF)} private val utf8: Array[Byte] => Array[Char] = { // scala 2.8.1 try { val m1 = io.Codec.getClass.getDeclaredMethod("toUTF8", classOf[Array[Byte]]); {f => m1.invoke(io.Codec, f).asInstanceOf[Array[Char]]} } catch { case e: NoSuchMethodException => { // scala 2.9.0.RC1 val m2 = io.Codec.getClass.getDeclaredMethod("fromUTF8", classOf[Array[Byte]]); {f => m2.invoke(io.Codec, f).asInstanceOf[Array[Char]]} } } } /** * Transforms array subsequence of the current buffer into the UTF8 String and * stores and array of bytes for the decompiler */ def fromUTF8StringAndBytes: StringBytesPair = { val chunk: Array[Byte] = bytes drop pos take length StringBytesPair(utf8(chunk).mkString, chunk) } def byte(i: Int) = bytes(pos) & 0xFF } /** * The wrapper for decode UTF-8 string */ case class StringBytesPair(string: String, bytes: Array[Byte]) /**Provides rules for parsing byte-code. */ trait ByteCodeReader extends RulesWithState { type S = ByteCode type Parser[A] = Rule[A, String] val byte = apply(_ nextByte) val u1 = byte ^^ (_ & 0xFF) val u2 = bytes(2) ^^ (_ toInt) val u4 = bytes(4) ^^ (_ toInt) // should map to Long?? def bytes(n: Int) = apply(_ next n) } object ClassFileParser extends ByteCodeReader { def parse(byteCode: ByteCode) = expect(classFile)(byteCode) def parseAnnotations(byteCode: ByteCode) = expect(annotations)(byteCode) val magicNumber = (u4 filter (_ == 0xCAFEBABE)) | error("Not a valid class file") val version = u2 ~ u2 ^^ {case minor ~ major => (major, minor)} val constantPool = (u2 ^^ ConstantPool) >> repeatUntil(constantPoolEntry)(_ isFull) // NOTE currently most constants just evaluate to a string description // TODO evaluate to useful values val utf8String = (u2 >> bytes) ^^ add1 {raw => pool => raw.fromUTF8StringAndBytes } val intConstant = u4 ^^ add1 {x => pool => x} val floatConstant = bytes(4) ^^ add1 {raw => pool => "Float: TODO"} val longConstant = bytes(8) ^^ add2 {raw => pool => raw.toLong} val doubleConstant = bytes(8) ^^ add2 {raw => pool => "Double: TODO"} val classRef = u2 ^^ add1 {x => pool => "Class: " + pool(x)} val stringRef = u2 ^^ add1 {x => pool => "String: " + pool(x)} val fieldRef = memberRef("Field") val methodRef = memberRef("Method") val interfaceMethodRef = memberRef("InterfaceMethod") val nameAndType = u2 ~ u2 ^^ add1 { case name ~ descriptor => pool => "NameAndType: " + pool(name) + ", " + pool(descriptor) } val constantPoolEntry = u1 >> { case 1 => utf8String case 3 => intConstant case 4 => floatConstant case 5 => longConstant case 6 => doubleConstant case 7 => classRef case 8 => stringRef case 9 => fieldRef case 10 => methodRef case 11 => interfaceMethodRef case 12 => nameAndType } val interfaces = u2 >> u2.times // bytes are parametrizes by the length, declared in u4 section val attribute = u2 ~ (u4 >> bytes) ^~^ Attribute // parse attributes u2 times val attributes = u2 >> attribute.times // parse runtime-visible annotations abstract class ElementValue case class AnnotationElement(elementNameIndex: Int, elementValue: ElementValue) case class ConstValueIndex(index: Int) extends ElementValue case class EnumConstValue(typeNameIndex: Int, constNameIndex: Int) extends ElementValue case class ClassInfoIndex(index: Int) extends ElementValue case class Annotation(typeIndex: Int, elementValuePairs: Seq[AnnotationElement]) extends ElementValue case class ArrayValue(values: Seq[ElementValue]) extends ElementValue def element_value: Parser[ElementValue] = u1 >> { case 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z' | 's' => u2 ^^ ConstValueIndex case 'e' => u2 ~ u2 ^~^ EnumConstValue case 'c' => u2 ^^ ClassInfoIndex case '@' => annotation //nested annotation case '[' => u2 >> element_value.times ^^ ArrayValue } val element_value_pair = u2 ~ element_value ^~^ AnnotationElement val annotation: Parser[Annotation] = u2 ~ (u2 >> element_value_pair.times) ^~^ Annotation val annotations = u2 >> annotation.times val field = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Field val fields = u2 >> field.times val method = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Method val methods = u2 >> method.times val header = magicNumber -~ u2 ~ u2 ~ constantPool ~ u2 ~ u2 ~ u2 ~ interfaces ^~~~~~~^ ClassFileHeader val classFile = header ~ fields ~ methods ~ attributes ~- !u1 ^~~~^ ClassFile // TODO create a useful object, not just a string def memberRef(description: String) = u2 ~ u2 ^^ add1 { case classRef ~ nameAndTypeRef => pool => description + ": " + pool(classRef) + ", " + pool(nameAndTypeRef) } def add1[T](f: T => ConstantPool => Any)(raw: T) (pool: ConstantPool) = pool add f(raw) def add2[T](f: T => ConstantPool => Any)(raw: T) (pool: ConstantPool) = pool add f(raw) add {pool => ""} } case class ClassFile( header: ClassFileHeader, fields: Seq[Field], methods: Seq[Method], attributes: Seq[Attribute]) { def majorVersion = header.major def minorVersion = header.minor def className = constant(header.classIndex) def superClass = constant(header.superClassIndex) def interfaces = header.interfaces.map(constant) def constant(index: Int) = header.constants(index) match { case StringBytesPair(str, _) => str case z => z } def constantWrapped(index: Int) = header.constants(index) def attribute(name: String) = attributes.find { attrib => constant(attrib.nameIndex) == name } val RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations" def annotations = (attributes.find( attr => constant(attr.nameIndex) == RUNTIME_VISIBLE_ANNOTATIONS ) .map( attr => ClassFileParser.parseAnnotations(attr.byteCode) )) def annotation(name: String) = annotations.flatMap( seq => seq.find( annot => constant(annot.typeIndex) == name ) ) } case class Attribute(nameIndex: Int, byteCode: ByteCode) case class Field(flags: Int, nameIndex: Int, descriptorIndex: Int, attributes: Seq[Attribute]) case class Method(flags: Int, nameIndex: Int, descriptorIndex: Int, attributes: Seq[Attribute]) case class ClassFileHeader( minor: Int, major: Int, constants: ConstantPool, flags: Int, classIndex: Int, superClassIndex: Int, interfaces: Seq[Int]) { def constant(index: Int) = constants(index) } case class ConstantPool(len: Int) { val size = len - 1 private val buffer = new scala.collection.mutable.ArrayBuffer[ConstantPool => Any] private val values = Array.fill[Option[Any]](size)(None) def isFull = buffer.length >= size def apply(index: Int) = { // Note constant pool indices are 1-based val i = index - 1 values(i) getOrElse { val value = buffer(i)(this) buffer(i) = null values(i) = Some(value) value } } def add(f: ConstantPool => Any) = { buffer += f this } } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Flags.scala ================================================ package com.codahale.jerkson.util.scalax.rules.scalasig trait Flags { def hasFlag(flag: Long): Boolean def isImplicit = hasFlag(0x00000001) def isFinal = hasFlag(0x00000002) def isPrivate = hasFlag(0x00000004) def isProtected = hasFlag(0x00000008) def isSealed = hasFlag(0x00000010) def isOverride = hasFlag(0x00000020) def isCase = hasFlag(0x00000040) def isAbstract = hasFlag(0x00000080) def isDeferred = hasFlag(0x00000100) def isMethod = hasFlag(0x00000200) def isModule = hasFlag(0x00000400) def isInterface = hasFlag(0x00000800) def isMutable = hasFlag(0x00001000) def isParam = hasFlag(0x00002000) def isPackage = hasFlag(0x00004000) def isDeprecated = hasFlag(0x00008000) def isCovariant = hasFlag(0x00010000) def isCaptured = hasFlag(0x00010000) def isByNameParam = hasFlag(0x00010000) def isContravariant = hasFlag(0x00020000) def isLabel = hasFlag(0x00020000) // method symbol is a label. Set by TailCall def isInConstructor = hasFlag(0x00020000) // class symbol is defined in this/superclass constructor def isAbstractOverride = hasFlag(0x00040000) def isLocal = hasFlag(0x00080000) def isJava = hasFlag(0x00100000) def isSynthetic = hasFlag(0x00200000) def isStable = hasFlag(0x00400000) def isStatic = hasFlag(0x00800000) def isCaseAccessor = hasFlag(0x01000000) def isTrait = hasFlag(0x02000000) def isBridge = hasFlag(0x04000000) def isAccessor = hasFlag(0x08000000) def isSuperAccessor = hasFlag(0x10000000) def isParamAccessor = hasFlag(0x20000000) def isModuleVar = hasFlag(0x40000000) // for variables: is the variable caching a module value def isMonomorphic = hasFlag(0x40000000) // for type symbols: does not have type parameters def isLazy = hasFlag(0x80000000L) // symbol is a lazy val. can't have MUTABLE unless transformed by typer def isError = hasFlag(0x100000000L) def isOverloaded = hasFlag(0x200000000L) def isLifted = hasFlag(0x400000000L) def isMixedIn = hasFlag(0x800000000L) def isExistential = hasFlag(0x800000000L) def isExpandedName = hasFlag(0x1000000000L) def isImplementationClass = hasFlag(0x2000000000L) def isPreSuper = hasFlag(0x2000000000L) } ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/ScalaSig.scala ================================================ package com.codahale.jerkson.util package scalax package rules package scalasig import ClassFileParser.{ConstValueIndex, Annotation} import scala.reflect.generic.ByteCodecs object ScalaSigParser { import CaseClassSigParser.{SCALA_SIG, SCALA_SIG_ANNOTATION, BYTES_VALUE} def scalaSigFromAnnotation(classFile: ClassFile): Option[ScalaSig] = { import classFile._ classFile.annotation(SCALA_SIG_ANNOTATION) map { case Annotation(_, elements) => val bytesElem = elements.find( elem => constant(elem.elementNameIndex) == BYTES_VALUE ).get val bytes = ((bytesElem.elementValue match {case ConstValueIndex(index) => constantWrapped(index)}) .asInstanceOf[StringBytesPair].bytes) val length = ByteCodecs.decode(bytes) ScalaSigAttributeParsers.parse(ByteCode(bytes.take(length))) } } def scalaSigFromAttribute(classFile: ClassFile): Option[ScalaSig] = classFile.attribute(SCALA_SIG).map(_.byteCode).map(ScalaSigAttributeParsers.parse) def parse(classFile: ClassFile): Option[ScalaSig] = { val scalaSig = scalaSigFromAttribute(classFile) scalaSig match { // No entries in ScalaSig attribute implies that the signature is stored in the annotation case Some(ScalaSig(_, _, entries)) if entries.length == 0 => scalaSigFromAnnotation(classFile) case x => x } } def parse(clazz: Class[_]): Option[ScalaSig] = { val byteCode = ByteCode.forClass(clazz) val classFile = ClassFileParser.parse(byteCode) parse(classFile) } } object ScalaSigAttributeParsers extends ByteCodeReader { def parse(byteCode: ByteCode) = expect(scalaSig)(byteCode) val nat = apply { def natN(in: ByteCode, x: Int): Result[ByteCode, Int, Nothing] = in.nextByte match { case Success(out, b) => { val y = (x << 7) + (b & 0x7f) if ((b & 0x80) == 0) Success(out, y) else natN(out, y) } case _ => Failure } in => natN(in, 0) } val rawBytes = nat >> bytes val entry = nat ~ rawBytes val symtab = nat >> entry.times val scalaSig = nat ~ nat ~ symtab ^~~^ ScalaSig val utf8 = read(x => x.fromUTF8StringAndBytes.string) val longValue = read(_ toLong) } case class ScalaSig(majorVersion: Int, minorVersion: Int, table: Seq[Int ~ ByteCode]) extends DefaultMemoisable { case class Entry(index: Int, entryType: Int, byteCode: ByteCode) extends DefaultMemoisable { def scalaSig = ScalaSig.this def setByteCode(byteCode: ByteCode) = Entry(index, entryType, byteCode) } def hasEntry(index: Int) = table isDefinedAt index def getEntry(index: Int) = { val entryType ~ byteCode = table(index) Entry(index, entryType, byteCode) } def parseEntry(index: Int) = applyRule(ScalaSigParsers.parseEntry(ScalaSigEntryParsers.entry)(index)) implicit def applyRule[A](parser: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(parser)(this) override def toString = "ScalaSig version " + majorVersion + "." + minorVersion + { for (i <- 0 until table.size) yield i + ":\t" + parseEntry(i) // + "\n\t" + getEntry(i) }.mkString("\n", "\n", "") lazy val symbols: Seq[Symbol] = ScalaSigParsers.symbols lazy val topLevelClasses: List[ClassSymbol] = ScalaSigParsers.topLevelClasses lazy val topLevelObjects: List[ObjectSymbol] = ScalaSigParsers.topLevelObjects } object ScalaSigParsers extends RulesWithState with MemoisableRules { type S = ScalaSig type Parser[A] = Rule[A, String] val symTab = read(_.table) val size = symTab ^^ (_.size) def entry(index: Int) = memo(("entry", index)) { cond(_ hasEntry index) -~ read(_ getEntry index) >-> { entry => Success(entry, entry.entryType) } } def parseEntry[A](parser: ScalaSigEntryParsers.EntryParser[A]) (index: Int): Parser[A] = entry(index) -~ parser >> {a => entry => Success(entry.scalaSig, a)} def allEntries[A](f: ScalaSigEntryParsers.EntryParser[A]) = size >> { n => anyOf((0 until n) map parseEntry(f)) } lazy val entries = allEntries(ScalaSigEntryParsers.entry) as "entries" lazy val symbols = allEntries(ScalaSigEntryParsers.symbol) as "symbols" lazy val methods = allEntries(ScalaSigEntryParsers.methodSymbol) as "methods" lazy val attributes = allEntries(ScalaSigEntryParsers.attributeInfo) as "attributes" lazy val topLevelClasses = allEntries(ScalaSigEntryParsers.topLevelClass) lazy val topLevelObjects = allEntries(ScalaSigEntryParsers.topLevelObject) } object ScalaSigEntryParsers extends RulesWithState with MemoisableRules { import ScalaSigAttributeParsers.{nat, utf8, longValue} type S = ScalaSig#Entry type EntryParser[A] = Rule[A, String] implicit def byteCodeEntryParser[A](rule: ScalaSigAttributeParsers.Parser[A]): EntryParser[A] = apply { entry => rule(entry.byteCode) mapOut (entry setByteCode _) } def toEntry[A](index: Int) = apply { sigEntry => ScalaSigParsers.entry(index)(sigEntry.scalaSig) } def parseEntry[A](parser: EntryParser[A]) (index: Int) = (toEntry(index) -~ parser) implicit def entryType(code: Int) = key filter (_ == code) val index = read(_.index) val key = read(_.entryType) lazy val entry: EntryParser[Any] = symbol | typeEntry | literal | name | attributeInfo | annotInfo | children | get val ref = byteCodeEntryParser(nat) val termName = 1 -~ utf8 val typeName = 2 -~ utf8 val name = termName | typeName as "name" def refTo[A](rule: EntryParser[A]): EntryParser[A] = ref >>& parseEntry(rule) lazy val nameRef = refTo(name) lazy val symbolRef = refTo(symbol) lazy val typeRef = refTo(typeEntry) lazy val constantRef = refTo(literal) val symbolInfo = nameRef ~ symbolRef ~ nat ~ (symbolRef ?) ~ ref ~ get ^~~~~~^ SymbolInfo def symHeader(key: Int) = (key -~ none | (key + 64) -~ nat) def symbolEntry(key: Int) = symHeader(key) -~ symbolInfo /*************************************************** * Symbol table attribute format: * Symtab = nentries_Nat {Entry} * Entry = 1 TERMNAME len_Nat NameInfo * | 2 TYPENAME len_Nat NameInfo * | 3 NONEsym len_Nat * | 4 TYPEsym len_Nat SymbolInfo * | 5 ALIASsym len_Nat SymbolInfo * | 6 CLASSsym len_Nat SymbolInfo [thistype_Ref] * | 7 MODULEsym len_Nat SymbolInfo * | 8 VALsym len_Nat [defaultGetter_Ref /* no longer needed*/] SymbolInfo [alias_Ref] * | 9 EXTref len_Nat name_Ref [owner_Ref] * | 10 EXTMODCLASSref len_Nat name_Ref [owner_Ref] * | 11 NOtpe len_Nat * | 12 NOPREFIXtpe len_Nat * | 13 THIStpe len_Nat sym_Ref * | 14 SINGLEtpe len_Nat type_Ref sym_Ref * | 15 CONSTANTtpe len_Nat constant_Ref * | 16 TYPEREFtpe len_Nat type_Ref sym_Ref {targ_Ref} * | 17 TYPEBOUNDStpe len_Nat tpe_Ref tpe_Ref * | 18 REFINEDtpe len_Nat classsym_Ref {tpe_Ref} * | 19 CLASSINFOtpe len_Nat classsym_Ref {tpe_Ref} * | 20 METHODtpe len_Nat tpe_Ref {sym_Ref} * | 21 POLYTtpe len_Nat tpe_Ref {sym_Ref} * | 22 IMPLICITMETHODtpe len_Nat tpe_Ref {sym_Ref} /* no longer needed */ * | 52 SUPERtpe len_Nat tpe_Ref tpe_Ref * | 24 LITERALunit len_Nat * | 25 LITERALboolean len_Nat value_Long * | 26 LITERALbyte len_Nat value_Long * | 27 LITERALshort len_Nat value_Long * | 28 LITERALchar len_Nat value_Long * | 29 LITERALint len_Nat value_Long * | 30 LITERALlong len_Nat value_Long * | 31 LITERALfloat len_Nat value_Long * | 32 LITERALdouble len_Nat value_Long * | 33 LITERALstring len_Nat name_Ref * | 34 LITERALnull len_Nat * | 35 LITERALclass len_Nat tpe_Ref * | 36 LITERALenum len_Nat sym_Ref * | 40 SYMANNOT len_Nat sym_Ref AnnotInfoBody * | 41 CHILDREN len_Nat sym_Ref {sym_Ref} * | 42 ANNOTATEDtpe len_Nat [sym_Ref /* no longer needed */] tpe_Ref {annotinfo_Ref} * | 43 ANNOTINFO len_Nat AnnotInfoBody * | 44 ANNOTARGARRAY len_Nat {constAnnotArg_Ref} * | 47 DEBRUIJNINDEXtpe len_Nat level_Nat index_Nat * | 48 EXISTENTIALtpe len_Nat type_Ref {symbol_Ref} */ val noSymbol = 3 -^ NoSymbol val typeSymbol = symbolEntry(4) ^^ TypeSymbol as "typeSymbol" val aliasSymbol = symbolEntry(5) ^^ AliasSymbol as "alias" val classSymbol = symbolEntry(6) ~ (ref ?) ^~^ ClassSymbol as "class" val objectSymbol = symbolEntry(7) ^^ ObjectSymbol as "object" val methodSymbol = symHeader(8) -~ /*(ref?) -~*/ symbolInfo ~ (ref ?) ^~^ MethodSymbol as "method" val extRef = 9 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extRef" val extModClassRef = 10 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extModClassRef" lazy val symbol: EntryParser[Symbol] = oneOf( noSymbol, typeSymbol, aliasSymbol, classSymbol, objectSymbol, methodSymbol, extRef, extModClassRef ) as "symbol" val classSymRef = refTo(classSymbol) val attribTreeRef = ref val typeLevel = nat val typeIndex = nat lazy val typeEntry: EntryParser[Type] = oneOf( 11 -^ NoType, 12 -^ NoPrefixType, 13 -~ symbolRef ^^ ThisType, 14 -~ typeRef ~ symbolRef ^~^ SingleType, 15 -~ constantRef ^^ ConstantType, 16 -~ typeRef ~ symbolRef ~ (typeRef *) ^~~^ TypeRefType, 17 -~ typeRef ~ typeRef ^~^ TypeBoundsType, 18 -~ classSymRef ~ (typeRef *) ^~^ RefinedType, 19 -~ symbolRef ~ (typeRef *) ^~^ ClassInfoType, 20 -~ typeRef ~ (symbolRef *) ^~^ MethodType, 21 -~ typeRef ~ (refTo(typeSymbol) +) ^~^ PolyType, // TODO: make future safe for past by doing the same transformation as in the full unpickler in case we're reading pre-2.9 classfiles 21 -~ typeRef ^^ NullaryMethodType, 22 -~ typeRef ~ (symbolRef *) ^~^ ImplicitMethodType, 42 -~ typeRef ~ (attribTreeRef *) ^~^ AnnotatedType, 51 -~ typeRef ~ symbolRef ~ (attribTreeRef *) ^~~^ AnnotatedWithSelfType, 47 -~ typeLevel ~ typeIndex ^~^ DeBruijnIndexType, 48 -~ typeRef ~ (symbolRef *) ^~^ ExistentialType ) as "type" lazy val literal = oneOf( 24 -^ (), 25 -~ longValue ^^ (_ != 0L), 26 -~ longValue ^^ (_.toByte), 27 -~ longValue ^^ (_.toShort), 28 -~ longValue ^^ (_.toChar), 29 -~ longValue ^^ (_.toInt), 30 -~ longValue ^^ (_.toLong), 31 -~ longValue ^^ (l => java.lang.Float.intBitsToFloat(l.toInt)), 32 -~ longValue ^^ (java.lang.Double.longBitsToDouble), 33 -~ nameRef, 34 -^ null, 35 -~ typeRef ) lazy val attributeInfo = 40 -~ symbolRef ~ typeRef ~ (constantRef ?) ~ (nameRef ~ constantRef *) ^~~~^ AttributeInfo // sym_Ref info_Ref {constant_Ref} {nameRef constantRef} lazy val children = 41 -~ (nat *) ^^ Children //sym_Ref {sym_Ref} lazy val annotInfo = 43 -~ (nat *) ^^ AnnotInfo // attarg_Ref {constant_Ref attarg_Ref} lazy val topLevelClass = classSymbol filter isTopLevelClass lazy val topLevelObject = objectSymbol filter isTopLevel def isTopLevel(symbol: Symbol) = symbol.parent match { case Some(ext: ExternalSymbol) => true case _ => false } def isTopLevelClass(symbol: Symbol) = !symbol.isModule && isTopLevel(symbol) } case class AttributeInfo(symbol: Symbol, typeRef: Type, value: Option[Any], values: Seq[String ~ Any]) // sym_Ref info_Ref {constant_Ref} {nameRef constantRef} case class Children(symbolRefs: Seq[Int]) //sym_Ref {sym_Ref} case class AnnotInfo(refs: Seq[Int]) // attarg_Ref {constant_Ref attarg_Ref} /*************************************************** * | 49 TREE len_Nat 1 EMPTYtree * | 49 TREE len_Nat 2 PACKAGEtree type_Ref sym_Ref mods_Ref name_Ref {tree_Ref} * | 49 TREE len_Nat 3 CLASStree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 4 MODULEtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref * | 49 TREE len_Nat 5 VALDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 6 DEFDEFtree type_Ref sym_Ref mods_Ref name_Ref numtparams_Nat {tree_Ref} numparamss_Nat {numparams_Nat {tree_Ref}} tree_Ref tree_Ref * | 49 TREE len_Nat 7 TYPEDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 8 LABELtree type_Ref sym_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 9 IMPORTtree type_Ref sym_Ref tree_Ref {name_Ref name_Ref} * | 49 TREE len_Nat 11 DOCDEFtree type_Ref sym_Ref string_Ref tree_Ref * | 49 TREE len_Nat 12 TEMPLATEtree type_Ref sym_Ref numparents_Nat {tree_Ref} tree_Ref {tree_Ref} * | 49 TREE len_Nat 13 BLOCKtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 14 CASEtree type_Ref tree_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 15 SEQUENCEtree type_Ref {tree_Ref} * | 49 TREE len_Nat 16 ALTERNATIVEtree type_Ref {tree_Ref} * | 49 TREE len_Nat 17 STARtree type_Ref {tree_Ref} * | 49 TREE len_Nat 18 BINDtree type_Ref sym_Ref name_Ref tree_Ref * | 49 TREE len_Nat 19 UNAPPLYtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 20 ARRAYVALUEtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 21 FUNCTIONtree type_Ref sym_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 22 ASSIGNtree type_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 23 IFtree type_Ref tree_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 24 MATCHtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 25 RETURNtree type_Ref sym_Ref tree_Ref * | 49 TREE len_Nat 26 TREtree type_Ref tree_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 27 THROWtree type_Ref tree_Ref * | 49 TREE len_Nat 28 NEWtree type_Ref tree_Ref * | 49 TREE len_Nat 29 TYPEDtree type_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 30 TYPEAPPLYtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 31 APPLYtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 32 APPLYDYNAMICtree type_Ref sym_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 33 SUPERtree type_Ref sym_Ref tree_Ref name_Ref * | 49 TREE len_Nat 34 THIStree type_Ref sym_Ref name_Ref * | 49 TREE len_Nat 35 SELECTtree type_Ref sym_Ref tree_Ref name_Ref * | 49 TREE len_Nat 36 IDENTtree type_Ref sym_Ref name_Ref * | 49 TREE len_Nat 37 LITERALtree type_Ref constant_Ref * | 49 TREE len_Nat 38 TYPEtree type_Ref * | 49 TREE len_Nat 39 ANNOTATEDtree type_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 40 SINGLETONTYPEtree type_Ref tree_Ref * | 49 TREE len_Nat 41 SELECTFROMTYPEtree type_Ref tree_Ref name_Ref * | 49 TREE len_Nat 42 COMPOUNDTYPEtree type_Ref tree_Ref * | 49 TREE len_Nat 43 APPLIEDTYPEtree type_Ref tree_Ref {tree_Ref} * | 49 TREE len_Nat 44 TYPEBOUNDStree type_Ref tree_Ref tree_Ref * | 49 TREE len_Nat 45 EXISTENTIALTYPEtree type_Ref tree_Ref {tree_Ref} * | 50 MODIFIERS len_Nat flags_Long privateWithin_Ref * SymbolInfo = name_Ref owner_Ref flags_LongNat [privateWithin_Ref] info_Ref * NameInfo = * NumInfo = * Ref = Nat * AnnotInfoBody = info_Ref {annotArg_Ref} {name_Ref constAnnotArg_Ref} * AnnotArg = Tree | Constant * ConstAnnotArg = Constant | AnnotInfo | AnnotArgArray * * len is remaining length after `len'. */ ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Symbol.scala ================================================ package com.codahale.jerkson.util package scalax package rules package scalasig import ScalaSigEntryParsers._ trait Symbol extends Flags { def name: String def parent: Option[Symbol] def children: Seq[Symbol] def path: String = parent.map(_.path + ".").getOrElse("") + name } case object NoSymbol extends Symbol { def name = "" def parent = None def hasFlag(flag: Long) = false def children = Nil } abstract class ScalaSigSymbol extends Symbol { def applyRule[A](rule: EntryParser[A]): A = expect(rule)(entry) def applyScalaSigRule[A](rule: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(rule)(entry.scalaSig) def entry: ScalaSig#Entry def index = entry.index lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (_.parent == Some(this)) lazy val attributes: Seq[AttributeInfo] = applyScalaSigRule(ScalaSigParsers.attributes) filter (_.symbol == this) } case class ExternalSymbol(name: String, parent: Option[Symbol], entry: ScalaSig#Entry) extends ScalaSigSymbol { override def toString = path def hasFlag(flag: Long) = false } case class SymbolInfo(name: String, owner: Symbol, flags: Int, privateWithin: Option[AnyRef], info: Int, entry: ScalaSig#Entry) { def symbolString(any: AnyRef) = any match { case sym: SymbolInfoSymbol => sym.index.toString case other => other.toString } override def toString = name + ", owner=" + symbolString(owner) + ", flags=" + flags.toHexString + ", info=" + info + (privateWithin match { case Some(any) => ", privateWithin=" + symbolString(any) case None => " " }) } abstract class SymbolInfoSymbol extends ScalaSigSymbol { def symbolInfo: SymbolInfo def entry = symbolInfo.entry def name = symbolInfo.name def parent = Some(symbolInfo.owner) def hasFlag(flag: Long) = (symbolInfo.flags & flag) != 0L lazy val infoType = applyRule(parseEntry(typeEntry)(symbolInfo.info)) } case class TypeSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol { override def path = name } case class AliasSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol { override def path = name } case class ClassSymbol(symbolInfo: SymbolInfo, thisTypeRef: Option[Int]) extends SymbolInfoSymbol { lazy val selfType = thisTypeRef.map {(x: Int) => applyRule(parseEntry(typeEntry)(x))} } case class ObjectSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol case class MethodSymbol(symbolInfo: SymbolInfo, aliasRef: Option[Int]) extends SymbolInfoSymbol ================================================ FILE: src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Type.scala ================================================ package com.codahale.jerkson.util package scalax package rules package scalasig abstract class Type case object NoType extends Type case object NoPrefixType extends Type case class ThisType(symbol: Symbol) extends Type case class SingleType(typeRef: Type, symbol: Symbol) extends Type case class ConstantType(constant: Any) extends Type case class TypeRefType(prefix: Type, symbol: Symbol, typeArgs: Seq[Type]) extends Type case class TypeBoundsType(lower: Type, upper: Type) extends Type case class RefinedType(classSym: Symbol, typeRefs: List[Type]) extends Type case class ClassInfoType(symbol: Symbol, typeRefs: Seq[Type]) extends Type case class ClassInfoTypeWithCons(symbol: Symbol, typeRefs: Seq[Type], cons: String) extends Type case class MethodType(resultType: Type, paramSymbols: Seq[Symbol]) extends Type case class NullaryMethodType(resultType: Type) extends Type case class PolyType(typeRef: Type, symbols: Seq[TypeSymbol]) extends Type case class PolyTypeWithCons(typeRef: Type, symbols: Seq[TypeSymbol], cons: String) extends Type case class ImplicitMethodType(resultType: Type, paramSymbols: Seq[Symbol]) extends Type case class AnnotatedType(typeRef: Type, attribTreeRefs: List[Int]) extends Type case class AnnotatedWithSelfType(typeRef: Type, symbol: Symbol, attribTreeRefs: List[Int]) extends Type case class DeBruijnIndexType(typeLevel: Int, typeIndex: Int) extends Type case class ExistentialType(typeRef: Type, symbols: Seq[Symbol]) extends Type ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/ASTTypeSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import com.codahale.jerkson.AST._ import com.codahale.simplespec.Spec import org.junit.Test class ASTTypeSupportSpec extends Spec { class `An AST.JInt` { @Test def `generates a JSON int` = { generate(JInt(15)).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[JInt]("15").must(be(JInt(15))) } @Test def `is parsable from a JSON int as a JValue` = { parse[JValue]("15").must(be(JInt(15))) } } class `An AST.JFloat` { @Test def `generates a JSON int` = { generate(JFloat(15.1)).must(be("15.1")) } @Test def `is parsable from a JSON float` = { parse[JFloat]("15.1").must(be(JFloat(15.1))) } @Test def `is parsable from a JSON float as a JValue` = { parse[JValue]("15.1").must(be(JFloat(15.1))) } } class `An AST.JString` { @Test def `generates a JSON string` = { generate(JString("woo")).must(be("\"woo\"")) } @Test def `is parsable from a JSON string` = { parse[JString]("\"woo\"").must(be(JString("woo"))) } @Test def `is parsable from a JSON string as a JValue` = { parse[JValue]("\"woo\"").must(be(JString("woo"))) } } class `An AST.JNull` { @Test def `generates a JSON null` = { generate(JNull).must(be("null")) } @Test def `is parsable from a JSON null` = { parse[JNull.type]("null").must(be(JNull)) } @Test def `is parsable from a JSON null as a JValue` = { parse[JValue]("null").must(be(JNull)) } } class `An AST.JBoolean` { @Test def `generates a JSON true` = { generate(JBoolean(true)).must(be("true")) } @Test def `generates a JSON false` = { generate(JBoolean(false)).must(be("false")) } @Test def `is parsable from a JSON true` = { parse[JBoolean]("true").must(be(JBoolean(true))) } @Test def `is parsable from a JSON false` = { parse[JBoolean]("false").must(be(JBoolean(false))) } @Test def `is parsable from a JSON true as a JValue` = { parse[JValue]("true").must(be(JBoolean(true))) } @Test def `is parsable from a JSON false as a JValue` = { parse[JValue]("false").must(be(JBoolean(false))) } } class `An AST.JArray of JInts` { @Test def `generates a JSON array of ints` = { generate(JArray(List(JInt(1), JInt(2), JInt(3)))).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[JArray]("[1,2,3]").must(be(JArray(List(JInt(1), JInt(2), JInt(3))))) } @Test def `is parsable from a JSON array of ints as a JValue` = { parse[JValue]("[1,2,3]").must(be(JArray(List(JInt(1), JInt(2), JInt(3))))) } } class `An AST.JObject` { val obj = JObject(List(JField("id", JInt(1)), JField("name", JString("Coda")))) @Test def `generates a JSON object with matching field values` = { generate(obj).must(be("""{"id":1,"name":"Coda"}""")) } @Test def `is parsable from a JSON object` = { parse[JObject]("""{"id":1,"name":"Coda"}""").must(be(obj)) } @Test def `is parsable from a JSON object as a JValue` = { parse[JValue]("""{"id":1,"name":"Coda"}""").must(be(obj)) } @Test def `is parsable from an empty JSON object` = { parse[JObject]("""{}""").must(be(JObject(Nil))) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/BasicTypeSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.simplespec.Spec import com.codahale.jerkson.Json._ import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.JsonNode import org.junit.Test class BasicTypeSupportSpec extends Spec { class `A Byte` { @Test def `generates a JSON int` = { generate(15.toByte).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[Byte]("15").must(be(15)) } } class `A Short` { @Test def `generates a JSON int` = { generate(15.toShort).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[Short]("15").must(be(15)) } } class `An Int` { @Test def `generates a JSON int` = { generate(15).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[Int]("15").must(be(15)) } } class `A Long` { @Test def `generates a JSON int` = { generate(15L).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[Long]("15").must(be(15L)) } } class `A BigInt` { @Test def `generates a JSON int` = { generate(BigInt(15)).must(be("15")) } @Test def `is parsable from a JSON int` = { parse[BigInt]("15").must(be(BigInt(15))) } @Test def `is parsable from a JSON string` = { parse[BigInt]("\"15\"").must(be(BigInt(15))) } } class `A Float` { @Test def `generates a JSON float` = { generate(15.1F).must(be("15.1")) } @Test def `is parsable from a JSON float` = { parse[Float]("15.1").must(be(15.1F)) } } class `A Double` { @Test def `generates a JSON float` = { generate(15.1).must(be("15.1")) } @Test def `is parsable from a JSON float` = { parse[Double]("15.1").must(be(15.1D)) } } class `A BigDecimal` { @Test def `generates a JSON float` = { generate(BigDecimal(15.5)).must(be("15.5")) } @Test def `is parsable from a JSON float` = { parse[BigDecimal]("15.5").must(be(BigDecimal(15.5))) } @Test def `is parsable from a JSON int` = { parse[BigDecimal]("15").must(be(BigDecimal(15.0))) } } class `A String` { @Test def `generates a JSON string` = { generate("woo").must(be("\"woo\"")) } @Test def `is parsable from a JSON string` = { parse[String]("\"woo\"").must(be("woo")) } } class `A StringBuilder` { @Test def `generates a JSON string` = { generate(new StringBuilder("foo")).must(be("\"foo\"")) } @Test def `is parsable from a JSON string` = { parse[StringBuilder]("\"foo\"").toString().must(be("foo")) } } class `A null Object` { @Test def `generates a JSON null` = { generate[Object](null).must(be("null")) } @Test def `is parsable from a JSON null` = { parse[Object]("null").must(be(not(notNull))) } } class `A Boolean` { @Test def `generates a JSON true` = { generate(true).must(be("true")) } @Test def `generates a JSON false` = { generate(false).must(be("false")) } @Test def `is parsable from a JSON true` = { parse[Boolean]("true").must(be(true)) } @Test def `is parsable from a JSON false` = { parse[Boolean]("false").must(be(false)) } } class `A Some[Int]` { @Test def `generates a JSON int` = { generate(Some(12)).must(be("12")) } @Test def `is parsable from a JSON int as an Option[Int]` = { parse[Option[Int]]("12").must(be(Some(12))) } } class `A None` { @Test def `generates a JSON null` = { generate(None).must(be("null")) } @Test def `is parsable from a JSON null as an Option[Int]` = { parse[Option[Int]]("null").must(be(None)) } } class `A Left[String]` { @Test def `generates a JSON string` = { generate(Left("woo")).must(be("\"woo\"")) } @Test def `is parsable from a JSON string as an Either[String, Int]` = { parse[Either[String, Int]]("\"woo\"").must(be(Left("woo"))) } } class `A Right[String]` { @Test def `generates a JSON string` = { generate(Right("woo")).must(be("\"woo\"")) } @Test def `is parsable from a JSON string as an Either[Int, String]` = { parse[Either[Int, String]]("\"woo\"").must(be(Right("woo"))) } } class `A JsonNode` { @Test def `generates whatever the JsonNode is` = { generate(new IntNode(2)).must(be("2")) } @Test def `is parsable from a JSON AST node` = { parse[JsonNode]("2").must(be(new IntNode(2))) } @Test def `is parsable from a JSON AST node as a specific type` = { parse[IntNode]("2").must(be(new IntNode(2))) } @Test def `is itself parsable` = { parse[Int](new IntNode(2)).must(be(2)) } } class `An Array[Int]` { @Test def `generates a JSON array of ints` = { generate(Array(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Array[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Array[Int]]("[]").toList.must(be(List.empty)) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import com.codahale.simplespec.Spec import com.codahale.jerkson.ParsingException import com.fasterxml.jackson.databind.node.IntNode import org.junit.Test class CaseClassSupportSpec extends Spec { class `A basic case class` { @Test def `generates a JSON object with matching field values` = { generate(CaseClass(1, "Coda")).must(be("""{"id":1,"name":"Coda"}""")) } @Test def `is parsable from a JSON object with corresponding fields` = { parse[CaseClass]("""{"id":1,"name":"Coda"}""").must(be(CaseClass(1, "Coda"))) } @Test def `is parsable from a JSON object with extra fields` = { parse[CaseClass]("""{"id":1,"name":"Coda","derp":100}""").must(be(CaseClass(1, "Coda"))) } @Test def `is not parsable from an incomplete JSON object` = { evaluating { parse[CaseClass]("""{"id":1}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [id, name], but found [id].""")) } } class `A case class with lazy fields` { @Test def `generates a JSON object with those fields evaluated` = { generate(CaseClassWithLazyVal(1)).must(be("""{"id":1,"woo":"yeah"}""")) } @Test def `is parsable from a JSON object without those fields` = { parse[CaseClassWithLazyVal]("""{"id":1}""").must(be(CaseClassWithLazyVal(1))) } @Test def `is not parsable from an incomplete JSON object` = { evaluating { parse[CaseClassWithLazyVal]("""{}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found [].""")) } } class `A case class with ignored members` { @Test def `generates a JSON object without those fields` = { generate(CaseClassWithIgnoredField(1)).must(be("""{"id":1}""")) generate(CaseClassWithIgnoredFields(1)).must(be("""{"id":1}""")) } @Test def `is parsable from a JSON object without those fields` = { parse[CaseClassWithIgnoredField]("""{"id":1}""").must(be(CaseClassWithIgnoredField(1))) parse[CaseClassWithIgnoredFields]("""{"id":1}""").must(be(CaseClassWithIgnoredFields(1))) } @Test def `is not parsable from an incomplete JSON object` = { evaluating { parse[CaseClassWithIgnoredField]("""{}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found [].""")) evaluating { parse[CaseClassWithIgnoredFields]("""{}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found [].""")) } } class `A case class with transient members` { @Test def `generates a JSON object without those fields` = { generate(CaseClassWithTransientField(1)).must(be("""{"id":1}""")) } @Test def `is parsable from a JSON object without those fields` = { parse[CaseClassWithTransientField]("""{"id":1}""").must(be(CaseClassWithTransientField(1))) } @Test def `is not parsable from an incomplete JSON object` = { evaluating { parse[CaseClassWithTransientField]("""{}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found [].""")) } } class `A case class with an overloaded field` { @Test def `generates a JSON object with the nullary version of that field` = { generate(CaseClassWithOverloadedField(1)).must(be("""{"id":1}""")) } } class `A case class with an Option[String] member` { @Test def `generates a field if the member is Some` = { generate(CaseClassWithOption(Some("what"))).must(be("""{"value":"what"}""")) } @Test def `is parsable from a JSON object with that field` = { parse[CaseClassWithOption]("""{"value":"what"}""").must(be(CaseClassWithOption(Some("what")))) } @Test def `doesn't generate a field if the member is None` = { generate(CaseClassWithOption(None)).must(be("""{}""")) } @Test def `is parsable from a JSON object without that field` = { parse[CaseClassWithOption]("""{}""").must(be(CaseClassWithOption(None))) } @Test def `is parsable from a JSON object with a null value for that field` = { parse[CaseClassWithOption]("""{"value":null}""").must(be(CaseClassWithOption(None))) } } class `A case class with a JsonNode member` { @Test def `generates a field of the given type` = { generate(CaseClassWithJsonNode(new IntNode(2))).must(be("""{"value":2}""")) } } class `A case class with members of all ScalaSig types` { val json = """ { "map": { "one": "two" }, "set": [1, 2, 3], "string": "woo", "list": [4, 5, 6], "seq": [7, 8, 9], "sequence": [10, 11, 12], "collection": [13, 14, 15], "indexedSeq": [16, 17, 18], "randomAccessSeq": [19, 20, 21], "vector": [22, 23, 24], "bigDecimal": 12.0, "bigInt": 13, "int": 1, "long": 2, "char": "x", "bool": false, "short": 14, "byte": 15, "float": 34.5, "double": 44.9, "any": true, "anyRef": "wah", "intMap": { "1": "1" }, "longMap": { "2": 2 } } """ @Test def `is parsable from a JSON object with those fields` = { parse[CaseClassWithAllTypes](json).must(be( CaseClassWithAllTypes( map = Map("one" -> "two"), set = Set(1, 2, 3), string = "woo", list = List(4, 5, 6), seq = Seq(7, 8, 9), indexedSeq = IndexedSeq(16, 17, 18), vector = Vector(22, 23, 24), bigDecimal = BigDecimal("12.0"), bigInt = BigInt("13"), int = 1, long = 2L, char = 'x', bool = false, short = 14, byte = 15, float = 34.5f, double = 44.9d, any = true, anyRef = "wah", intMap = Map(1 -> 1), longMap = Map(2L -> 2L) ) )) } } class `A case class nested inside of an object` { @Test def `is parsable from a JSON object` = { parse[OuterObject.NestedCaseClass]("""{"id": 1}""").must(be(OuterObject.NestedCaseClass(1))) } } class `A case class nested inside of an object nested inside of an object` { @Test def `is parsable from a JSON object` = { parse[OuterObject.InnerObject.SuperNestedCaseClass]("""{"id": 1}""").must(be(OuterObject.InnerObject.SuperNestedCaseClass(1))) } } class `A case class with two constructors` { @Test def `is parsable from a JSON object with the same parameters as the case accessor` = { parse[CaseClassWithTwoConstructors]("""{"id":1,"name":"Bert"}""").must(be(CaseClassWithTwoConstructors(1, "Bert"))) } @Test def `is parsable from a JSON object which works with the second constructor` = { evaluating { parse[CaseClassWithTwoConstructors]("""{"id":1}""") }.must(throwA[ParsingException]) } } class `A case class with snake-cased fields` { @Test def `is parsable from a snake-cased JSON object` = { parse[CaseClassWithSnakeCase]("""{"one_thing":"yes","two_thing":"good"}""").must(be(CaseClassWithSnakeCase("yes", "good"))) } @Test def `generates a snake-cased JSON object` = { generate(CaseClassWithSnakeCase("yes", "good")).must(be("""{"one_thing":"yes","two_thing":"good"}""")) } @Test def `throws errors with the snake-cased field names present` = { evaluating { parse[CaseClassWithSnakeCase]("""{"one_thing":"yes"}""") }.must(throwA[ParsingException]("Invalid JSON. Needed [one_thing, two_thing], but found [one_thing].")) } } class `A case class with array members` { @Test def `is parsable from a JSON object` = { val c = parse[CaseClassWithArrays]("""{"one":"1","two":["a","b","c"],"three":[1,2,3]}""") c.one.must(be("1")) c.two.must(be(Array("a", "b", "c"))) c.three.must(be(Array(1, 2, 3))) } @Test def `generates a JSON object` = { generate(CaseClassWithArrays("1", Array("a", "b", "c"), Array(1, 2, 3))).must(be( """{"one":"1","two":["a","b","c"],"three":[1,2,3]}""" )) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/CollectionSupportSpec.scala ================================================ package com.codahale.jerkson.tests import scala.collection._ import com.codahale.jerkson.Json._ import com.codahale.simplespec.Spec import org.junit.{Ignore, Test} class CollectionSupportSpec extends Spec { class `A collection.BitSet` { @Test def `generates a JSON array of ints` = { generate(BitSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3))) } } class `A collection.Iterator[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).iterator).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Iterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Iterator[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A collection.Traversable[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).toTraversable).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Traversable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Traversable[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A collection.BufferedIterator[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).iterator.buffered).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[BufferedIterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[BufferedIterator[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A collection.Iterable[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).toIterable).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Iterable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Iterable[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A collection.Set[Int]` { @Test def `generates a JSON array of ints` = { generate(Set(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Set[Int]]("[1,2,3]").must(be(Set(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Set[Int]]("[]").must(be(Set.empty[Int])) } } class `A collection.Map[String, Int]` { @Test def `generates a JSON object with int field values` = { generate(Map("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[Map[String, Int]]("""{"one":1,"two":2}""").must(be(Map("one" -> 1, "two" -> 2))) } @Test def `is parsable from an empty JSON object` = { parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int])) } } class `A collection.IndexedSeq[Int]` { @Test def `generates a JSON array of ints` = { generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty)) } } class `A collection.Seq[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Seq[Int]]("[]").must(be(Seq.empty[Int])) } } class `A collection.SortedMap[String, Int]` { @Test def `generates a JSON object with int field values` = { generate(SortedMap("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}""")) } // TODO: 6/1/11 -- figure out how to deserialize SortedMap instances /** * I think all this would take is a mapping from Class[_] to Ordering, which * would need to have hard-coded the various primitive types, and then add * support for Ordered and Comparable classes. Once we have the Ordering, * we can pass it in manually to a builder. */ @Ignore @Test def `is parsable from a JSON object with int field values` = { parse[SortedMap[String, Int]]("""{"one":1,"two":2}""").must(be(SortedMap("one" -> 1, "two" -> 2))) } @Ignore @Test def `is parsable from an empty JSON object` = { parse[SortedMap[String, Int]]("{}").must(be(SortedMap.empty[String, Int])) } } class `A collection.SortedSet[Int]` { @Test def `generates a JSON array of ints` = { generate(SortedSet(1, 2, 3)).must(be("[1,2,3]")) } // TODO: 6/1/11 -- figure out how to deserialize SortedMap instances /** * I think all this would take is a mapping from Class[_] to Ordering, which * would need to have hard-coded the various primitive types, and then add * support for Ordered and Comparable classes. Once we have the Ordering, * we can pass it in manually to a builder. */ @Ignore @Test def `is parsable from a JSON array of ints` = { parse[SortedSet[Int]]("[1,2,3]").must(be(SortedSet(1, 2, 3))) } @Ignore @Test def `is parsable from an empty JSON array` = { parse[SortedSet[Int]]("[]").must(be(SortedSet.empty[Int])) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/DefaultCollectionSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import com.codahale.simplespec.Spec import com.codahale.jerkson.ParsingException import org.junit.{Ignore, Test} class DefaultCollectionSupportSpec extends Spec { class `A Range` { @Test def `generates a JSON object` = { generate(Range.inclusive(1, 4, 3)).must(be("""{"start":1,"end":4,"step":3,"inclusive":true}""")) } @Test def `generates a JSON object without the inclusive field if it's exclusive` = { generate(Range(1, 4, 3)).must(be("""{"start":1,"end":4,"step":3}""")) } @Test def `generates a JSON object without the step field if it's 1` = { generate(Range(1, 4)).must(be("""{"start":1,"end":4}""")) } @Test def `is parsable from a JSON object` = { parse[Range]("""{"start":1,"end":4,"step":3,"inclusive":true}""").must(be(Range.inclusive(1, 4, 3))) } @Test def `is parsable from a JSON object without the inclusive field` = { parse[Range]("""{"start":1,"end":4,"step":3}""").must(be(Range(1, 4, 3))) } @Test def `is parsable from a JSON object without the step field` = { parse[Range]("""{"start":1,"end":4}""").must(be(Range(1, 4))) } @Test def `is not parsable from a JSON object without the required fields` = { evaluating { parse[Range]("""{"start":1}""") }.must(throwA[ParsingException]("""Invalid JSON. Needed [start, end, , ], but found [start].""")) } } class `A Pair[Int]` { @Ignore @Test def `generates a two-element JSON array of ints` = { // TODO: 5/31/11 -- fix Pair serialization generate(Pair(1, 2)).must(be("[1,2]")) } @Ignore @Test def `is parsable from a two-element JSON array of ints` = { // TODO: 5/31/11 -- fix Pair deserialization parse[Pair[Int, Int]]("[1,2]").must(be(Pair(1, 2))) } } class `A Triple[Int]` { @Ignore @Test def `generates a three-element JSON array of ints` = { // TODO: 5/31/11 -- fix Triple serialization generate(Triple(1, 2, 3)).must(be("[1,2,3]")) } @Ignore @Test def `is parsable from a three-element JSON array of ints` = { // TODO: 5/31/11 -- fix Triple deserialization parse[Triple[Int, Int, Int]]("[1,2,3]").must(be(Triple(1, 2, 3))) } } class `A four-tuple` { @Ignore @Test def `generates a four-element JSON array` = { // TODO: 5/31/11 -- fix Tuple4 serialization generate((1, "2", 3, "4")).must(be("[1,\"2\",3,\"4\"]")) } @Ignore @Test def `is parsable from a three-element JSON array of ints` = { // TODO: 5/31/11 -- fix Tuple4 deserialization parse[(Int, String, Int, String)]("[1,\"2\",3,\"4\"]").must(be((1, "2", 3, "4"))) } } // TODO: 6/1/11 -- add support for all Tuple1->TupleBillionty types class `A Seq[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Seq[Int]]("[]").must(be(Seq.empty[Int])) } } class `A List[Int]` { @Test def `generates a JSON array of ints` = { generate(List(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[List[Int]]("[1,2,3]").must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[List[Int]]("[]").must(be(List.empty[Int])) } } class `An IndexedSeq[Int]` { @Test def `generates a JSON array of ints` = { generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty[Int])) } } class `A Vector[Int]` { @Test def `generates a JSON array of ints` = { generate(Vector(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Vector[Int]]("[1,2,3]").must(be(Vector(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Vector[Int]]("[]").must(be(Vector.empty[Int])) } } class `A Set[Int]` { @Test def `generates a JSON array of ints` = { generate(Set(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Set[Int]]("[1,2,3]").must(be(Set(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Set[Int]]("[]").must(be(Set.empty[Int])) } } class `A Map[String, Int]` { @Test def `generates a JSON object with int field values` = { generate(Map("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[Map[String, Int]]("""{"one":1,"two":2}""").must(be(Map("one" -> 1, "two" -> 2))) } @Test def `is parsable from an empty JSON object` = { parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int])) } } class `A Map[String, Any]` { @Test def `generates a JSON object with mixed field values` = { generate(Map("one" -> 1, "two" -> "2")).must(be("""{"one":1,"two":"2"}""")) } @Test def `is parsable from a JSON object with mixed field values` = { parse[Map[String, Any]]("""{"one":1,"two":"2"}""").must(be(Map[String, Any]("one" -> 1, "two" -> "2"))) } @Test def `is parsable from an empty JSON object` = { parse[Map[String, Any]]("{}").must(be(Map.empty[String, Any])) } } class `A Stream[Int]` { @Test def `generates a JSON array` = { generate(Stream(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Stream[Int]]("[1,2,3]").must(be(Stream(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Stream[Int]]("[]").must(be(Stream.empty[Int])) } } class `An Iterator[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).iterator).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Iterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Iterator[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A Traversable[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).toTraversable).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Traversable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Traversable[Int]]("[]").toList.must(be(List.empty[Int])) } } class `A BufferedIterator[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).iterator.buffered).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[BufferedIterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[BufferedIterator[Int]]("[]").toList.must(be(List.empty[Int])) } } class `An Iterable[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3).toIterable).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Iterable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Iterable[Int]]("[]").toList.must(be(List.empty[Int])) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/EdgeCaseSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import com.codahale.simplespec.Spec import com.codahale.jerkson.ParsingException import java.io.ByteArrayInputStream import org.junit.Test class EdgeCaseSpec extends Spec { class `Deserializing lists` { @Test def `doesn't cache Seq builders` = { parse[List[Int]]("[1,2,3,4]").must(be(List(1, 2, 3, 4))) parse[List[Int]]("[1,2,3,4]").must(be(List(1, 2, 3, 4))) } } class `Parsing a JSON array of ints with nulls` { @Test def `should be readable as a List[Option[Int]]` = { parse[List[Option[Int]]]("[1,2,null,4]").must(be(List(Some(1), Some(2), None, Some(4)))) } } class `Deserializing maps` { @Test def `doesn't cache Map builders` = { parse[Map[String, Int]](""" {"one":1, "two": 2} """).must(be(Map("one" -> 1, "two" -> 2))) parse[Map[String, Int]](""" {"one":1, "two": 2} """).must(be(Map("one" -> 1, "two" -> 2))) } } class `Parsing malformed JSON` { @Test def `should throw a ParsingException with an informative message` = { evaluating { parse[Boolean]("jjf8;09") }.must(throwA[ParsingException]( "Malformed JSON. Unexpected character ('j' (code 106)): expected a " + "valid value (number, String, array, object, 'true', 'false' " + "or 'null') at character offset 0.")) evaluating { parse[CaseClass]("{\"ye\":1") }.must(throwA[ParsingException]( "Malformed JSON. Unexpected end-of-input: expected close marker for " + "OBJECT at character offset 20.")) } } class `Parsing invalid JSON` { @Test def `should throw a ParsingException with an informative message` = { evaluating { parse[CaseClass]("900") }.must(throwA[ParsingException]( ("""Can not deserialize instance of com.codahale.jerkson.tests.CaseClass out of VALUE_NUMBER_INT token\n""" + """ at \[Source: java.io.StringReader@[0-9a-f]+; line: 1, column: 1\]""").r)) evaluating { parse[CaseClass]("{\"woo\": 1}") }.must(throwA[ParsingException]("Invalid JSON. Needed [id, name], but found [woo].")) } } class `Parsing an empty document` { @Test def `should throw a ParsingException with an informative message` = { val input = new ByteArrayInputStream(Array.empty) evaluating { parse[CaseClass](input) }.must(throwA[ParsingException]("""No content to map due to end\-of\-input""".r)) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala ================================================ package com.codahale.jerkson.tests import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonIgnore} import com.codahale.jerkson.JsonSnakeCase case class CaseClass(id: Long, name: String) case class CaseClassWithLazyVal(id: Long) { lazy val woo = "yeah" } case class CaseClassWithIgnoredField(id: Long) { @JsonIgnore val uncomfortable = "Bad Touch" } @JsonIgnoreProperties(Array("uncomfortable", "unpleasant")) case class CaseClassWithIgnoredFields(id: Long) { val uncomfortable = "Bad Touch" val unpleasant = "The Creeps" } case class CaseClassWithTransientField(id: Long) { @transient val lol = "I'm sure it's just a phase." } case class CaseClassWithOverloadedField(id: Long) { def id(prefix: String): String = prefix + id } case class CaseClassWithOption(value: Option[String]) case class CaseClassWithJsonNode(value: JsonNode) case class CaseClassWithAllTypes(map: Map[String, String], set: Set[Int], string: String, list: List[Int], seq: Seq[Int], indexedSeq: IndexedSeq[Int], vector: Vector[Int], bigDecimal: BigDecimal, bigInt: BigInt, int: Int, long: Long, char: Char, bool: Boolean, short: Short, byte: Byte, float: Float, double: Double, any: Any, anyRef: AnyRef, intMap: Map[Int, Int], longMap: Map[Long, Long]) object OuterObject { case class NestedCaseClass(id: Long) object InnerObject { case class SuperNestedCaseClass(id: Long) } } case class CaseClassWithTwoConstructors(id: Long, name: String) { def this(id: Long) = this(id, "New User") } @JsonSnakeCase case class CaseClassWithSnakeCase(oneThing: String, twoThing: String) case class CaseClassWithArrays(one: String, two: Array[String], three: Array[Int]) ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/FancyTypeSupportSpec.scala ================================================ package com.codahale.jerkson.tests import java.net.URI import com.codahale.simplespec.Spec import org.junit.Test import com.codahale.jerkson.Json._ import java.util.UUID class FancyTypeSupportSpec extends Spec { class `A URI` { @Test def `generates a JSON string` = { generate(new URI("http://example.com/resource?query=yes")).must(be("\"http://example.com/resource?query=yes\"")) } @Test def `is parsable from a JSON string` = { parse[URI]("\"http://example.com/resource?query=yes\"").must(be(new URI("http://example.com/resource?query=yes"))) } } class `A UUID` { val uuid = UUID.fromString("a62047e4-bfb5-4d71-aad7-1a6b338eee63") @Test def `generates a JSON string` = { generate(uuid).must(be("\"a62047e4-bfb5-4d71-aad7-1a6b338eee63\"")) } @Test def `is parsable from a JSON string` = { parse[UUID]("\"a62047e4-bfb5-4d71-aad7-1a6b338eee63\"").must(be(uuid)) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/ImmutableCollectionSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.simplespec.Spec import com.codahale.jerkson.Json._ import scala.collection.immutable._ import com.codahale.jerkson.ParsingException import org.junit.{Ignore, Test} class ImmutableCollectionSupportSpec extends Spec { class `An immutable.Seq[Int]` { @Test def `generates a JSON array of ints` = { generate(Seq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Seq[Int]]("[]").must(be(Seq.empty[Int])) } } class `An immutable.List[Int]` { @Test def `generates a JSON array of ints` = { generate(List(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[List[Int]]("[1,2,3]").must(be(List(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[List[Int]]("[]").must(be(List.empty[Int])) } } class `An immutable.IndexedSeq[Int]` { @Test def `generates a JSON array of ints` = { generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty[Int])) } } class `An immutable.TreeSet[Int]` { @Test def `generates a JSON array` = { generate(TreeSet(1)).must(be("[1]")) } // TODO: 6/1/11 -- figure out how to deserialize TreeSet instances /** * I think all this would take is a mapping from Class[_] to Ordering, which * would need to have hard-coded the various primitive types, and then add * support for Ordered and Comparable classes. Once we have the Ordering, * we can pass it in manually to a builder. */ @Ignore @Test def `is parsable from a JSON array of ints` = { parse[TreeSet[Int]]("[1,2,3]").must(be(TreeSet(1, 2, 3))) } @Ignore @Test def `is parsable from an empty JSON array` = { parse[TreeSet[Int]]("[]").must(be(TreeSet.empty[Int])) } } class `An immutable.HashSet[Int]` { @Test def `generates a JSON array` = { generate(HashSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[HashSet[Int]]("[1,2,3]").must(be(HashSet(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[HashSet[Int]]("[]").must(be(HashSet.empty[Int])) } } class `An immutable.BitSet` { @Test def `generates a JSON array` = { generate(BitSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[BitSet]("[]").must(be(BitSet.empty)) } } class `An immutable.TreeMap[String, Int]` { @Test def `generates a JSON object` = { generate(TreeMap("one" -> 1)).must(be("""{"one":1}""")) } // TODO: 6/1/11 -- figure out how to deserialize TreeMap instances /** * I think all this would take is a mapping from Class[_] to Ordering, which * would need to have hard-coded the various primitive types, and then add * support for Ordered and Comparable classes. Once we have the Ordering, * we can pass it in manually to a builder. */ @Ignore @Test def `is parsable from a JSON object with int field values` = { parse[TreeMap[String, Int]]("""{"one":1}""").must(be(TreeMap("one" -> 1))) } @Ignore @Test def `is parsable from an empty JSON object` = { parse[TreeMap[String, Int]]("{}").must(be(TreeMap.empty[String, Int])) } } class `An immutable.HashMap[String, Int]` { @Test def `generates a JSON object` = { generate(HashMap("one" -> 1)).must(be("""{"one":1}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[HashMap[String, Int]]("""{"one":1}""").must(be(HashMap("one" -> 1))) } @Test def `is parsable from an empty JSON object` = { parse[HashMap[String, Int]]("{}").must(be(HashMap.empty[String, Int])) } } class `An immutable.HashMap[String, Any]` { @Test def `generates a JSON object` = { generate(HashMap[String, Any]("one" -> 1)).must(be("""{"one":1}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[HashMap[String, Any]]("""{"one":1}""").must(be(HashMap("one" -> 1))) } @Test def `is parsable from an empty JSON object` = { parse[HashMap[String, Any]]("{}").must(be(HashMap.empty[String, Any])) } @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[HashMap[String, Any]]("[{}]") }.must(throwA[ParsingException]) } } class `An immutable.Map[Int, String]` { @Test def `generates a JSON object` = { generate(Map(1 -> "one")).must(be("""{"1":"one"}""")) } @Test def `is parsable from a JSON object with decimal field names and string field values` = { parse[Map[Int, String]]("""{"1":"one"}""").must(be(Map(1 -> "one"))) } @Test def `is not parsable from a JSON object with non-decimal field names` = { evaluating { parse[Map[Int, String]]("""{"one":"one"}""") }.must(throwA[ParsingException]) } @Test def `is parsable from an empty JSON object` = { parse[Map[Int, String]]("{}").must(be(Map.empty[Int, String])) } } class `An immutable.Map[Int, Any]` { @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[Map[Int, Any]]("[{}]") }.must(throwA[ParsingException]) } } class `An immutable.IntMap[Any]` { @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[IntMap[Any]]("[{}]") }.must(throwA[ParsingException]) } } class `An immutable.LongMap[Any]` { @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[LongMap[Any]]("[{}]") }.must(throwA[ParsingException]) } } class `An immutable.Map[Long, Any]` { @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[Map[Long, Any]]("[{}]") }.must(throwA[ParsingException]) } } class `An immutable.Map[Long, String]` { @Test def `generates a JSON object` = { generate(Map(1L -> "one")).must(be("""{"1":"one"}""")) } @Test def `is parsable from a JSON object with decimal field names and string field values` = { parse[Map[Long, String]]("""{"1":"one"}""").must(be(Map(1L -> "one"))) } @Test def `is not parsable from a JSON object with non-decimal field names` = { evaluating { parse[Map[Long, String]]("""{"one":"one"}""") }.must(throwA[ParsingException]) } @Test def `is parsable from an empty JSON object` = { parse[Map[Long, String]]("{}").must(be(Map.empty[Long, String])) } } class `An immutable.IntMap[String]` { @Test def `generates a JSON object` = { generate(IntMap(1 -> "one")).must(be("""{"1":"one"}""")) } @Test def `is parsable from a JSON object with decimal field names and string field values` = { parse[IntMap[String]]("""{"1":"one"}""").must(be(IntMap(1 -> "one"))) } @Test def `is not parsable from a JSON object with non-decimal field names` = { evaluating { parse[IntMap[String]]("""{"one":"one"}""") }.must(throwA[ParsingException]) } @Test def `is parsable from an empty JSON object` = { parse[IntMap[String]]("{}").must(be(IntMap.empty[String])) } } class `An immutable.LongMap[String]` { @Test def `generates a JSON object` = { generate(LongMap(1L -> "one")).must(be("""{"1":"one"}""")) } @Test def `is parsable from a JSON object with int field names and string field values` = { parse[LongMap[String]]("""{"1":"one"}""").must(be(LongMap(1L -> "one"))) } @Test def `is not parsable from a JSON object with non-decimal field names` = { evaluating { parse[LongMap[String]]("""{"one":"one"}""") }.must(throwA[ParsingException]) } @Test def `is parsable from an empty JSON object` = { parse[LongMap[String]]("{}").must(be(LongMap.empty)) } } class `An immutable.Queue[Int]` { @Test def `generates a JSON array` = { generate(Queue(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Queue[Int]]("[1,2,3]").must(be(Queue(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Queue[Int]]("[]").must(be(Queue.empty)) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/JValueSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import com.codahale.jerkson.AST._ import com.codahale.simplespec.Spec import org.junit.Test class JValueSpec extends Spec { class `Selecting single nodes` { @Test def `returns None with primitives` = { (parse[JValue]("8") \ "blah").must(be(JNull)) } @Test def `returns None on nonexistent fields` = { (parse[JValue]("{\"one\": \"1\"}") \ "two").must(be(JNull)) } @Test def `returns a JValue with an existing field` = { (parse[JValue]("{\"one\": \"1\"}") \ "one").must(be(JString("1"))) } } class `Selecting array members` { @Test def `returns None with primitives` = { (parse[JValue]("\"derp\"").apply(0)).must(be(JNull)) } @Test def `returns None on out of bounds` = { (parse[JValue]("[0, 1, 2, 3]").apply(4)).must(be(JNull)) } @Test def `returns a JValue` = { (parse[JValue]("[0, 1, 2, 3]").apply(2)).must(be(JInt(2))) } } class `Deep selecting` { @Test def `returns Nil with primitives` = { (parse[JValue]("0.234") \\ "herp").must(be(empty)) } @Test def `returns Nil on nothing found` = { (parse[JValue]("{\"one\": {\"two\" : \"three\"}}") \\ "four").must(be(empty)) } @Test def `returns single leaf nodes` = { (parse[JValue]("{\"one\": {\"two\" : \"three\"}}") \\ "two").must(be(Seq(JString("three")))) } @Test def `should return multiple leaf nodes` = { (parse[JValue]("{\"one\": {\"two\" : \"three\"}, \"four\": {\"two\" : \"five\"}}") \\ "two").must(be(Seq(JString("three"),JString("five")))) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/MutableCollectionSupportSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.simplespec.Spec import com.codahale.jerkson.Json._ import scala.collection.mutable._ import com.codahale.jerkson.ParsingException import org.junit.Test class MutableCollectionSupportSpec extends Spec { class `A mutable.ResizableArray[Int]` { @Test def `generates a JSON array of ints` = { generate(ResizableArray(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[ResizableArray[Int]]("[1,2,3]").must(be(ResizableArray(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[ResizableArray[Int]]("[]").must(be(ResizableArray.empty[Int])) } } class `A mutable.ArraySeq[Int]` { @Test def `generates a JSON array of ints` = { generate(ArraySeq(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[ArraySeq[Int]]("[1,2,3]").must(be(ArraySeq(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[ArraySeq[Int]]("[]").must(be(ArraySeq.empty[Int])) } } class `A mutable.MutableList[Int]` { private val xs = new MutableList[Int] xs ++= List(1, 2, 3) @Test def `generates a JSON array` = { generate(xs).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[MutableList[Int]]("[1,2,3]").must(be(xs)) } @Test def `is parsable from an empty JSON array` = { parse[MutableList[Int]]("[]").must(be(new MutableList[Int]())) } } class `A mutable.Queue[Int]` { @Test def `generates a JSON array` = { generate(Queue(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[Queue[Int]]("[1,2,3]").must(be(Queue(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[Queue[Int]]("[]").must(be(new Queue[Int]())) } } class `A mutable.ListBuffer[Int]` { @Test def `generates a JSON array` = { generate(ListBuffer(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[ListBuffer[Int]]("[1,2,3]").must(be(ListBuffer(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[ListBuffer[Int]]("[]").must(be(ListBuffer.empty[Int])) } } class `A mutable.ArrayBuffer[Int]` { @Test def `generates a JSON array` = { generate(ArrayBuffer(1, 2, 3)).must(be("[1,2,3]")) } @Test def `is parsable from a JSON array of ints` = { parse[ArrayBuffer[Int]]("[1,2,3]").must(be(ArrayBuffer(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[ArrayBuffer[Int]]("[]").must(be(ArrayBuffer.empty[Int])) } } class `A mutable.BitSet` { @Test def `generates a JSON array` = { generate(BitSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[BitSet]("[]").must(be(BitSet.empty)) } } class `A mutable.HashSet[Int]` { @Test def `generates a JSON array` = { generate(HashSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[HashSet[Int]]("[1,2,3]").must(be(HashSet(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[HashSet[Int]]("[]").must(be(HashSet.empty[Int])) } } class `A mutable.LinkedHashSet[Int]` { @Test def `generates a JSON array` = { generate(LinkedHashSet(1)).must(be("[1]")) } @Test def `is parsable from a JSON array of ints` = { parse[LinkedHashSet[Int]]("[1,2,3]").must(be(LinkedHashSet(1, 2, 3))) } @Test def `is parsable from an empty JSON array` = { parse[LinkedHashSet[Int]]("[]").must(be(LinkedHashSet.empty[Int])) } } class `A mutable.Map[String, Int]` { @Test def `generates a JSON object` = { generate(Map("one" -> 1)).must(be("""{"one":1}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[Map[String, Int]]("""{"one":1}""").must(be(Map("one" -> 1))) } @Test def `is parsable from an empty JSON object` = { parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int])) } } class `A mutable.Map[String, Any]` { @Test def `is not parsable from an empty JSON object in a JSON array` = { evaluating { parse[Map[String, Any]]("[{}]") }.must(throwA[ParsingException]) } } class `A mutable.HashMap[String, Int]` { @Test def `generates a JSON object` = { generate(HashMap("one" -> 1)).must(be("""{"one":1}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[HashMap[String, Int]]("""{"one":1}""").must(be(HashMap("one" -> 1))) } @Test def `is parsable from an empty JSON object` = { parse[HashMap[String, Int]]("{}").must(be(HashMap.empty[String, Int])) } } class `A mutable.LinkedHashMap[String, Int]` { @Test def `generates a JSON object` = { generate(LinkedHashMap("one" -> 1)).must(be("""{"one":1}""")) } @Test def `is parsable from a JSON object with int field values` = { parse[LinkedHashMap[String, Int]]("""{"one":1}""").must(be(LinkedHashMap("one" -> 1))) } @Test def `is parsable from an empty JSON object` = { parse[LinkedHashMap[String, Int]]("{}").must(be(LinkedHashMap.empty[String, Int])) } } } ================================================ FILE: src/test/scala/com/codahale/jerkson/tests/StreamingSpec.scala ================================================ package com.codahale.jerkson.tests import com.codahale.jerkson.Json._ import java.io.ByteArrayInputStream import com.codahale.simplespec.Spec import org.junit.Test class StreamingSpec extends Spec { class `Parsing a stream of objects` { val json = """[ {"id":1, "name": "Coda"}, {"id":2, "name": "Niki"}, {"id":3, "name": "Biscuit"}, {"id":4, "name": "Louie"} ]""" @Test def `returns an iterator of stream elements` = { stream[CaseClass](new ByteArrayInputStream(json.getBytes)).toList .must(be(CaseClass(1, "Coda") :: CaseClass(2, "Niki") :: CaseClass(3, "Biscuit") :: CaseClass(4, "Louie") :: Nil)) } } }