Serializer is now specified instead of unit function that manages kryo registrations

This commit is contained in:
Robinson 2023-02-23 20:14:09 +01:00
parent 8cb3b5abec
commit 687889abce
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
3 changed files with 151 additions and 69 deletions

View File

@ -15,7 +15,9 @@
*/ */
package dorkbox.storage package dorkbox.storage
import com.esotericsoftware.kryo.Kryo import dorkbox.bytes.decodeBase58
import dorkbox.bytes.encodeToBase58String
import dorkbox.storage.serializer.SerializerBytes
import dorkbox.storage.types.MemoryStore import dorkbox.storage.types.MemoryStore
import dorkbox.storage.types.PropertyStore import dorkbox.storage.types.PropertyStore
import mu.KLogger import mu.KLogger
@ -24,7 +26,7 @@ import org.slf4j.helpers.NOPLogger
import java.io.File import java.io.File
typealias AccessFunc = ((key: Any, value: Any, load: (key: Any, value: Any) -> Unit) -> Unit) typealias AccessFunc = ((serializer: SerializerBytes, key: Any, value: Any?, load: (key: Any, value: Any?) -> Unit) -> Unit)
abstract class Storage(val logger: KLogger) : AutoCloseable { abstract class Storage(val logger: KLogger) : AutoCloseable {
companion object { companion object {
@ -39,6 +41,8 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
} }
const val versionTag = "__VERSION__" const val versionTag = "__VERSION__"
private val defaultLogger: KLogger = KotlinLogging.logger(NOPLogger.NOP_LOGGER)
} }
fun init(initMessage: String) { fun init(initMessage: String) {
@ -152,9 +156,9 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
fun onSave(onSave: AccessFunc): Builder fun onSave(onSave: AccessFunc): Builder
/** /**
* Specify how to configure the kryo instance for serialization. This instance of kryo will be used to serialize the key/value data, which is subsequently saved to disk * Specify how to configure the kryo instance for serialization. This instance of kryo will be used to serialize the key/value data, which is subsequently saved
*/ */
fun onNewSerializer(onNewKryo: Kryo.() -> Unit): Builder fun serializer(serializer: SerializerBytes): Builder
/** /**
* Assigns a logger to use for the storage system. The default is a No Operation (NOP) logger which will ignore everything. * Assigns a logger to use for the storage system. The default is a No Operation (NOP) logger which will ignore everything.
@ -165,14 +169,19 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
abstract class FileBuilder<B : Builder>: Builder { abstract class FileBuilder<B : Builder>: Builder {
var onLoad: AccessFunc? = null companion object {
var onSave: AccessFunc? = null private val defaultSerializer = SerializerBytes {}
var onNewKryo: Kryo.() -> Unit = { } }
abstract var onLoad: AccessFunc
abstract var onSave: AccessFunc
var serializer: SerializerBytes = defaultSerializer
var shared = false var shared = false
@Volatile var sharedBuild: Storage? = null @Volatile
var sharedBuild: Storage? = null
var logger: KLogger = KotlinLogging.logger(NOPLogger.NOP_LOGGER) var logger: KLogger = defaultLogger
var file = File("storage.db") var file = File("storage.db")
var readOnly = false var readOnly = false
@ -207,8 +216,8 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
return this return this
} }
override fun onNewSerializer(onNewKryo: Kryo.() -> Unit): Builder { override fun serializer(serializer: SerializerBytes): Builder {
this.onNewKryo = onNewKryo this.serializer = serializer
return this return this
} }
@ -249,6 +258,38 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
readOnlyViolent = true readOnlyViolent = true
return this as B return this as B
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is FileBuilder<*>) return false
if (onLoad != other.onLoad) return false //lambda
if (onSave != other.onSave) return false //lambda
if (serializer != other.serializer) return false //lambda
if (shared != other.shared) return false
if (sharedBuild != other.sharedBuild) return false
if (logger != other.logger) return false
if (file != other.file) return false
if (readOnly != other.readOnly) return false
if (readOnlyViolent != other.readOnlyViolent) return false
if (isStringBased != other.isStringBased) return false
return true
}
override fun hashCode(): Int {
var result = onLoad?.hashCode() ?: 0 //lambda
result = 31 * result + (onSave?.hashCode() ?: 0) //lambda
result = 31 * result + serializer.hashCode() //lambda
result = 31 * result + shared.hashCode()
result = 31 * result + (sharedBuild?.hashCode() ?: 0)
result = 31 * result + logger.hashCode()
result = 31 * result + file.hashCode()
result = 31 * result + readOnly.hashCode()
result = 31 * result + readOnlyViolent.hashCode()
result = 31 * result + isStringBased.hashCode()
return result
}
} }
@ -260,7 +301,7 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
* This storage system DOES NOT care about serializing data, so `register` has no effect. * This storage system DOES NOT care about serializing data, so `register` has no effect.
*/ */
class Memory : Builder { class Memory : Builder {
private var logger: KLogger = KotlinLogging.logger(NOPLogger.NOP_LOGGER) private var logger: KLogger = defaultLogger
private var shared = false private var shared = false
@Volatile private var sharedBuild: Storage? = null @Volatile private var sharedBuild: Storage? = null
@ -292,7 +333,7 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
return this return this
} }
override fun onNewSerializer(onNewKryo: Kryo.() -> Unit): Builder { override fun serializer(serializer: SerializerBytes): Builder {
return this return this
} }
@ -304,14 +345,55 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
this.logger = logger this.logger = logger
return this return this
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Memory) return false
if (logger != other.logger) return false
if (shared != other.shared) return false
if (sharedBuild != other.sharedBuild) return false
if (isStringBased != other.isStringBased) return false
return true
}
override fun hashCode(): Int {
var result = logger.hashCode()
result = 31 * result + shared.hashCode()
result = 31 * result + (sharedBuild?.hashCode() ?: 0)
result = 31 * result + isStringBased.hashCode()
return result
}
} }
/** /**
* Java property file storage system * Java property file storage system
*/ */
class Property : FileBuilder<Property>() { class Property : FileBuilder<Property>() {
companion object {
// property files MUST load via strings.
val defaultOnLoad: AccessFunc = { serializer, key, value, load ->
val keyBytes = (key as String).decodeBase58()
val valueBytes = (value as String).decodeBase58()
val xKey = serializer.deserialize<Any>(keyBytes)!!
val xValue = serializer.deserialize<Any>(valueBytes)
load(xKey, xValue)
}
val defaultOnSave: AccessFunc = { serializer, key, value, save ->
val xKey = serializer.serialize(key).encodeToBase58String()
val xValue = serializer.serialize(value).encodeToBase58String()
save(xKey, xValue)
}
}
private var autoLoad = false private var autoLoad = false
override var onLoad: AccessFunc = defaultOnLoad
override var onSave: AccessFunc = defaultOnSave
override val isStringBased = true override val isStringBased = true
/** /**
@ -319,8 +401,16 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
*/ */
override fun build(): Storage { override fun build(): Storage {
return manageShared { return manageShared {
PropertyStore(file.absoluteFile, autoLoad, readOnly, readOnlyViolent, logger, onNewKryo, PropertyStore(
onLoad, onSave) dbFile = file.absoluteFile,
autoLoad = autoLoad,
readOnly = readOnly,
readOnlyViolent = readOnlyViolent,
logger = logger,
serializer = serializer,
onLoad = onLoad,
onSave = onSave
)
} }
} }
@ -331,6 +421,24 @@ abstract class Storage(val logger: KLogger) : AutoCloseable {
autoLoad = true autoLoad = true
return this return this
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Property) return false
if (!super.equals(other)) return false
if (autoLoad != other.autoLoad) return false
if (isStringBased != other.isStringBased) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + autoLoad.hashCode()
result = 31 * result + isStringBased.hashCode()
return result
}
} }
// /** // /**

