Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,31 @@ import java.io.File
import java.io.InputStream
import java.io.Reader
import java.text.SimpleDateFormat
import java.util.concurrent.atomic.AtomicInteger

/**
* TabooLib
* taboolib.module.configuration.ConfigFile
* TabooLib taboolib.module.configuration.ConfigFile
*
* @author mac
* @since 2021/11/22 12:49 上午
*/
/**
* 表示一个配置文件,继承自 ConfigSection 并实现 Configuration 接口。
*
* @property file 与此配置关联的文件对象,可为 null
* @property name 配置文件的名称(不包含扩展名)
*
* @param root 根配置对象
*
* @author mac
* @since 2021/11/22 12:49 上午
*/
open class ConfigFile(root: Config) : ConfigSection(root), Configuration {

override var file: File? = null
override var name: String = ""

private val reloadGenerationCounter = AtomicInteger(0)

override val reloadGeneration: Int
get() = reloadGenerationCounter.get()

// 存储重载回调的列表
private val reloadCallback = ArrayList<Runnable>()

Expand Down Expand Up @@ -88,6 +92,7 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration {
warning("File: $file")
throw ex
}
reloadGenerationCounter.incrementAndGet()
reloadCallback.forEach { it.run() }
}

Expand All @@ -105,6 +110,7 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration {
warning("Source: \n$contents")
throw t
}
reloadGenerationCounter.incrementAndGet()
reloadCallback.forEach { it.run() }
}

Expand All @@ -116,6 +122,7 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration {
override fun loadFromReader(reader: Reader) {
clear()
parser().parse(reader, root, ParsingMode.REPLACE)
reloadGenerationCounter.incrementAndGet()
reloadCallback.forEach { it.run() }
}

Expand All @@ -127,6 +134,7 @@ open class ConfigFile(root: Config) : ConfigSection(root), Configuration {
override fun loadFromInputStream(inputStream: InputStream) {
clear()
parser().parse(inputStream, root, ParsingMode.REPLACE)
reloadGenerationCounter.incrementAndGet()
reloadCallback.forEach { it.run() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ interface Configuration : ConfigurationSection {
*/
var file: File?

/**
* 重载代数,用于缓存失效(例如 `ReloadAwareLazy`)。
*
* 默认使用“文件指纹”或“内容哈希”来提供一个会随配置变化而变化的值:
* - 若存在关联文件,则使用 `lastModified XOR length`
* - 否则使用 `saveToString().hashCode()`
*
* 如需更高性能或更精确的控制,可在实现中覆盖为实例级别的计数器/版本号。
*/
val reloadGeneration: Int
get() {
val file = file
if (file != null && file.exists()) {
return (file.lastModified() xor file.length()).hashCode()
}
return saveToString().hashCode()
}

/**
* 保存为字符串
*/
Expand Down Expand Up @@ -322,4 +340,4 @@ interface Configuration : ConfigurationSection {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package taboolib.module.configuration.util

import taboolib.module.configuration.Configuration
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/**
* 一个“按 Key 失效”的懒加载委托。
*
* - 首次访问时执行 [initializer] 并缓存结果
* - 之后每次访问都会计算 [key],当 Key 发生变化时会重新执行 [initializer]
* - 线程安全:使用双重检查 + `synchronized` 保护初始化与更新
*/
class KeyedLazy<T>(private val key: () -> Any?, private val initializer: () -> T) : ReadOnlyProperty<Any?, T> {

private val lock = Any()

@Volatile
private var state: State<T>? = null

override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val currentKey = key()
val current = state
if (current != null && current.key == currentKey) {
return current.value
}

return synchronized(lock) {
val latestKey = key()
val latest = state
if (latest != null && latest.key == latestKey) {
return@synchronized latest.value
}

val newValue = initializer()
state = State(latestKey, newValue)
newValue
}
}

private data class State<T>(val key: Any?, val value: T)
}

/**
* 一个“随配置重载代数失效”的懒加载委托。
*
* - 以 [Configuration.reloadGeneration](或其他可变 Key)作为失效条件
* - 当代数变化时重新执行 [initializer] 并更新缓存
* - 线程安全:使用双重检查 + `synchronized` 保护初始化与更新
*
* `config` 的用法:
* - 传入 [Configuration]:使用其 [Configuration.reloadGeneration]
* - 传入 `File`:使用 `lastModified XOR length` 作为指纹
* - 传入 `() -> Any?`:使用返回值作为指纹(推荐,最灵活)
* - 传入其他对象:使用 `hashCode()` 作为指纹(通常不会随内容变化)
* - 传入 `null`:若 `thisRef` 是 [Configuration],则使用 `thisRef.reloadGeneration`;否则将不会自动失效
*/
class ReloadAwareLazy<T>(private val config: Any? = null, private val initializer: () -> T) : ReadOnlyProperty<Any?, T> {

private val lock = Any()

@Volatile
private var state: State<T>? = null

override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val currentKey = resolveKey(thisRef)
val current = state
if (current != null && current.key == currentKey) {
return current.value
}

return synchronized(lock) {
val latestKey = resolveKey(thisRef)
val latest = state
if (latest != null && latest.key == latestKey) {
return@synchronized latest.value
}

val newValue = initializer()
state = State(latestKey, newValue)
newValue
}
}

private fun resolveKey(thisRef: Any?): Any? {
return when (val c = config) {
is Configuration -> c.reloadGeneration
is java.io.File -> FileFingerprint(c.lastModified(), c.length())
is Function0<*> -> c.invoke()
null -> (thisRef as? Configuration)?.reloadGeneration ?: 0
else -> c.hashCode()
}
}

private data class FileFingerprint(val lastModified: Long, val length: Long)

private data class State<T>(val key: Any?, val value: T)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package taboolib.module.configuration.util

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import taboolib.module.configuration.Configuration
import java.io.File

class LazyTest {

@Test
fun keyedLazyCachesUntilKeyChanges() {
class Holder {
var key: Int = 0
var initCount: Int = 0
val value: Int by KeyedLazy({ key }) { ++initCount }
}

val holder = Holder()
assertEquals(1, holder.value)
assertEquals(1, holder.value)

holder.key = 1
assertEquals(2, holder.value)
assertEquals(2, holder.value)
}

@Test
fun reloadAwareLazyInvalidatesWhenConfigReloads() {
val config = Configuration.loadFromString("a: 1")
var initCount = 0

val value: Int by ReloadAwareLazy(config) { ++initCount }

assertEquals(1, value)
assertEquals(1, value)

config.loadFromString("a: 2")
assertEquals(2, value)
assertEquals(2, value)
}

@Test
fun reloadAwareLazyInvalidatesWhenFileFingerprintChanges(@TempDir tempDir: File) {
val file = File(tempDir, "config.txt").apply { writeText("1") }
var initCount = 0

val value: Int by ReloadAwareLazy(file) { ++initCount }

val firstLength = file.length()
assertEquals(1, value)
assertEquals(1, value)

file.writeText("11")
assertEquals(firstLength + 1, file.length())
assertEquals(2, value)
assertEquals(2, value)
}
}
Loading