refactor(config): 重构 AppPaths 的构造方法, 应对将来 Kotlin 更新中的特性.

先前的方法是利用了初始化与调用的顺序, 来实现的 Supplier 互补(虽然在代码中, 确实存在未初始化调用的情况, 但实际运行的时候, 会先初始化, 再调用 Supplier),
但是未来 Kotlin 的更新中,编译器会把这个操作视为未初始化错误, 所以在这次改动中修复掉这个 bug 操作.
This commit is contained in:
LamGC 2022-08-15 01:38:08 +08:00
parent dce28be9c7
commit 255a02c93c
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
2 changed files with 51 additions and 10 deletions

View File

@ -17,7 +17,8 @@ import java.io.File
import java.net.URL import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.function.Supplier
import kotlin.reflect.KProperty
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
@ -102,9 +103,9 @@ private fun createDefaultRepositoryId(): String {
* 必须提供 `pathSupplier` `fileSupplier` 其中一个, 才能正常提供路径. * 必须提供 `pathSupplier` `fileSupplier` 其中一个, 才能正常提供路径.
*/ */
internal enum class AppPaths( internal enum class AppPaths(
private val pathSupplier: () -> String = { fileSupplier.invoke().canonicalPath }, private val pathSupplier: PathSupplier,
private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer, private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer,
private val fileSupplier: () -> File = { File(pathSupplier()) } private val fileSupplier: FileSupplier,
) { ) {
/** /**
* 数据根目录. * 数据根目录.
@ -113,7 +114,7 @@ internal enum class AppPaths(
* *
* 提示: 结尾不带 `/`. * 提示: 结尾不带 `/`.
*/ */
DATA_ROOT(fileSupplier = { DATA_ROOT(fileSupplier = FileSupplier {
File( File(
System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH) System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH)
?: System.getProperty("user.dir") ?: "." ?: System.getProperty("user.dir") ?: "."
@ -125,7 +126,7 @@ internal enum class AppPaths(
} }
}), }),
CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, { CONFIG_APPLICATION(PathSupplier { "$DATA_ROOT/config.json" }, {
if (!file.exists()) { if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use { file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.appConfigGson.toJson( GsonConst.appConfigGson.toJson(
@ -141,7 +142,7 @@ internal enum class AppPaths(
} }
} }
}), }),
CONFIG_BOT({ "$DATA_ROOT/bot.json" }, { CONFIG_BOT(PathSupplier { "$DATA_ROOT/bot.json" }, {
if (!file.exists()) { if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use { file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson( GsonConst.botConfigGson.toJson(
@ -167,10 +168,25 @@ internal enum class AppPaths(
TEMP({ "$DATA_ROOT/tmp/" }) TEMP({ "$DATA_ROOT/tmp/" })
; ;
val file: File constructor(pathSupplier: PathSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
get() = fileSupplier.invoke() fileSupplier = FileSupplier { File(pathSupplier.path).canonicalFile },
val path: String pathSupplier = pathSupplier,
get() = pathSupplier.invoke() initializer = initializer
)
constructor(fileSupplier: FileSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
fileSupplier = fileSupplier,
pathSupplier = PathSupplier { fileSupplier.file.canonicalPath },
initializer = initializer
)
constructor(pathSupplier: () -> String) : this(
fileSupplier = FileSupplier { File(pathSupplier.invoke()).canonicalFile },
pathSupplier = PathSupplier { pathSupplier.invoke() }
)
val file: File by fileSupplier
val path: String by pathSupplier
private val initialized = AtomicBoolean(false) private val initialized = AtomicBoolean(false)
@ -206,6 +222,20 @@ internal enum class AppPaths(
const val ENV_DATA_PATH = "BOT_DATA_PATH" const val ENV_DATA_PATH = "BOT_DATA_PATH"
} }
private class FileSupplier(private val supplier: Supplier<File>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): File = supplier.get()
val file: File
get() = supplier.get()
}
private class PathSupplier(private val supplier: Supplier<String>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): String = supplier.get()
val path: String
get() = supplier.get()
}
} }
/** /**

View File

@ -21,6 +21,17 @@ import kotlin.test.*
internal class AppPathsTest { internal class AppPathsTest {
@Test
fun `Consistency check`() {
for (path in AppPaths.values()) {
assertEquals(
File(path.path).canonicalPath,
path.file.canonicalPath,
"路径 File 与 Path 不一致: ${path.name}"
)
}
}
@Test @Test
fun `Data root path priority`() { fun `Data root path priority`() {
System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, "fromSystemProperties") System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, "fromSystemProperties")