Skip to content

Commit 404bcce

Browse files
mongodbencbullingerrustagir
authored
(DOCSP-30004): Kotlin codecs (#75)
# Pull Request Info [PR Reviewing Guidelines](https://github.com/mongodb/docs-java/blob/master/REVIEWING.md) JIRA - https://jira.mongodb.org/browse/DOCSP-30004 Staging - https://docs-mongodbcom-staging.corp.mongodb.com/kotlin/docsworker-xlarge/DOCSP-30004/fundamentals/data-formats/codecs/ ## Self-Review Checklist - [ ] Is this free of any warnings or errors in the RST? - [ ] Did you run a spell-check? - [ ] Did you run a grammar-check? - [ ] Are all the links working? --------- Co-authored-by: cbullinger <[email protected]> Co-authored-by: Rea Rustagi <[email protected]> Co-authored-by: Rea Radhika Rustagi <[email protected]>
1 parent 7fb6948 commit 404bcce

16 files changed

+365
-112
lines changed

examples/src/test/kotlin/CodecTest.kt

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
2+
import com.mongodb.MongoClientSettings
3+
import com.mongodb.kotlin.client.coroutine.MongoClient
4+
import config.getConfig
5+
import kotlinx.coroutines.flow.toList
6+
import kotlinx.coroutines.runBlocking
7+
import org.bson.BsonReader
8+
import org.bson.BsonType
9+
import org.bson.BsonWriter
10+
import org.bson.codecs.*
11+
import org.bson.codecs.configuration.CodecProvider
12+
import org.bson.codecs.configuration.CodecRegistries
13+
import org.bson.codecs.configuration.CodecRegistry
14+
import org.junit.jupiter.api.Test
15+
import org.junit.jupiter.api.TestInstance
16+
import kotlin.test.assertEquals
17+
18+
19+
// :replace-start: {
20+
// "terms": {
21+
// "CONNECTION_URI_PLACEHOLDER": "\"<connection string uri>\""
22+
// }
23+
// }
24+
25+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
26+
internal class CodecTest {
27+
// :snippet-start: example-enum
28+
enum class PowerStatus {
29+
ON,
30+
OFF
31+
}
32+
// :snippet-end:
33+
34+
// :snippet-start: example-codec
35+
class PowerStatusCodec : Codec<PowerStatus> {
36+
override fun encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext) = writer.writeBoolean(value == PowerStatus.ON)
37+
38+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus {
39+
return when (reader.readBoolean()) {
40+
true -> PowerStatus.ON
41+
false -> PowerStatus.OFF
42+
}
43+
}
44+
45+
override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java
46+
}
47+
// :snippet-end:
48+
49+
// :snippet-start: example-class
50+
data class Monolight(
51+
var powerStatus: PowerStatus = PowerStatus.OFF,
52+
var colorTemperature: Int? = null
53+
) {
54+
override fun toString(): String = "Monolight [powerStatus=$powerStatus, colorTemperature=$colorTemperature]"
55+
}
56+
// :snippet-end:
57+
58+
// :snippet-start: example-codec-2
59+
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> {
60+
private val powerStatusCodec: Codec<PowerStatus>
61+
private val integerCodec: Codec<Int>
62+
63+
init {
64+
powerStatusCodec = registry[PowerStatus::class.java]
65+
integerCodec = registry.get(Int::class.java)
66+
}
67+
68+
override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
69+
writer.writeStartDocument()
70+
writer.writeName("powerStatus")
71+
powerStatusCodec.encode(writer, value.powerStatus, encoderContext)
72+
writer.writeName("colorTemperature")
73+
integerCodec.encode(writer, value.colorTemperature, encoderContext)
74+
writer.writeEndDocument()
75+
}
76+
77+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight {
78+
val monolight = Monolight()
79+
reader.readStartDocument()
80+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
81+
when (reader.readName()) {
82+
"powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext)
83+
"colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext)
84+
"_id" -> reader.readObjectId()
85+
}
86+
}
87+
reader.readEndDocument()
88+
return monolight
89+
}
90+
91+
override fun getEncoderClass(): Class<Monolight> = Monolight::class.java
92+
}
93+
// :snippet-end:
94+
95+
// TODO: is there a more kotlin semantic way to do this?
96+
// :snippet-start: codec-provider
97+
class MonolightCodecProvider : CodecProvider {
98+
@Suppress("UNCHECKED_CAST")
99+
override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? {
100+
return if (clazz == Monolight::class.java) {
101+
MonolightCodec(registry) as Codec<T>
102+
} else null // Return null when not a provider for the requested class
103+
}
104+
}
105+
// :snippet-end:
106+
107+
@Test
108+
fun overrideDefaultCodecTest() {
109+
class MyEnumCodec : Codec<PowerStatus> {
110+
override fun encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext) = writer.writeBoolean(value == PowerStatus.ON)
111+
112+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus {
113+
return when (reader.readBoolean()) {
114+
true -> PowerStatus.ON
115+
false -> PowerStatus.OFF
116+
}
117+
}
118+
119+
override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java
120+
}
121+
// :snippet-start: override-default-codec
122+
val newRegistry = CodecRegistries.fromRegistries(
123+
CodecRegistries.fromCodecs(MyEnumCodec()),
124+
MongoClientSettings.getDefaultCodecRegistry()
125+
)
126+
// :snippet-end:
127+
// TODO assertions to test
128+
}
129+
130+
@Test
131+
fun bsonTypeClassMapTest() {
132+
// :snippet-start: bson-type-class-map
133+
val bsonTypeClassMap = BsonTypeClassMap()
134+
val clazz = bsonTypeClassMap[BsonType.ARRAY]
135+
println("Class name: " + clazz.name)
136+
// :snippet-end:
137+
assertEquals(clazz, List::class.java)
138+
}
139+
140+
@Test
141+
fun bsonTypeClassMapReplacementTest() {
142+
// :snippet-start: bson-type-class-map-replacement
143+
val replacements = mutableMapOf<BsonType, Class<*>>(BsonType.ARRAY to MutableSet::class.java)
144+
val bsonTypeClassMap = BsonTypeClassMap(replacements)
145+
val clazz = bsonTypeClassMap[BsonType.ARRAY]
146+
println("Class name: " + clazz.name)
147+
// :snippet-end:
148+
assertEquals(clazz, MutableSet::class.java)
149+
}
150+
151+
152+
@Test
153+
fun initCodecRegistryTest() {
154+
// :snippet-start: init-codec-registry
155+
val codecRegistry = CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec())
156+
// :snippet-end:
157+
// :snippet-start: get-codec-from-registry
158+
val powerStatusCodec = codecRegistry.get(PowerStatus::class.java)
159+
val integerCodec = codecRegistry.get(Integer::class.java)
160+
// :snippet-end:
161+
// Just asserting true b/c if the above doesn't throw an exception, it worked
162+
assert(true)
163+
}
164+
165+
@Test
166+
fun fullExampleTest() = runBlocking {
167+
// :snippet-start: full-example
168+
fun main() = runBlocking {
169+
// :remove-start:
170+
val config = getConfig()
171+
val CONNECTION_URI_PLACEHOLDER = config.connectionUri
172+
// :remove-end:
173+
val mongoClient = MongoClient.create(CONNECTION_URI_PLACEHOLDER)
174+
val codecRegistry = CodecRegistries.fromRegistries(
175+
CodecRegistries.fromCodecs(IntegerCodec(), PowerStatusCodec()),
176+
CodecRegistries.fromProviders(MonolightCodecProvider()),
177+
MongoClientSettings.getDefaultCodecRegistry()
178+
)
179+
val database = mongoClient.getDatabase("codecs_example_products")
180+
val collection = database.getCollection<Monolight>("monolights")
181+
.withCodecRegistry(codecRegistry)
182+
183+
// Construct and insert an instance of Monolight
184+
val myMonolight = Monolight(PowerStatus.ON, 5200)
185+
collection.insertOne(myMonolight)
186+
187+
// Retrieve one or more instances of Monolight
188+
val lights = collection.find().toList()
189+
println(lights)
190+
// :remove-start:
191+
assertEquals(1, lights.size)
192+
assertEquals(myMonolight, lights.first())
193+
collection.drop()
194+
mongoClient.close()
195+
// :remove-end:
196+
}
197+
// :snippet-end:
198+
main()
199+
}
200+
}
201+
// :replace-end:
202+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
val collection = database.getCollection<Document>("testCollection")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
val database = client.getDatabase("testDatabase")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
val replacements = mutableMapOf<BsonType, Class<*>>(BsonType.ARRAY to MutableSet::class.java)
2+
val bsonTypeClassMap = BsonTypeClassMap(replacements)
3+
val clazz = bsonTypeClassMap[BsonType.ARRAY]
4+
println("Class name: " + clazz.name)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
val bsonTypeClassMap = BsonTypeClassMap()
2+
val clazz = bsonTypeClassMap[BsonType.ARRAY]
3+
println("Class name: " + clazz.name)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class MonolightCodecProvider : CodecProvider {
2+
@Suppress("UNCHECKED_CAST")
3+
override fun <T> get(clazz: Class<T>, registry: CodecRegistry): Codec<T>? {
4+
return if (clazz == Monolight::class.java) {
5+
MonolightCodec(registry) as Codec<T>
6+
} else null // Return null when not a provider for the requested class
7+
}
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
data class Monolight(
2+
var powerStatus: PowerStatus = PowerStatus.OFF,
3+
var colorTemperature: Int? = null
4+
) {
5+
override fun toString(): String = "Monolight [powerStatus=$powerStatus, colorTemperature=$colorTemperature]"
6+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class MonolightCodec(registry: CodecRegistry) : Codec<Monolight> {
2+
private val powerStatusCodec: Codec<PowerStatus>
3+
private val integerCodec: Codec<Int>
4+
5+
init {
6+
powerStatusCodec = registry[PowerStatus::class.java]
7+
integerCodec = registry.get(Int::class.java)
8+
}
9+
10+
override fun encode(writer: BsonWriter, value: Monolight, encoderContext: EncoderContext) {
11+
writer.writeStartDocument()
12+
writer.writeName("powerStatus")
13+
powerStatusCodec.encode(writer, value.powerStatus, encoderContext)
14+
writer.writeName("colorTemperature")
15+
integerCodec.encode(writer, value.colorTemperature, encoderContext)
16+
writer.writeEndDocument()
17+
}
18+
19+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): Monolight {
20+
val monolight = Monolight()
21+
reader.readStartDocument()
22+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
23+
when (reader.readName()) {
24+
"powerStatus" -> monolight.powerStatus = powerStatusCodec.decode(reader, decoderContext)
25+
"colorTemperature" -> monolight.colorTemperature = integerCodec.decode(reader, decoderContext)
26+
"_id" -> reader.readObjectId()
27+
}
28+
}
29+
reader.readEndDocument()
30+
return monolight
31+
}
32+
33+
override fun getEncoderClass(): Class<Monolight> = Monolight::class.java
34+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class PowerStatusCodec : Codec<PowerStatus> {
2+
override fun encode(writer: BsonWriter, value: PowerStatus, encoderContext: EncoderContext) = writer.writeBoolean(value == PowerStatus.ON)
3+
4+
override fun decode(reader: BsonReader, decoderContext: DecoderContext): PowerStatus {
5+
return when (reader.readBoolean()) {
6+
true -> PowerStatus.ON
7+
false -> PowerStatus.OFF
8+
}
9+
}
10+
11+
override fun getEncoderClass(): Class<PowerStatus> = PowerStatus::class.java
12+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
enum class PowerStatus {
2+
ON,
3+
OFF
4+
}

0 commit comments

Comments
 (0)