From 7a8d1cac628fefe48501bd19a637dd2d6b773503 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Mon, 6 May 2024 19:35:03 +0200 Subject: [PATCH] Add Reference/Collections --- .idea/kotlinc.xml | 2 +- build.gradle.kts | 2 +- .../nl/astraeus/persistence/Datastore.kt | 17 ++- .../nl/astraeus/persistence/Reference.kt | 133 ------------------ .../nl/astraeus/persistence/Transaction.kt | 6 +- .../persistence/reference/Reference.kt | 28 ++++ .../reference/ReferenceCollection.kt | 82 +++++++++++ .../persistence/reference/ReferenceList.kt | 66 +++++++++ .../reference/ReferenceListIterator.kt | 47 +++++++ .../astraeus/persistence/TestPersistence.kt | 21 ++- .../nl/astraeus/persistence/TestReferences.kt | 92 ++++++++++++ .../nl/astraeus/persistence/TestThreaded.kt | 9 +- 12 files changed, 360 insertions(+), 145 deletions(-) delete mode 100644 src/main/kotlin/nl/astraeus/persistence/Reference.kt create mode 100644 src/main/kotlin/nl/astraeus/persistence/reference/Reference.kt create mode 100644 src/main/kotlin/nl/astraeus/persistence/reference/ReferenceCollection.kt create mode 100644 src/main/kotlin/nl/astraeus/persistence/reference/ReferenceList.kt create mode 100644 src/main/kotlin/nl/astraeus/persistence/reference/ReferenceListIterator.kt create mode 100644 src/test/kotlin/nl/astraeus/persistence/TestReferences.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fe63bb6..53bf319 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 696dbe6..c236054 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.9.23" + kotlin("jvm") version "2.0.0-RC2" id("maven-publish") id("signing") } diff --git a/src/main/kotlin/nl/astraeus/persistence/Datastore.kt b/src/main/kotlin/nl/astraeus/persistence/Datastore.kt index 6f3459b..53edd1e 100644 --- a/src/main/kotlin/nl/astraeus/persistence/Datastore.kt +++ b/src/main/kotlin/nl/astraeus/persistence/Datastore.kt @@ -44,7 +44,7 @@ class Datastore( private val fileManager = FileManager(directory) private val transactionFormatter = DecimalFormat("#") private var nextTransactionNumber = 1L - private val data: MutableMap, TypeData> = ConcurrentHashMap() + private var data: MutableMap, TypeData> = ConcurrentHashMap() private val indexes: MutableMap, MutableMap> = ConcurrentHashMap() init { @@ -66,6 +66,18 @@ class Datastore( } } + return data[javaClass]!!.nextId.get() + } + + fun getNextIdAndIncrement(javaClass: Class): Long { + if (data[javaClass] == null) { + synchronized(this) { + if (data[javaClass] == null) { + data[javaClass] = TypeData() + } + } + } + return data[javaClass]!!.nextId.getAndIncrement() } @@ -255,8 +267,7 @@ class Datastore( val versionNumber = ois.readInt() check(versionNumber == 1) { "Unsupported version number: $versionNumber" } nextTransactionNumber = ois.readLong() + 1 - data.clear() - data.putAll(ois.readObject() as MutableMap, TypeData>) + data = ois.readObject() as MutableMap, TypeData> val foundIndexes = mutableMapOf, MutableList>() val numberOfClassesWithIndex = ois.readInt() diff --git a/src/main/kotlin/nl/astraeus/persistence/Reference.kt b/src/main/kotlin/nl/astraeus/persistence/Reference.kt deleted file mode 100644 index 4d0c144..0000000 --- a/src/main/kotlin/nl/astraeus/persistence/Reference.kt +++ /dev/null @@ -1,133 +0,0 @@ -package nl.astraeus.nl.astraeus.persistence - -import java.io.Serializable -import kotlin.reflect.KProperty - -class Reference( - val cls: Class, -) : Serializable { - companion object { - private const val serialVersionUID: Long = 1L - } - - var id: Long = 0 - - operator fun getValue(thisRef: H, property: KProperty<*>): S { - return currentTransaction()?.find(cls.kotlin, id) ?: throw IllegalStateException("Reference not found") - } - - operator fun setValue(thisRef: H, property: KProperty<*>, value: S) { - id = value.id - // todo: only store if not already stored? - currentTransaction()?.store(value) - } -} - -class ListReference( - val cls: Class, -) : Serializable { - companion object { - private const val serialVersionUID: Long = 1L - } - - var ids: ReferenceList = ReferenceList(cls) - - operator fun getValue(thisRef: H, property: KProperty<*>): ReferenceList { - return ids - } - - operator fun setValue(thisRef: H, property: KProperty<*>, value: List) { - this.ids.clear() - this.ids.addAll(value) - } -} - -class ReferenceList( - val cls: Class, -) : MutableList { - val ids = ArrayList() - - private fun checkElementIsPersisted(element: T) { - if (currentTransaction()?.find(cls.kotlin, element.id) == null) { - currentTransaction()?.store(element) - } - } - - override val size: Int = ids.size - override fun clear() = ids.clear() - - override fun addAll(elements: Collection): Boolean { - TODO("Not yet implemented") - } - - override fun addAll(index: Int, elements: Collection): Boolean { - TODO("Not yet implemented") - } - - override fun add(index: Int, element: T) { - ids.add(index, element.id) - } - - override fun add(element: T): Boolean { - return ids.add(element.id) - } - - override fun get(index: Int): T = currentTransaction()?.find(cls.kotlin, ids[index]) ?: throw IllegalStateException("Reference not found") - - override fun isEmpty(): Boolean = ids.isEmpty() - - override fun iterator(): MutableIterator { - TODO("Not yet implemented") - } - - override fun listIterator(): MutableListIterator { - TODO("Not yet implemented") - } - - override fun listIterator(index: Int): MutableListIterator { - TODO("Not yet implemented") - } - - override fun removeAt(index: Int): T { - val id = ids.removeAt(index) - - return currentTransaction()?.find(cls.kotlin, id) ?: throw IllegalStateException("Reference not found") - } - - override fun set(index: Int, element: T): T { - TODO("Not yet implemented") - } - - override fun retainAll(elements: Collection): Boolean { - TODO("Not yet implemented") - } - - override fun removeAll(elements: Collection): Boolean { - TODO("Not yet implemented") - } - - override fun remove(element: T): Boolean { - TODO("Not yet implemented") - } - - override fun subList(fromIndex: Int, toIndex: Int): MutableList { - TODO("Not yet implemented") - } - - override fun lastIndexOf(element: T): Int { - TODO("Not yet implemented") - } - - override fun indexOf(element: T): Int { - TODO("Not yet implemented") - } - - override fun containsAll(elements: Collection): Boolean { - TODO("Not yet implemented") - } - - override fun contains(element: T): Boolean { - TODO("Not yet implemented") - } - -} \ No newline at end of file diff --git a/src/main/kotlin/nl/astraeus/persistence/Transaction.kt b/src/main/kotlin/nl/astraeus/persistence/Transaction.kt index 113301a..3a9c388 100644 --- a/src/main/kotlin/nl/astraeus/persistence/Transaction.kt +++ b/src/main/kotlin/nl/astraeus/persistence/Transaction.kt @@ -5,6 +5,7 @@ import kotlin.reflect.KClass inline fun Query.count(): Int = this.count(T::class) inline fun Query.find(id: Long): T? = this.find(T::class, id) +inline fun Query.all(): List = this.all(T::class) inline fun Query.search(noinline search: (T) -> Boolean): List = this.search(T::class, search) inline fun Query.findByIndex( @@ -42,6 +43,9 @@ open class Query( open fun find(clazz: KClass, id: Long): T? = persistent.datastore.find(clazz, id) + open fun all(clazz: Class): List = all(clazz.kotlin) + open fun all(clazz: KClass): List = search(clazz) { _ -> true } + open fun search(clazz: KClass, search: (T) -> Boolean): List = persistent.datastore.search(clazz, search) fun findByIndex( @@ -99,7 +103,7 @@ class Transaction( fun store(obj: Persistable) { if (obj.id == 0L) { - obj.id = persistent.datastore.getNextId(obj.javaClass) + obj.id = persistent.datastore.getNextIdAndIncrement(obj.javaClass) } else if (obj.id > persistent.datastore.getNextId(obj.javaClass)) { persistent.datastore.setMaxId(obj.javaClass, obj.id + 1) } diff --git a/src/main/kotlin/nl/astraeus/persistence/reference/Reference.kt b/src/main/kotlin/nl/astraeus/persistence/reference/Reference.kt new file mode 100644 index 0000000..02648d1 --- /dev/null +++ b/src/main/kotlin/nl/astraeus/persistence/reference/Reference.kt @@ -0,0 +1,28 @@ +package nl.astraeus.nl.astraeus.persistence.reference + +import nl.astraeus.nl.astraeus.persistence.Persistable +import nl.astraeus.nl.astraeus.persistence.currentTransaction +import kotlin.reflect.KProperty + +class Reference( + val cls: Class, + val setter: (Long?) -> Unit, + val getter: () -> Long?, +) { + + operator fun getValue(thisRef: Persistable, property: KProperty<*>): S? { + return currentTransaction()?.find(cls.kotlin, (getter() ?: 0L)) + } + + operator fun setValue(thisRef: Persistable, property: KProperty<*>, value: S?) { + if (value != null) { + // todo: only store if not already stored? + currentTransaction()?.store(value) + } + setter(value?.id) + } + + companion object { + private const val serialVersionUID: Long = 1L + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceCollection.kt b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceCollection.kt new file mode 100644 index 0000000..c804cfb --- /dev/null +++ b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceCollection.kt @@ -0,0 +1,82 @@ +package nl.astraeus.nl.astraeus.persistence.reference + +import nl.astraeus.nl.astraeus.persistence.Persistable +import nl.astraeus.nl.astraeus.persistence.currentTransaction +import java.io.Serializable + +inline fun referenceCollection( + ids: MutableCollection = mutableListOf() +) = ReferenceCollection(T::class.java, ids) + +open class ReferenceCollection( + val cls: Class, + val ids: MutableCollection = mutableListOf() +) : MutableCollection, Serializable { + + protected fun checkElementIsPersisted(element: T) { + if (currentTransaction()?.find(cls.kotlin, element.id) == null) { + currentTransaction()?.store(element) + } + } + + override val size: Int + get() { + return ids.size + } + + override fun clear() { + ids.clear() + } + + override fun isEmpty(): Boolean = ids.isEmpty() + + override fun iterator(): MutableIterator { + return object : MutableIterator { + private var idsIterator = ids.iterator() + + override fun hasNext(): Boolean = idsIterator.hasNext() + + override fun next(): T = idsIterator.next().let { + currentTransaction()?.find(cls.kotlin, it) + } ?: throw IllegalStateException("Reference not found") + + override fun remove() = idsIterator.remove() + } + } + + override fun retainAll(elements: Collection): Boolean { + return ids.retainAll(elements.map { it.id }.toSet()) + } + + override fun removeAll(elements: Collection): Boolean { + return ids.removeAll(elements.map { it.id }.toSet()) + } + + override fun remove(element: T): Boolean { + return ids.remove(element.id) + } + + override fun containsAll(elements: Collection): Boolean { + return ids.containsAll(elements.map { it.id }) + } + + override fun contains(element: T): Boolean { + return ids.contains(element.id) + } + + override fun addAll(elements: Collection): Boolean { + for (element in elements) { + checkElementIsPersisted(element) + } + return ids.addAll(elements.map { it.id }) + } + + override fun add(element: T): Boolean { + checkElementIsPersisted(element) + return ids.add(element.id) + } + + companion object { + private const val serialVersionUID: Long = 1L + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceList.kt b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceList.kt new file mode 100644 index 0000000..efc483f --- /dev/null +++ b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceList.kt @@ -0,0 +1,66 @@ +package nl.astraeus.nl.astraeus.persistence.reference + +import nl.astraeus.nl.astraeus.persistence.Persistable +import nl.astraeus.nl.astraeus.persistence.currentTransaction +import java.io.Serializable + +inline fun referenceList( + ids: MutableList = mutableListOf() +) = ReferenceList(T::class.java, ids) + +class ReferenceList( + cls: Class, + val idsList: MutableList = mutableListOf() +) : ReferenceCollection(cls, idsList), MutableList, Serializable { + + override fun add(index: Int, element: T) { + checkElementIsPersisted(element) + idsList.add(index, element.id) + } + + override fun addAll(index: Int, elements: Collection): Boolean { + for(element in elements) { + checkElementIsPersisted(element) + } + return idsList.addAll(index, elements.map { it.id }) + } + + override fun get(index: Int): T = currentTransaction()?.find(cls.kotlin, idsList[index]) ?: throw IllegalStateException("Reference not found") + override fun indexOf(element: T): Int { + return idsList.indexOf(element.id) + } + + override fun isEmpty(): Boolean = ids.isEmpty() + override fun lastIndexOf(element: T): Int { + return idsList.lastIndexOf(element.id) + } + + override fun listIterator(): MutableListIterator { + return ReferenceListIterator(cls, idsList) + } + + override fun listIterator(index: Int): MutableListIterator { + return ReferenceListIterator(cls, idsList, index) + } + + override fun removeAt(index: Int): T { + val id = idsList.removeAt(index) + + return currentTransaction()?.find(cls.kotlin, id) ?: throw IllegalStateException("Reference not found") + } + + override fun subList(fromIndex: Int, toIndex: Int): MutableList { + return ReferenceList(cls, idsList.subList(fromIndex, toIndex)) + } + + override fun set(index: Int, element: T): T { + checkElementIsPersisted(element) + idsList[index] = element.id + return element + } + + companion object { + private const val serialVersionUID: Long = 1L + } + +} diff --git a/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceListIterator.kt b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceListIterator.kt new file mode 100644 index 0000000..7da58f4 --- /dev/null +++ b/src/main/kotlin/nl/astraeus/persistence/reference/ReferenceListIterator.kt @@ -0,0 +1,47 @@ +package nl.astraeus.nl.astraeus.persistence.reference + +import nl.astraeus.nl.astraeus.persistence.Persistable +import nl.astraeus.nl.astraeus.persistence.currentTransaction + +class ReferenceListIterator( + private val cls: Class, + idsList: MutableList, + index: Int = 0 +) : MutableListIterator { + private val idsIterator = idsList.listIterator(index) + + private fun checkElementIsPersisted(element: T) { + if (currentTransaction()?.find(cls.kotlin, element.id) == null) { + currentTransaction()?.store(element) + } + } + + override fun add(element: T) { + checkElementIsPersisted(element) + idsIterator.add(element.id) + } + + override fun hasNext(): Boolean = idsIterator.hasNext() + override fun hasPrevious(): Boolean = idsIterator.hasPrevious() + + override fun next(): T = idsIterator.next().let { + currentTransaction()?.find(cls.kotlin, it) + } ?: throw IllegalStateException("Reference not found") + + override fun nextIndex(): Int = idsIterator.nextIndex() + + override fun previous(): T = idsIterator.previous().let { + currentTransaction()?.find(cls.kotlin, it) + } ?: throw IllegalStateException("Reference not found") + + override fun previousIndex(): Int = idsIterator.previousIndex() + + override fun remove() { + idsIterator.remove() + } + + override fun set(element: T) { + checkElementIsPersisted(element) + idsIterator.set(element.id) + } +} \ No newline at end of file diff --git a/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt b/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt index d931b7c..3f356a6 100644 --- a/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt +++ b/src/test/kotlin/nl/astraeus/persistence/TestPersistence.kt @@ -2,11 +2,12 @@ package nl.astraeus.persistence import nl.astraeus.nl.astraeus.persistence.Persistable import nl.astraeus.nl.astraeus.persistence.Persistent -import nl.astraeus.nl.astraeus.persistence.Reference import nl.astraeus.nl.astraeus.persistence.TransactionLog +import nl.astraeus.nl.astraeus.persistence.currentTransaction import nl.astraeus.nl.astraeus.persistence.find import nl.astraeus.nl.astraeus.persistence.findByIndex import nl.astraeus.nl.astraeus.persistence.index +import nl.astraeus.nl.astraeus.persistence.reference.ReferenceCollection import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import java.io.File @@ -19,7 +20,11 @@ class TestPersistence { override var version: Long = 0, val name: String ) : Persistable, Cloneable { - //var persons: MutableList by ListReference(Person::class.java) + val personIds: MutableList = ArrayList() + val persons: MutableCollection + get() { + return ReferenceCollection(Person::class.java, personIds) + } companion object { private const val serialVersionUID: Long = 1L @@ -35,15 +40,23 @@ class TestPersistence { override var version: Long = 0, val name: String, val age: Int, + private var companyId: Long? = null ) : Persistable, Cloneable { - var company: Company by Reference(Company::class.java) + var company: Company? + get() = currentTransaction()?.find(Company::class, companyId ?: 0L) + set(value) { + if (value != null) { + currentTransaction()?.store(value) + } + companyId = value?.id + } companion object { private const val serialVersionUID: Long = 1L } override fun toString(): String { - return "Person(id=$id, version=$version, name='$name', age=$age)" + return "Person(id=$id, version=$version, name='$name', age=$age, company=$companyId)" } } diff --git a/src/test/kotlin/nl/astraeus/persistence/TestReferences.kt b/src/test/kotlin/nl/astraeus/persistence/TestReferences.kt new file mode 100644 index 0000000..98a607c --- /dev/null +++ b/src/test/kotlin/nl/astraeus/persistence/TestReferences.kt @@ -0,0 +1,92 @@ +package nl.astraeus.persistence + +import nl.astraeus.nl.astraeus.persistence.Persistable +import nl.astraeus.nl.astraeus.persistence.Persistent +import nl.astraeus.nl.astraeus.persistence.TransactionLog +import nl.astraeus.nl.astraeus.persistence.currentTransaction +import nl.astraeus.nl.astraeus.persistence.reference.referenceCollection +import java.io.File +import kotlin.test.Test + +class TestReferences { + + class Company( + override var id: Long = 0, + override var version: Long = 0, + val name: String + ) : Persistable, Cloneable { + val persons: MutableCollection = referenceCollection() + + companion object { + private const val serialVersionUID: Long = 1L + } + + override fun toString(): String { + return "Company(id=$id, version=$version, name='$name', persons=${persons.size})" + } + } + + class Person( + override var id: Long = 0, + override var version: Long = 0, + val name: String, + val age: Int, + private var companyId: Long? = null + ) : Persistable, Cloneable { + var company: Company? + get() = currentTransaction()?.find(Company::class, companyId ?: 0L) + set(value) { + if (value != null) { + currentTransaction()?.store(value) + } + companyId = value?.id + } + + companion object { + private const val serialVersionUID: Long = 1L + } + + override fun toString(): String { + return "Person(id=$id, version=$version, name='$name', age=$age, company=$companyId)" + } + } + + @Test + fun showTransactions() { + val log = TransactionLog(File("data", "test-references")) + + log.showTransactions() + } + + @Test + fun testSerializeDeSerializeReferenceList() { + val pst = Persistent( + directory = File("data", "test-references"), + ) + + pst.transaction { + val company = find(Company::class, 1L) ?: Company( + id = 1L, + name = "ACME" + ) + + if (company.persons.isEmpty()) { + val person = Person( + id = 0L, + name = "John Doe", + age = 25 + ) + person.company = company + store(person) + company.persons.add(person) + } + + store(company) + + for (person in company.persons) { + println("Person: $person") + } + } + } + +} diff --git a/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt b/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt index 0ab5929..e8d1460 100644 --- a/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt +++ b/src/test/kotlin/nl/astraeus/persistence/TestThreaded.kt @@ -2,7 +2,7 @@ package nl.astraeus.persistence import nl.astraeus.nl.astraeus.persistence.Persistable import nl.astraeus.nl.astraeus.persistence.Persistent -import nl.astraeus.nl.astraeus.persistence.Reference +import nl.astraeus.nl.astraeus.persistence.reference.Reference import nl.astraeus.nl.astraeus.persistence.TransactionLog import nl.astraeus.nl.astraeus.persistence.count import nl.astraeus.nl.astraeus.persistence.index @@ -36,8 +36,13 @@ class TestThreaded { override var version: Long = 0, val name: String, val age: Int, + var companyId: Long? = null ) : Persistable, Cloneable { - var company: Company by Reference(Company::class.java) + var company: Company? by Reference( + Company::class.java, + { id -> companyId = id }, + { companyId } + ) companion object { private const val serialVersionUID: Long = 1L