Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new functions in JsonArray #110

Merged
merged 2 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions yorkie/src/main/kotlin/dev/yorkie/document/json/JsonArray.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public class JsonArray internal constructor(
internal val context: ChangeContext,
override val target: CrdtArray,
) : JsonElement(), Collection<JsonElement> {
internal val id
get() = target.createdAt

override val size: Int
get() = target.length
Expand All @@ -44,45 +42,53 @@ public class JsonArray internal constructor(
return target[createdAt]?.toJsonElement(context)
}

public fun put(value: Boolean) = putPrimitive(value)
public fun put(value: Boolean, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: Int) = putPrimitive(value)
public fun put(value: Int, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: Long) = putPrimitive(value)
public fun put(value: Long, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: Double) = putPrimitive(value)
public fun put(value: Double, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: String) = putPrimitive(value)
public fun put(value: String, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: ByteArray) = putPrimitive(value)
public fun put(value: ByteArray, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: Date) = putPrimitive(value)
public fun put(value: Date, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

public fun put(value: JsonPrimitive) = putPrimitive(value)
public fun put(value: JsonPrimitive, prevCreatedAt: TimeTicket? = null) =
putPrimitive(value, prevCreatedAt)

private fun putPrimitive(value: Any) {
private fun putPrimitive(value: Any, prevCreatedAt: TimeTicket? = null) {
val primitive = if (value is JsonPrimitive) {
value.target
} else {
CrdtPrimitive(value, context.issueTimeTicket())
}
putCrdtElement(primitive)
putCrdtElement(primitive, prevCreatedAt)
}

public fun putNewObject(): JsonObject {
public fun putNewObject(prevCreatedAt: TimeTicket? = null): JsonObject {
val obj = CrdtObject(context.issueTimeTicket(), rht = ElementRht())
putCrdtElement(obj)
putCrdtElement(obj, prevCreatedAt)
return obj.toJsonElement(context)
}

public fun putNewArray(): JsonArray {
public fun putNewArray(prevCreatedAt: TimeTicket? = null): JsonArray {
val array = CrdtArray(context.issueTimeTicket())
putCrdtElement(array)
putCrdtElement(array, prevCreatedAt)
return array.toJsonElement(context)
}

private fun putCrdtElement(value: CrdtElement) {
val prevCreated = target.lastCreatedAt
private fun putCrdtElement(value: CrdtElement, prevCreatedAt: TimeTicket? = null) {
val prevCreated = prevCreatedAt ?: target.lastCreatedAt
target.insertAfter(prevCreated, value)
context.registerElement(value, target)
context.push(
Expand Down Expand Up @@ -124,6 +130,22 @@ public class JsonArray internal constructor(
}

public fun moveAfter(prevCreatedAt: TimeTicket, createdAt: TimeTicket) {
moveInternal(prevCreatedAt, createdAt)
}

public fun moveBefore(nextCreatedAt: TimeTicket, createdAt: TimeTicket) {
moveInternal(target.getPrevCreatedAt(nextCreatedAt), createdAt)
}

public fun moveFront(createdAt: TimeTicket) {
moveInternal(target.head.createdAt, createdAt)
}

public fun moveLast(createdAt: TimeTicket) {
moveInternal(target.lastCreatedAt, createdAt)
}

private fun moveInternal(prevCreatedAt: TimeTicket, createdAt: TimeTicket) {
val executedAt = context.issueTimeTicket()
target.moveAfter(prevCreatedAt, createdAt, executedAt)
context.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ public class JsonCounter internal constructor(
internal val context: ChangeContext,
override val target: CrdtCounter,
) : JsonElement() {
public val id
get() = target.id

public val value
public val value: CounterValue
get() = target.value

public fun increase(value: Int): JsonCounter {
Expand Down
12 changes: 12 additions & 0 deletions yorkie/src/main/kotlin/dev/yorkie/document/json/JsonElement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.yorkie.document.crdt.CrdtElement
import dev.yorkie.document.crdt.CrdtObject
import dev.yorkie.document.crdt.CrdtPrimitive
import dev.yorkie.document.crdt.CrdtText
import dev.yorkie.document.time.TimeTicket

/**
* [JsonElement] is a wrapper for [CrdtElement] that provides users with an
Expand All @@ -15,6 +16,9 @@ import dev.yorkie.document.crdt.CrdtText
public abstract class JsonElement {
internal abstract val target: CrdtElement

public val id: TimeTicket
get() = target.id

public fun toJson() = target.toJson()

override fun toString() = toJson()
Expand Down Expand Up @@ -53,4 +57,12 @@ public abstract class JsonElement {
}
}
}

override fun equals(other: Any?): Boolean {
return target == (other as? JsonElement)?.target
}

override fun hashCode(): Int {
return target.hashCode()
}
}
2 changes: 0 additions & 2 deletions yorkie/src/main/kotlin/dev/yorkie/document/json/JsonObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ public class JsonObject internal constructor(
internal val context: ChangeContext,
override val target: CrdtObject,
) : JsonElement() {
public val id
get() = target.createdAt

public val keys: List<String>
get() = target.keys
Expand Down
4 changes: 0 additions & 4 deletions yorkie/src/main/kotlin/dev/yorkie/document/json/JsonText.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import dev.yorkie.document.crdt.TextWithAttributes
import dev.yorkie.document.operation.EditOperation
import dev.yorkie.document.operation.SelectOperation
import dev.yorkie.document.operation.StyleOperation
import dev.yorkie.document.time.TimeTicket
import dev.yorkie.util.YorkieLogger

/**
Expand All @@ -18,9 +17,6 @@ public class JsonText internal constructor(
override val target: CrdtText,
) : JsonElement() {

public val id: TimeTicket
get() = target.id

public val values: List<TextWithAttributes>
get() = target.values

Expand Down
203 changes: 203 additions & 0 deletions yorkie/src/test/kotlin/dev/yorkie/document/json/JsonArrayTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package dev.yorkie.document.json

import dev.yorkie.document.change.ChangeContext
import dev.yorkie.document.change.ChangeID
import dev.yorkie.document.crdt.CrdtArray
import dev.yorkie.document.crdt.CrdtObject
import dev.yorkie.document.crdt.CrdtPrimitive
import dev.yorkie.document.crdt.CrdtRoot
import dev.yorkie.document.crdt.ElementRht
import dev.yorkie.document.time.TimeTicket
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import java.util.Date
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNull
import kotlin.test.assertTrue

class JsonArrayTest {

private lateinit var target: JsonArray

@Before
fun setUp() {
val obj = CrdtObject(TimeTicket.InitialTimeTicket, rht = ElementRht())
val array = CrdtArray(TimeTicket.InitialTimeTicket)
target = JsonArray(
ChangeContext(
ChangeID.InitialChangeID,
CrdtRoot(obj),
null,
),
array,
)
}

@Test
fun `should put values and elements`() {
val jsonPrimitive = JsonPrimitive(CrdtPrimitive("json", TimeTicket.InitialTimeTicket))
target.apply {
put(false)
put(0)
put(1L)
put(2.0)
put("string")
put("byte array".toByteArray())
put(Date(1_000))
put(jsonPrimitive)
putNewArray().put(1)
putNewObject().setNewArray("array")
}
assertTrue(jsonPrimitive in target)
assertEquals(
"""[false,0,1,2.0,"string","byte array",1000,"json",[1],{"array":[]}]""",
target.toJson(),
)
}

@Test
fun `should put values and elements with prevCreatedAt`() {
val inserted = listOf(
target.putNewArray(),
target.putNewObject().apply {
set("k1", "v1")
},
)
assertEquals("""[[],{"k1":"v1"}]""", target.toJson())
assertTrue(target.containsAll(inserted))

val array = assertIs<JsonArray>(inserted.first())
target.put(1, array.id)
assertEquals("""[[],1,{"k1":"v1"}]""", target.toJson())

val obj = assertIs<JsonObject>(inserted.last())
target.put(false, obj.id)
assertEquals("""[[],1,{"k1":"v1"},false]""", target.toJson())
}

@Test
fun `should remove elements using index`() {
target.apply {
put(0)
putNewArray().put("value")
}
assertEquals("""[0,["value"]]""", target.toJson())

assertIs<JsonPrimitive>(target.removeAt(0))
assertEquals("""[["value"]]""", target.toJson())

assertNull(target.removeAt(3))
assertEquals("""[["value"]]""", target.toJson())

assertIs<JsonArray>(target.removeAt(0))
assertTrue(target.isEmpty())
}

@Test
fun `should remove elements using TimeTicket`() {
val obj = target.putNewObject()
assertIs<JsonObject>(target.last())
assertTrue(target.isNotEmpty())

target.remove(obj.target.createdAt)
assertTrue(target.isEmpty())

assertThrows(NoSuchElementException::class.java) {
target.remove(TimeTicket.MaxTimeTicket)
}
}

@Test
fun `should handle moveAfter`() {
target.apply {
put(1)
put(2)
put(3)
}
assertEquals("[1,2,3]", target.toJson())

val firstElement = requireNotNull(target.getAs<JsonPrimitive>(0))
val lastElement = requireNotNull(target.getAs<JsonPrimitive>(2))
target.moveAfter(lastElement.id, firstElement.id)
assertEquals("[2,3,1]", target.toJson())
}

@Test
fun `should handle moveBefore`() {
target.apply {
put(1)
put(2)
put(3)
}
assertEquals("[1,2,3]", target.toJson())

val firstElement = requireNotNull(target.getAs<JsonPrimitive>(0))
val lastElement = requireNotNull(target.getAs<JsonPrimitive>(2))
target.moveBefore(lastElement.id, firstElement.id)
assertEquals("[2,1,3]", target.toJson())
}

@Test
fun `should handle moveFront`() {
target.apply {
put(1)
put(2)
put(3)
}
assertEquals("[1,2,3]", target.toJson())

val lastElement = requireNotNull(target.getAs<JsonPrimitive>(2))
target.moveFront(lastElement.id)
assertEquals("[3,1,2]", target.toJson())
}

@Test
fun `should handle moveLast`() {
target.apply {
put(1)
put(2)
put(3)
}
assertEquals("[1,2,3]", target.toJson())

val firstElement = requireNotNull(target.getAs<JsonPrimitive>(0))
target.moveLast(firstElement.id)
assertEquals("[2,3,1]", target.toJson())
}

@Test
fun `should return null when index for get function does not exist`() {
assertNull(target[0])
}

@Test
fun `should return null when index for getAs function does not exist`() {
assertNull(target.getAs(0))
}

@Test
fun `should return null when TimeTicket for get function does not exist`() {
assertNull(target[TimeTicket.MaxTimeTicket])
}

@Test
fun `should return null when TimeTicket for getAs function does not exist`() {
assertNull(target.getAs(TimeTicket.MaxTimeTicket))
}

@Test
fun `should return requested type with getAs`() {
target.put(0)
val get = assertIs<JsonPrimitive>(target[0])
assertEquals(0, get.value)

val getAs = assertIs<JsonPrimitive>(target.getAs<JsonPrimitive>(0))
assertEquals(0, getAs.value)

assertThrows(TypeCastException::class.java) {
target.getAs<JsonArray>(0)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ package dev.yorkie.document.json
import dev.yorkie.assertJsonContentEquals
import dev.yorkie.document.Document
import dev.yorkie.document.crdt.CrdtCounter.CounterType
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertEquals

@OptIn(ExperimentalCoroutinesApi::class)
class JsonCounterTest {
private val document = Document(Document.Key(""))

Expand Down