Browse Source

add DataArray

00ll00 5 years ago
parent
commit
c3bacf8abc

+ 3 - 3
src/main/kotlin/bot_luo_core/bot/BotLuo.kt

@@ -1,7 +1,7 @@
 package bot_luo_core.bot
 
 import bot_luo_core.cli.CmdHandler
-import bot_luo_core.data.DataObj
+import bot_luo_core.data.Data
 import bot_luo_core.util.JsonWorker
 import bot_luo_core.util.Logger
 import bot_luo_core.util.Time
@@ -273,8 +273,8 @@ object BotLuo {
 
     fun saveAll() {
         runBlocking {
-            DataObj.savingJobs.values.forEach { it.start(); it.join() }
-            DataObj.savingJobs.keys.forEach { it.cancelAndJoin() }
+            Data.savingJobs.values.forEach { it.start(); it.join() }
+            Data.savingJobs.keys.forEach { it.cancelAndJoin() }
         }
     }
 

+ 64 - 18
src/main/kotlin/bot_luo_core/cli/commands/DataCmd.kt

@@ -5,15 +5,11 @@ import bot_luo_core.cli.annotation.Argument
 import bot_luo_core.cli.annotation.Command
 import bot_luo_core.cli.annotation.Method
 import bot_luo_core.cli.checkers.GroupCmdWorkingChecker
-import bot_luo_core.cli.commands.DataCmd.Companion.castTo
 import bot_luo_core.cli.exceptions.SyntaxError
 import bot_luo_core.cli.handlers.GroupArgHandler
 import bot_luo_core.cli.handlers.JsonPathArgHandler
 import bot_luo_core.cli.handlers.UserArgHandler
-import bot_luo_core.data.DataObj
-import bot_luo_core.data.Group
-import bot_luo_core.data.User
-import bot_luo_core.data.withLockedAccessing
+import bot_luo_core.data.*
 import bot_luo_core.util.TableBuilder
 import com.alibaba.fastjson.JSONArray
 import com.alibaba.fastjson.JSONObject
@@ -32,12 +28,29 @@ class DataCmd(context: CmdContext) : Cmd(context) {
 
     /*  ========================  get  ========================  */
 
-    fun get(dataObj: DataObj, objName: String, objId: String = "", path: ArrayList<String> = ArrayList()): CmdReceipt {
+    fun get(dataObject: DataObject, objName: String, objId: String = "", path: ArrayList<String> = ArrayList()): CmdReceipt {
         val table = TableBuilder(4)
         table.th("${objName}数据 —— $objId")
         table.p("/"+path.joinToString("/")).br().br()
         try {
-            readByPath(dataObj.jsonObj, path, table)
+            readByPath(dataObject.jsonObj, path, table)
+            context.print(table.toString())
+            return SUCCESS
+        } catch (e: NullPointerException) {
+            table.tr("元素不存在")
+        } catch (e: IndexOutOfBoundsException) {
+            table.tr("数组下标超出范围")
+        }
+        context.print(table.toString())
+        return FATAL
+    }
+
+    fun get(dataArray: DataArray, arrayName: String, arrayId: String = "", path: ArrayList<String> = ArrayList()): CmdReceipt {
+        val table = TableBuilder(4)
+        table.th("${arrayName}数据 —— $arrayId")
+        table.p("/"+path.joinToString("/")).br().br()
+        try {
+            readByPath(dataArray.jsonArray, path, table)
             context.print(table.toString())
             return SUCCESS
         } catch (e: NullPointerException) {
@@ -67,12 +80,30 @@ class DataCmd(context: CmdContext) : Cmd(context) {
 
     /*  ========================  set  ========================  */
 
-    suspend fun set(dataObj: DataObj, objName: String, objId: String = "", path: ArrayList<String>, value: String): CmdReceipt {
+    suspend fun set(dataObject: DataObject, objName: String, objId: String = "", path: ArrayList<String>, value: String): CmdReceipt {
         val table = TableBuilder(4)
         table.th("写入${objName}数据 —— $objId")
         table.p("/"+path.joinToString("/")).br().br()
-        try { withLockedAccessing(dataObj) {
-            val (old, new) = writeByPath(dataObj.jsonObj, path, value)
+        try { withLockedAccessing(dataObject) {
+            val (old, new) = writeByPath(dataObject.jsonObj, path, value)
+            table.tr(formatValue(old)).tb("->").tb(formatValue(new))
+            context.print(table.toString())
+            return SUCCESS
+        } } catch (e: NullPointerException) {
+            table.tr("拒绝访问")
+        } catch (e: IndexOutOfBoundsException) {
+            table.tr("数组下标超出范围")
+        }
+        context.print(table.toString())
+        return FATAL
+    }
+
+    suspend fun set(dataArray: DataArray, arrayName: String, arrayId: String = "", path: ArrayList<String>, value: String): CmdReceipt {
+        val table = TableBuilder(4)
+        table.th("写入${arrayName}数据 —— $arrayId")
+        table.p("/"+path.joinToString("/")).br().br()
+        try { withLockedAccessing(dataArray) {
+            val (old, new) = writeByPath(dataArray.jsonArray, path, value)
             table.tr(formatValue(old)).tb("->").tb(formatValue(new))
             context.print(table.toString())
             return SUCCESS
@@ -85,12 +116,30 @@ class DataCmd(context: CmdContext) : Cmd(context) {
         return FATAL
     }
 
-    suspend fun set(dataObj: DataObj, objName: String, objId: String = "", path: ArrayList<String>, value: String, type: String): CmdReceipt {
+    suspend fun set(dataObject: DataObject, objName: String, objId: String = "", path: ArrayList<String>, value: String, type: String): CmdReceipt {
         val table = TableBuilder(4)
         table.th("写入${objName}数据 —— $objId")
         table.p("/"+path.joinToString("/")).br().br()
-        try { withLockedAccessing(dataObj) {
-            val (old, new) = writeByPathTyped(dataObj.jsonObj, path, value castTo type)
+        try { withLockedAccessing(dataObject) {
+            val (old, new) = writeByPathTyped(dataObject.jsonObj, path, value castTo type)
+            table.tr(formatValue(old)).tb("->").tb(formatValue(new))
+            context.print(table.toString())
+            return SUCCESS
+        } } catch (e: NullPointerException) {
+            table.tr("拒绝访问")
+        } catch (e: IndexOutOfBoundsException) {
+            table.tr("数组下标超出范围")
+        }
+        context.print(table.toString())
+        return FATAL
+    }
+
+    suspend fun set(dataArray: DataArray, arrayName: String, arrayId: String = "", path: ArrayList<String>, value: String, type: String): CmdReceipt {
+        val table = TableBuilder(4)
+        table.th("写入${arrayName}数据 —— $arrayId")
+        table.p("/"+path.joinToString("/")).br().br()
+        try { withLockedAccessing(dataArray) {
+            val (old, new) = writeByPathTyped(dataArray.jsonArray, path, value castTo type)
             table.tr(formatValue(old)).tb("->").tb(formatValue(new))
             context.print(table.toString())
             return SUCCESS
@@ -189,7 +238,7 @@ class DataCmd(context: CmdContext) : Cmd(context) {
                 is Map<*, *> -> "<object: ${value.size}>"
                 is List<*> -> "<array: ${value.size}>"
                 null -> "null"
-                else -> "<unk>"
+                else -> "<${value::class.simpleName}>"
             }
         }
 
@@ -197,13 +246,10 @@ class DataCmd(context: CmdContext) : Cmd(context) {
         private fun readByPath(obj: Any?, path: ArrayList<String> = ArrayList(), table: TableBuilder) {
             if (path.isEmpty()) {
                 when (obj) {
-                    is Boolean -> table.tr(obj)
-                    is Number -> table.tr(obj)
-                    is String -> table.tr("\"$obj\"")
                     is JSONObject -> objectGet(obj, table)
                     is JSONArray -> arrayGet(obj, table)
                     null -> throw NullPointerException()
-                    else -> table.tr("<unk>")
+                    else -> table.tr(formatValue(obj))
                 }
             } else {
                 when (obj) {

+ 2 - 2
src/main/kotlin/bot_luo_core/cli/commands/DebugCmd.kt

@@ -4,7 +4,7 @@ import bot_luo_core.bot.BotLuo
 import bot_luo_core.cli.*
 import bot_luo_core.cli.annotation.Command
 import bot_luo_core.cli.annotation.Method
-import bot_luo_core.data.DataObj
+import bot_luo_core.data.Data
 import bot_luo_core.data.Groups
 import bot_luo_core.data.Users
 import bot_luo_core.util.TableBuilder
@@ -25,7 +25,7 @@ class DebugCmd(context: CmdContext) : Cmd(context) {
         table.tr("start_at:").tb( BotLuo.startAt relativeTo context.time)
         table.tr("active_groups:").tb(Groups.activeGroupsCount)
         table.tr("active_users:").tb(Users.activeUsersCount)
-        table.tr("save_jobs:").tb(DataObj.savingJobs.size)
+        table.tr("save_jobs:").tb(Data.savingJobs.size)
         context.print(table.toString())
         return SUCCESS
     }

+ 1 - 1
src/main/kotlin/bot_luo_core/data/CmdDataObj.kt

@@ -4,7 +4,7 @@ import bot_luo_core.cli.CmdExecutable
 import kotlinx.atomicfu.AtomicInt
 import kotlinx.atomicfu.atomic
 
-abstract class CmdDataObj(filePath: String): DataObj(filePath, 3000, true) {
+abstract class CmdDataObj(filePath: String): DataObject(filePath, 3000, true) {
 
     private val cmdData: HashMap<String, CmdData> =  getObj("cmdData")?: HashMap()
     private val cmdHandling = HashMap<CmdExecutable, AtomicInt>()

+ 140 - 0
src/main/kotlin/bot_luo_core/data/Data.kt

@@ -0,0 +1,140 @@
+package bot_luo_core.data
+
+import kotlinx.atomicfu.AtomicInt
+import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.Mutex
+import java.io.File
+
+/**
+ * # 数据类
+ *
+ * 继承[Mutex]
+ *
+ * 数据读取与写入由继承类实现
+ */
+abstract class Data(
+    /**
+     * JSON数据文件路径
+     */
+    protected val filePath: String,
+    /**
+     * 自动保存间隔时间(ms)
+     *
+     * 在访问空闲超过间隔后自动保存
+     */
+    private val autoSaveInterval: Long,
+    /**
+     * 是否自动卸载
+     *
+     * 为`true`时将把读取数据算作访问,且在自动保存后调用[unload]方法
+     */
+    private val saveAndUnload: Boolean
+) : Mutex by Mutex() {
+
+    private var saveJob: Job? = null
+
+    /**
+     * 访问数量
+     *
+     * 以此为依据判断是否在活跃,当访问数归零时开始计时尝试保存或卸载
+     *
+     * 并不能确保线程安全,仅保证保存的数据有效
+     *
+     * 使用[access]和[exit]方法更新
+     */
+    private var accessing: AtomicInt = atomic(0)
+    protected var changed: Boolean = false
+
+    /**
+     * 进行访问,[accessing]加一
+     *
+     * @see withAccessing
+     * @see withLockedAccessing
+     */
+    fun access() {
+        accessing.addAndGet(1)
+        saveJob?.cancel()
+        savingJobs.remove(saveJob)
+        saveJob = null
+    }
+
+    /**
+     * 退出访问,[accessing]减一
+     *
+     * @see withAccessing
+     * @see withLockedAccessing
+     */
+    fun exit() {
+        if (accessing.addAndGet(-1) == 0) {
+            launchSaveJob()
+        }
+    }
+
+    /**
+     * 保存兼卸载任务
+     */
+    protected fun launchSaveJob() {
+        saveJob?.cancel()
+        savingJobs.remove(saveJob)
+        saveJob = scope.launch {
+            try {
+                delay(autoSaveInterval)
+            } catch (ignore: CancellationException) {
+                return@launch
+            }
+            if (changed) savingJobs[saveJob]?.start()
+            if (saveAndUnload) unload()
+        }.apply {
+            savingJobs[this] = scope.launch(start = CoroutineStart.LAZY) { save() }
+            this.invokeOnCompletion { savingJobs.remove(this) }
+        }
+    }
+
+    abstract fun save()
+
+    /**
+     * 移除数据
+     *
+     * 需要确保卸载时数据已使用[save]存入磁盘
+     */
+    abstract fun unload()
+
+    /**
+     * 是否已有记录文件存在
+     */
+    fun exists() = File(filePath).exists()
+
+    companion object {
+        val savingJobs = HashMap<Job, Job>()
+        val scope = CoroutineScope(Dispatchers.IO)
+    }
+}
+
+/**
+ * 用于记录[Data]使用状况,实现自动保存
+ *
+ * 此方法不对数据加锁
+ */
+inline fun <T> withAccessing(vararg obj: Data, action: () -> T): T {
+    obj.forEach { it.access() }
+    try {
+        return action()
+    } finally {
+        obj.forEach { it.exit() }
+    }
+}
+
+/**
+ * 用于记录[Data]使用状况,实现自动保存
+ *
+ * 此方法带锁,使用时需避免锁死
+ */
+suspend inline fun <T> withLockedAccessing(vararg obj: Data, owner: Any? = null, action: () -> T): T {
+    obj.forEach { it.lock(owner); it.access() }
+    try {
+        return action()
+    } finally {
+        obj.forEach { it.exit(); it.unlock(owner) }
+    }
+}

+ 111 - 0
src/main/kotlin/bot_luo_core/data/DataArray.kt

@@ -0,0 +1,111 @@
+package bot_luo_core.data
+
+import com.alibaba.fastjson.JSON
+import com.alibaba.fastjson.JSONArray
+import com.alibaba.fastjson.JSONObject
+import com.alibaba.fastjson.TypeReference
+import com.alibaba.fastjson.parser.ParserConfig
+import com.alibaba.fastjson.util.TypeUtils
+import kotlinx.coroutines.sync.Mutex
+import java.io.File
+
+/**
+ * # 数据对象类
+ *
+ * 根对象为一个[JSONObject]
+ *
+ * 继承[Mutex]
+ *
+ * 建议使用[withAccessing]进行读访问,使用[withLockedAccessing]进行读写访问
+ */
+abstract class DataArray(
+    /**
+     * JSON数据文件路径
+     */
+    filePath: String,
+    /**
+     * 自动保存间隔时间(ms)
+     *
+     * 在访问空闲超过间隔后自动保存
+     */
+    autoSaveInterval: Long,
+    /**
+     * 是否自动卸载
+     *
+     * 为`true`时将把读取数据算作访问,且在自动保存后调用[unload]方法
+     */
+    saveAndUnload: Boolean
+): Data(filePath, autoSaveInterval, saveAndUnload) {
+
+    /**
+     * 使用[getObj]和[setObj]进行访问
+     */
+    val jsonArray: JSONArray
+
+    init {
+        val file = File(filePath)
+        jsonArray = if (file.exists())
+            JSON.parseArray (file.readText().ifBlank { "[]" })
+        else
+            JSONArray()
+        if (saveAndUnload) launchSaveJob()
+    }
+
+    /**
+     * 保存前需将有变动的数据使用[setObj]存入[jsonArray]
+     *
+     * 会自动创建不存在的文件和文件夹
+     */
+    override fun save() {
+        val file = File(filePath)
+        if (!file.exists()) {
+            if (!file.parentFile.exists()) file.parentFile.mkdirs()
+            file.createNewFile()
+        }
+        file.writeText(JSON.toJSONString(jsonArray, true))
+        changed = false
+    }
+
+    /**
+     * 读取数据,若不存在则返回`null`
+     *
+     * 自动更新访问时间
+     */
+    inline fun <reified T> getObj(index: Int): T? {
+        return if (index in jsonArray.indices) {
+            val tr = object : TypeReference<T>() {}
+            jsonArray.getObject(index, tr)
+        } else null
+    }
+    inline fun <reified T: List<*>> getArray(): T {
+        val tr = object : TypeReference<T>() {}
+        return jsonArray.toJavaObject(tr)
+    }
+
+    fun <T> JSONArray.getObject(index: Int, typeReference: TypeReference<*>?): T {
+        val obj: Any = this[index]
+        @Suppress("UNCHECKED_CAST")
+        return if (typeReference == null) {
+            obj as T
+        } else TypeUtils.cast(obj, typeReference.type, ParserConfig.getGlobalInstance())
+    }
+
+    /**
+     * 写入数据,不触发保存文件
+     *
+     * 此方法应在[withAccessing]或[withLockedAccessing]代码块中使用
+     *
+     * 自动设置被更改状态
+     *
+     * @see withAccessing
+     * @see withLockedAccessing
+     */
+    fun <T> setObj(index: Int, value: T?) {
+        jsonArray[index] = value
+        changed = true
+    }
+    fun <T: List<*>> setArray(value: T) {
+        jsonArray.fluentClear().fluentAddAll(value)
+        changed = true
+    }
+}

+ 0 - 204
src/main/kotlin/bot_luo_core/data/DataObj.kt

@@ -1,204 +0,0 @@
-package bot_luo_core.data
-
-import com.alibaba.fastjson.JSON
-import com.alibaba.fastjson.JSONObject
-import com.alibaba.fastjson.TypeReference
-import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.Mutex
-import java.io.File
-
-/**
- * # 数据对象类
- *
- * 继承[Mutex]
- *
- * 建议使用[withAccessing]进行读访问,使用[withLockedAccessing]进行读写访问
- */
-abstract class DataObj(
-    /**
-     * JSON数据文件路径
-     */
-    private val filePath: String,
-    /**
-     * 自动保存间隔时间(ms)
-     *
-     * 在访问空闲超过间隔后自动保存
-     */
-    private val autoSaveInterval: Long,
-    /**
-     * 是否自动卸载
-     *
-     * 为`true`时将把读取数据算作访问,且在自动保存后调用[unload]方法
-     */
-    private val saveAndUnload: Boolean
-): Mutex by Mutex() {
-
-    /**
-     * 使用[getObj]和[setObj]进行访问
-     */
-    val jsonObj: JSONObject
-
-    private var saveJob: Job? = null
-
-    /**
-     * 访问数量
-     *
-     * 以此为依据判断是否在活跃,当访问数归零时开始计时尝试保存或卸载
-     *
-     * 并不能确保线程安全,仅保证保存的数据有效
-     *
-     * 使用[access]和[]方法更新
-     */
-    private var accessing = atomic(0)
-    private var changed = false
-
-    /**
-     * 进行访问,[accessing]加一
-     *
-     * @see withAccessing
-     * @see withLockedAccessing
-     */
-    fun access() {
-        accessing.addAndGet(1)
-        saveJob?.cancel()
-        saveJob = null
-    }
-
-    /**
-     * 退出访问,[accessing]减一
-     *
-     * @see withAccessing
-     * @see withLockedAccessing
-     */
-    fun exit() {
-        if ( accessing.addAndGet(-1) == 0 ) {
-            launchSaveJob()
-        }
-    }
-
-    /**
-     * 保存兼卸载任务
-     */
-    private fun launchSaveJob() {
-        saveJob?.cancel()
-        savingJobs.remove(saveJob)
-        saveJob = scope.launch {
-            try {
-                delay(autoSaveInterval)
-            } catch (ignore: CancellationException) {
-                return@launch
-            }
-            if (changed) savingJobs[saveJob]?.start()
-            if (saveAndUnload) unload()
-        }.apply {
-            savingJobs[this] = scope.launch(start = CoroutineStart.LAZY) {save()}
-            this.invokeOnCompletion { savingJobs.remove(this) }
-        }
-    }
-
-    init {
-        val file = File(filePath)
-        jsonObj = if (file.exists())
-            JSON.parseObject(file.readText().ifBlank { "{}" })
-        else
-            JSONObject(HashMap())
-        if (saveAndUnload) launchSaveJob()
-    }
-
-    /**
-     * 保存前需将有变动的数据使用[setObj]存入[jsonObj]
-     *
-     * 会自动创建不存在的文件和文件夹
-     */
-    fun save() {
-        val file = File(filePath)
-        if (!file.exists()) {
-            if (!file.parentFile.exists()) file.parentFile.mkdirs()
-            file.createNewFile()
-        }
-        file.writeText(JSON.toJSONString(jsonObj, true))
-        changed = false
-    }
-
-    /**
-     * 移除数据对象
-     *
-     * 确保卸载时数据已使用[save]存入磁盘
-     */
-    abstract fun unload()
-
-    /**
-     * 是否已有记录文件存在
-     */
-    fun exists() = File(filePath).exists()
-
-    /**
-     * 读取数据,若不存在则返回`null`
-     *
-     * 自动更新访问时间
-     */
-    inline fun <reified T> getObj(key: String): T? {
-        return if (jsonObj.containsKey(key)) {
-            val tr = object : TypeReference<T>() {}
-            jsonObj.getObject(key, tr)
-        } else null
-    }
-    inline fun <reified T: Map<*, *>> getObj(): T {
-        val tr = object : TypeReference<T>() {}
-        return jsonObj.toJavaObject(tr)
-    }
-
-    /**
-     * 写入数据,不触发保存文件
-     *
-     * 此方法应在[withAccessing]或[withLockedAccessing]代码块中使用
-     *
-     * 自动设置被更改状态
-     *
-     * @see withAccessing
-     * @see withLockedAccessing
-     */
-    fun <T> setObj(key: String, value: T?) {
-        jsonObj[key] = value
-        changed = true
-    }
-    fun <T: Map<out String, *>> setObj(value: T?) {
-        jsonObj.fluentClear().fluentPutAll(value)
-        changed = true
-    }
-
-    companion object {
-        val savingJobs = HashMap<Job, Job>()
-        val scope = CoroutineScope(Dispatchers.IO)
-    }
-
-}
-
-/**
- * 用于记录[DataObj]使用状况,实现自动保存
- *
- * 此方法不对数据加锁
- */
-inline fun <T> withAccessing(vararg obj: DataObj, action: ()->T): T {
-    obj.forEach { it.access() }
-    try {
-        return action()
-    } finally {
-        obj.forEach { it.exit() }
-    }
-}
-
-/**
- * 用于记录[DataObj]使用状况,实现自动保存
- *
- * 此方法带锁,使用时需避免锁死
- */
-suspend inline fun <T> withLockedAccessing(vararg obj: DataObj, owner: Any? = null, action: ()->T): T {
-    obj.forEach { it.lock(owner); it.access() }
-    try {
-        return action()
-    } finally {
-        obj.forEach { it.exit(); it.unlock(owner) }
-    }
-}

+ 101 - 0
src/main/kotlin/bot_luo_core/data/DataObject.kt

@@ -0,0 +1,101 @@
+package bot_luo_core.data
+
+import com.alibaba.fastjson.JSON
+import com.alibaba.fastjson.JSONObject
+import com.alibaba.fastjson.JSONPObject
+import com.alibaba.fastjson.TypeReference
+import kotlinx.coroutines.sync.Mutex
+import java.io.File
+
+/**
+ * # 数据对象类
+ *
+ * 根对象为一个[JSONObject]
+ *
+ * 继承[Mutex]
+ *
+ * 建议使用[withAccessing]进行读访问,使用[withLockedAccessing]进行读写访问
+ */
+abstract class DataObject(
+    /**
+     * JSON数据文件路径
+     */
+    filePath: String,
+    /**
+     * 自动保存间隔时间(ms)
+     *
+     * 在访问空闲超过间隔后自动保存
+     */
+    autoSaveInterval: Long,
+    /**
+     * 是否自动卸载
+     *
+     * 为`true`时将把读取数据算作访问,且在自动保存后调用[unload]方法
+     */
+    saveAndUnload: Boolean
+): Data(filePath, autoSaveInterval, saveAndUnload) {
+
+    /**
+     * 使用[getObj]和[setObj]进行访问
+     */
+    val jsonObj: JSONObject
+
+    init {
+        val file = File(filePath)
+        jsonObj = if (file.exists())
+            JSON.parseObject(file.readText().ifBlank { "{}" })
+        else
+            JSONObject()
+        if (saveAndUnload) launchSaveJob()
+    }
+
+    /**
+     * 保存前需将有变动的数据使用[setObj]存入[jsonObj]
+     *
+     * 会自动创建不存在的文件和文件夹
+     */
+    override fun save() {
+        val file = File(filePath)
+        if (!file.exists()) {
+            if (!file.parentFile.exists()) file.parentFile.mkdirs()
+            file.createNewFile()
+        }
+        file.writeText(JSON.toJSONString(jsonObj, true))
+        changed = false
+    }
+
+    /**
+     * 读取数据,若不存在则返回`null`
+     *
+     * 自动更新访问时间
+     */
+    inline fun <reified T> getObj(key: String): T? {
+        return if (jsonObj.containsKey(key)) {
+            val tr = object : TypeReference<T>() {}
+            jsonObj.getObject(key, tr)
+        } else null
+    }
+    inline fun <reified T: Map<*, *>> getObj(): T {
+        val tr = object : TypeReference<T>() {}
+        return jsonObj.toJavaObject(tr)
+    }
+
+    /**
+     * 写入数据,不触发保存文件
+     *
+     * 此方法应在[withAccessing]或[withLockedAccessing]代码块中使用
+     *
+     * 自动设置被更改状态
+     *
+     * @see withAccessing
+     * @see withLockedAccessing
+     */
+    fun <T> setObj(key: String, value: T?) {
+        jsonObj[key] = value
+        changed = true
+    }
+    fun <T: Map<out String, *>> setObj(value: T?) {
+        jsonObj.fluentClear().fluentPutAll(value)
+        changed = true
+    }
+}

+ 1 - 1
src/main/kotlin/bot_luo_core/data/GroupTags.kt

@@ -6,7 +6,7 @@ import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 
-object GroupTags: DataObj("data/group_tags.json", 10000L, false) {
+object GroupTags: DataObject("data/group_tags.json", 10000L, false) {
     override fun unload() {}
 
     val BUILTIN_GROUP_TAGS = listOf("#this", "#none")

+ 1 - 1
src/main/kotlin/bot_luo_core/data/UserTags.kt

@@ -6,7 +6,7 @@ import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 
-object UserTags: DataObj("data/user_tags.json", 10000L, false)  {
+object UserTags: DataObject("data/user_tags.json", 10000L, false)  {
     override fun unload() {}
 
     val BUILTIN_USER_TAGS = listOf("#this", "#any")