Skip to content

Commit 1955cd6

Browse files
author
Roman Janusz
committed
implemented peekField for BsonReaderInput
1 parent 3854555 commit 1955cd6

File tree

7 files changed

+110
-18
lines changed

7 files changed

+110
-18
lines changed

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonInputOutput.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.avsystem.commons
22
package mongo
33

44
import com.avsystem.commons.serialization.GenCodec.ReadFailure
5-
import com.avsystem.commons.serialization.{InputAndSimpleInput, InputMetadata, OutputAndSimpleOutput, TypeMarker}
5+
import com.avsystem.commons.serialization.{FieldInput, InputAndSimpleInput, InputMetadata, OutputAndSimpleOutput, TypeMarker}
66
import org.bson.types.{Decimal128, ObjectId}
77
import org.bson.{BsonInvalidOperationException, BsonType, BsonValue}
88

@@ -89,6 +89,8 @@ object BsonInput {
8989
}
9090
}
9191

92+
trait BsonFieldInput extends BsonInput with FieldInput
93+
9294
trait BsonOutput extends Any with OutputAndSimpleOutput {
9395
def writeObjectId(objectId: ObjectId): Unit
9496
def writeDecimal128(decimal128: Decimal128): Unit

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonReaderInput.scala

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.avsystem.commons
22
package mongo
33

4-
import com.avsystem.commons.serialization.{FieldInput, ListInput, ObjectInput}
4+
import com.avsystem.commons.serialization.{ListInput, ObjectInput}
55
import com.google.common.collect.AbstractIterator
6+
import org.bson._
67
import org.bson.types.{Decimal128, ObjectId}
7-
import org.bson.{BsonReader, BsonType, BsonValue}
8+
9+
import _root_.scala.annotation.tailrec
810

911
class BsonReaderInput(br: BsonReader, override val legacyOptionEncoding: Boolean = false)
1012
extends BsonInput {
@@ -46,9 +48,7 @@ class BsonReaderInput(br: BsonReader, override val legacyOptionEncoding: Boolean
4648

4749
override def readObject(): BsonReaderObjectInput = {
4850
br.readStartDocument()
49-
new BsonReaderObjectInput(new BsonReaderIterator(br, _.readEndDocument(),
50-
br => new BsonReaderFieldInput(KeyEscaper.unescape(br.readName()), br, legacyOptionEncoding)
51-
))
51+
new BsonReaderObjectInput(br, legacyOptionEncoding)
5252
}
5353

5454
override def readObjectId(): ObjectId =
@@ -70,7 +70,7 @@ class BsonReaderInput(br: BsonReader, override val legacyOptionEncoding: Boolean
7070
}
7171

7272
final class BsonReaderFieldInput(name: String, br: BsonReader, legacyOptionEncoding: Boolean)
73-
extends BsonReaderInput(br, legacyOptionEncoding) with FieldInput {
73+
extends BsonReaderInput(br, legacyOptionEncoding) with BsonFieldInput {
7474
override def fieldName: String = name
7575
}
7676

@@ -91,7 +91,48 @@ final class BsonReaderListInput(it: BsonReaderIterator[BsonReaderInput]) extends
9191
override def nextElement(): BsonReaderInput = it.next()
9292
}
9393

94-
final class BsonReaderObjectInput(it: BsonReaderIterator[BsonReaderFieldInput]) extends ObjectInput {
94+
final class BsonReaderObjectInput(br: BsonReader, legacyOptionEncoding: Boolean) extends ObjectInput {
95+
private[this] val it = new BsonReaderIterator(br, _.readEndDocument(),
96+
br => new BsonReaderFieldInput(
97+
KeyEscaper.unescape(br.readName()),
98+
br,
99+
legacyOptionEncoding
100+
)
101+
)
102+
103+
private[this] var peekMark: BsonReaderMark = br.getMark
104+
private[this] var peekedFields: MHashMap[String, BsonValue] = _
105+
106+
override def peekField(name: String): Opt[BsonFieldInput] =
107+
br match {
108+
case _: BsonDocumentReader =>
109+
// Looks like there's a bug in BsonDocumentReader.Mark implementation, tests don't pass
110+
// TODO: find and report/fix this bug
111+
Opt.Empty
112+
case _ =>
113+
val peekedValue = peekedFields.opt.flatMap(_.getOpt(name)).orElse {
114+
val savedMark = br.getMark
115+
peekMark.reset()
116+
117+
@tailrec def loop(): Opt[BsonValue] =
118+
if (br.readBsonType() == BsonType.END_OF_DOCUMENT) Opt.Empty
119+
else KeyEscaper.unescape(br.readName()) match {
120+
case `name` => BsonValueUtils.decode(br).opt
121+
case otherName =>
122+
if (peekedFields eq null) {
123+
peekedFields = new MHashMap
124+
}
125+
peekedFields(otherName) = BsonValueUtils.decode(br)
126+
peekMark = br.getMark
127+
loop()
128+
}
129+
130+
try loop() finally savedMark.reset()
131+
}
132+
133+
peekedValue.map(new BsonValueFieldInput(name, _, legacyOptionEncoding))
134+
}
135+
95136
override def hasNext: Boolean = it.hasNext
96137
override def nextField(): BsonReaderFieldInput = it.next()
97138
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonValueInput.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class BsonValueInput(bsonValue: BsonValue, override val legacyOptionEncoding: Bo
6161
}
6262

6363
class BsonValueFieldInput(val fieldName: String, bsonValue: BsonValue, legacyOptionEncoding: Boolean)
64-
extends BsonValueInput(bsonValue, legacyOptionEncoding) with FieldInput
64+
extends BsonValueInput(bsonValue, legacyOptionEncoding) with BsonFieldInput
6565

6666
class BsonValueListInput(bsonArray: BsonArray, legacyOptionEncoding: Boolean) extends ListInput {
6767
private val it = bsonArray.iterator

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonValueUtils.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ object BsonValueUtils {
1313
bsonValueCodec.encode(bw, bv, encoderContext)
1414

1515
def decode(br: BsonReader): BsonValue = {
16-
br.readBsonType() // without this, `br.getCurrentBsonType` may return null (why?)
16+
if (br.getCurrentBsonType eq null) {
17+
br.readBsonType()
18+
}
1719
bsonValueCodec.decode(br, decoderContext)
1820
}
1921
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/GenCodecBasedBsonCodec.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import org.bson.{BsonReader, BsonWriter}
88
class GenCodecBasedBsonCodec[T](legacyOptionEncoding: Boolean)(
99
implicit ct: ClassTag[T], genCodec: GenCodec[T]
1010
) extends Codec[T] {
11-
override def getEncoderClass = ct.runtimeClass.asInstanceOf[Class[T]]
11+
override def getEncoderClass: Class[T] = ct.runtimeClass.asInstanceOf[Class[T]]
1212

13-
override def decode(reader: BsonReader, decoderContext: DecoderContext) = {
13+
override def decode(reader: BsonReader, decoderContext: DecoderContext): T =
1414
genCodec.read(new BsonReaderInput(reader, legacyOptionEncoding))
15-
}
1615

17-
override def encode(writer: BsonWriter, value: T, encoderContext: EncoderContext) = {
16+
override def encode(writer: BsonWriter, value: T, encoderContext: EncoderContext): Unit =
1817
genCodec.write(new BsonWriterOutput(writer, legacyOptionEncoding), value)
19-
}
2018
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/core/GenCodecRegistry.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ object GenCodecRegistry {
88
final val LegacyOptionEncoding: Boolean =
99
System.getProperty("commons.mongo.legacyOptionEncoding").opt.fold(false)(_.toBoolean)
1010

11-
def create[T: ClassTag : GenCodec](baseRegistry: CodecRegistry,
12-
legacyOptionEncoding: Boolean = LegacyOptionEncoding): CodecRegistry = {
13-
11+
def create[T: ClassTag : GenCodec](
12+
baseRegistry: CodecRegistry,
13+
legacyOptionEncoding: Boolean = LegacyOptionEncoding
14+
): CodecRegistry = {
1415
val genProvider = new GenCodecProvider[T](legacyOptionEncoding)
1516
val genRegistry = CodecRegistries.fromProviders(genProvider)
1617
CodecRegistries.fromRegistries(genRegistry, baseRegistry)

commons-mongo/jvm/src/test/scala/com/avsystem/commons/mongo/BsonInputOutputTest.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,54 @@ class BsonInputOutputTest extends AnyFunSuite with ScalaCheckPropertyChecks {
244244
}
245245
}
246246

247+
test("BsonBinaryReader peekField") {
248+
val doc = new BsonDocument()
249+
.append("str", new BsonString("str"))
250+
.append("int", new BsonInt32(32))
251+
.append("boo", new BsonBoolean(false))
252+
.append("arr", new BsonArray(JList(new BsonInt32(42), new BsonString("foo"))))
253+
.append("dbl", new BsonDouble(3.14))
254+
255+
val bof = new BasicOutputBuffer
256+
val bw = new BsonBinaryWriter(bof)
257+
BsonValueUtils.encode(bw, doc)
258+
bw.flush()
259+
bw.close()
260+
261+
val br = new BsonBinaryReader(ByteBuffer.wrap(bof.toByteArray))
262+
val input = new BsonReaderInput(br).readObject()
263+
264+
locally {
265+
val pf = input.peekField("str")
266+
assert(pf.exists(fi => fi.fieldName == "str" && fi.readBsonValue() == new BsonString("str")))
267+
}
268+
locally {
269+
val fi = input.nextField()
270+
assert(fi.fieldName == "str")
271+
fi.skip()
272+
}
273+
locally {
274+
val pf = input.peekField("boo")
275+
assert(pf.exists(fi => fi.fieldName == "boo" && fi.readBsonValue() == new BsonBoolean(false)))
276+
}
277+
locally {
278+
assert(input.peekField("not").isEmpty)
279+
}
280+
locally {
281+
val pf = input.peekField("dbl")
282+
assert(pf.exists(fi => fi.fieldName == "dbl" && fi.readBsonValue() == new BsonDouble(3.14)))
283+
}
284+
locally {
285+
val fi = input.nextField()
286+
assert(fi.fieldName == "int")
287+
fi.skip()
288+
}
289+
locally {
290+
val pf = input.peekField("str")
291+
assert(pf.exists(fi => fi.fieldName == "str" && fi.readBsonValue() == new BsonString("str")))
292+
}
293+
}
294+
247295
def listToBson[T](list: List[T])(converter: T => BsonValue) = new BsonArray(list.map(converter).asJava)
248296

249297
def mapToBson[T](map: Map[String, T])(valueConverter: T => BsonValue): BsonDocument = {

0 commit comments

Comments
 (0)