diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonArray.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonArray.kt index ad4403e59..6eeb30248 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonArray.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonArray.kt @@ -20,8 +20,6 @@ public class JsonArray internal constructor( internal val context: ChangeContext, override val target: CrdtArray, ) : JsonElement(), Collection { - internal val id - get() = target.createdAt override val size: Int get() = target.length @@ -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( @@ -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( diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonCounter.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonCounter.kt index 59c6d2787..d7a15738e 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonCounter.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonCounter.kt @@ -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 { diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonElement.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonElement.kt index 35e2cd2ba..769bdf8d6 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonElement.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonElement.kt @@ -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 @@ -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() @@ -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() + } } diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonObject.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonObject.kt index cb6fe48ef..d700c6c19 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonObject.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonObject.kt @@ -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 get() = target.keys diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonText.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonText.kt index 712518318..499f44a7f 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonText.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonText.kt @@ -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 /** @@ -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 get() = target.values diff --git a/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonArrayTest.kt b/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonArrayTest.kt new file mode 100644 index 000000000..41da7e563 --- /dev/null +++ b/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonArrayTest.kt @@ -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(inserted.first()) + target.put(1, array.id) + assertEquals("""[[],1,{"k1":"v1"}]""", target.toJson()) + + val obj = assertIs(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(target.removeAt(0)) + assertEquals("""[["value"]]""", target.toJson()) + + assertNull(target.removeAt(3)) + assertEquals("""[["value"]]""", target.toJson()) + + assertIs(target.removeAt(0)) + assertTrue(target.isEmpty()) + } + + @Test + fun `should remove elements using TimeTicket`() { + val obj = target.putNewObject() + assertIs(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(0)) + val lastElement = requireNotNull(target.getAs(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(0)) + val lastElement = requireNotNull(target.getAs(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(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(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(target[0]) + assertEquals(0, get.value) + + val getAs = assertIs(target.getAs(0)) + assertEquals(0, getAs.value) + + assertThrows(TypeCastException::class.java) { + target.getAs(0) + } + } +} diff --git a/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonCounterTest.kt b/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonCounterTest.kt index c39330419..d72dc2714 100644 --- a/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonCounterTest.kt +++ b/yorkie/src/test/kotlin/dev/yorkie/document/json/JsonCounterTest.kt @@ -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(""))