View File

@ -15,8 +15,8 @@
*/ */
package dorkbox.storage.types package dorkbox.storage.types
import com.esotericsoftware.kryo.Kryo
import dorkbox.storage.AccessFunc import dorkbox.storage.AccessFunc
import dorkbox.storage.serializer.SerializerBytes
import mu.KLogger import mu.KLogger
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@ -32,10 +32,19 @@ class PropertyStore(
readOnly: Boolean, readOnly: Boolean,
readOnlyViolent: Boolean, readOnlyViolent: Boolean,
logger: KLogger, logger: KLogger,
onNewKryo: Kryo.() -> Unit, serializer: SerializerBytes,
onLoad: AccessFunc?, onLoad: AccessFunc,
onSave: AccessFunc?, onSave: AccessFunc,
) : StringStore(dbFile, autoLoad, readOnly, readOnlyViolent, logger, onNewKryo, onLoad, onSave) { ) : StringStore(
dbFile = dbFile,
autoLoad = autoLoad,
readOnly = readOnly,
readOnlyViolent = readOnlyViolent,
logger = logger,
serializer = serializer,
onLoad = onLoad,
onSave = onSave
) {
private val propertiesOnDisk = object : Properties() { private val propertiesOnDisk = object : Properties() {
override fun keys(): Enumeration<Any> { override fun keys(): Enumeration<Any> {
@ -71,7 +80,7 @@ class PropertyStore(
} else { } else {
try { try {
propertiesOnDisk[k] = v propertiesOnDisk[k] = v
onLoadFunc(k, v, loadFunc) onLoad(serializer, k, v, loadFunc)
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Unable to parse property ($dbFile) [$k] : $v", e) logger.error("Unable to parse property ($dbFile) [$k] : $v", e)
} }

View File

@ -15,9 +15,6 @@
*/ */
package dorkbox.storage.types package dorkbox.storage.types
import com.esotericsoftware.kryo.Kryo
import dorkbox.bytes.decodeBase58
import dorkbox.bytes.encodeToBase58String
import dorkbox.storage.AccessFunc import dorkbox.storage.AccessFunc
import dorkbox.storage.Storage import dorkbox.storage.Storage
import dorkbox.storage.serializer.SerializerBytes import dorkbox.storage.serializer.SerializerBytes
@ -35,9 +32,9 @@ abstract class StringStore(
val readOnly: Boolean, val readOnly: Boolean,
val readOnlyViolent: Boolean, val readOnlyViolent: Boolean,
logger: KLogger, logger: KLogger,
onNewKryo: Kryo.() -> Unit, val serializer: SerializerBytes,
onLoad: AccessFunc?, val onLoad: AccessFunc,
onSave: AccessFunc?, val onSave: AccessFunc,
) : Storage(logger) { ) : Storage(logger) {
companion object { companion object {
@ -52,13 +49,10 @@ abstract class StringStore(
@Volatile @Volatile
private var isDirty = false private var isDirty = false
protected val onLoadFunc: AccessFunc protected val loadFunc: (key: Any, value: Any?) -> Unit
private val onSaveFunc: AccessFunc
protected val loadFunc: (key: Any, value: Any) -> Unit
protected val saveFunc: (key: Any, value: Any?) -> Unit protected val saveFunc: (key: Any, value: Any?) -> Unit
private val loadedProps = ConcurrentHashMap<Any, Any>() private val loadedProps = ConcurrentHashMap<Any, Any?>()
private val serializer = SerializerBytes(onNewKryo)
init { init {
loadFunc = { key, value -> loadFunc = { key, value ->
@ -68,29 +62,6 @@ abstract class StringStore(
onSave(key, value) onSave(key, value)
} }
onLoadFunc = if (onLoad != null) {
onLoad
} else {
{ key, value, load ->
val keyBytes = (key as String).decodeBase58()
val valueBytes = (value as String).decodeBase58()
val xKey = serializer.deserialize<Any>(keyBytes)!!
val xValue = serializer.deserialize<Any>(valueBytes)!!
load(xKey, xValue)
}
}
onSaveFunc = if (onSave != null) {
onSave
} else {
{ key, value, save ->
val xKey = serializer.serialize(key)!!.encodeToBase58String()
val xValue = serializer.serialize(value)!!.encodeToBase58String()
save(xKey, xValue)
}
}
// Make sure that the timer is run on shutdown. A HARD shutdown will just POW! kill it, a "nice" shutdown will run the hook // Make sure that the timer is run on shutdown. A HARD shutdown will just POW! kill it, a "nice" shutdown will run the hook
Runtime.getRuntime().addShutdownHook(thread) Runtime.getRuntime().addShutdownHook(thread)
} }
@ -132,7 +103,7 @@ abstract class StringStore(
synchronized(dbFile) { synchronized(dbFile) {
if (value != null) { if (value != null) {
try { try {
onSaveFunc(key, value, saveFunc) onSave(serializer, key, value, saveFunc)
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Unable to parse property ($dbFile) [$key] : $value", e) logger.error("Unable to parse property ($dbFile) [$key] : $value", e)
} }
@ -168,7 +139,7 @@ abstract class StringStore(
save(k, v.toString()) save(k, v.toString())
} else { } else {
try { try {
onSaveFunc(k, v, saveFunc) onSave(serializer, k, v, saveFunc)
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Unable to parse property ($dbFile) [$k] : $v", e) logger.error("Unable to parse property ($dbFile) [$k] : $v", e)
} }
@ -287,15 +258,12 @@ abstract class StringStore(
if (autoLoad != other.autoLoad) return false if (autoLoad != other.autoLoad) return false
if (readOnly != other.readOnly) return false if (readOnly != other.readOnly) return false
if (readOnlyViolent != other.readOnlyViolent) return false if (readOnlyViolent != other.readOnlyViolent) return false
if (thread != other.thread) return false
if (lastModifiedTime != other.lastModifiedTime) return false if (lastModifiedTime != other.lastModifiedTime) return false
if (isDirty != other.isDirty) return false if (isDirty != other.isDirty) return false
if (onLoadFunc != other.onLoadFunc) return false if (serializer != other.serializer) return false // class with lambda
if (onSaveFunc != other.onSaveFunc) return false if (onLoad != other.onLoad) return false //lambda
if (loadFunc != other.loadFunc) return false if (onSave != other.onSave) return false //lambda
if (saveFunc != other.saveFunc) return false
if (loadedProps != other.loadedProps) return false if (loadedProps != other.loadedProps) return false
if (serializer != other.serializer) return false
return true return true
} }
@ -305,15 +273,12 @@ abstract class StringStore(
result = 31 * result + autoLoad.hashCode() result = 31 * result + autoLoad.hashCode()
result = 31 * result + readOnly.hashCode() result = 31 * result + readOnly.hashCode()
result = 31 * result + readOnlyViolent.hashCode() result = 31 * result + readOnlyViolent.hashCode()
result = 31 * result + thread.hashCode()
result = 31 * result + lastModifiedTime.hashCode() result = 31 * result + lastModifiedTime.hashCode()
result = 31 * result + isDirty.hashCode() result = 31 * result + isDirty.hashCode()
result = 31 * result + onLoadFunc.hashCode() result = 31 * result + serializer.hashCode() // class with lambda
result = 31 * result + onSaveFunc.hashCode() result = 31 * result + onLoad.hashCode() //lambda
result = 31 * result + loadFunc.hashCode() result = 31 * result + onSave.hashCode() //lambda
result = 31 * result + saveFunc.hashCode()
result = 31 * result + loadedProps.hashCode() result = 31 * result + loadedProps.hashCode()
result = 31 * result + serializer.hashCode()
return result return result
} }
} }