Skip to content

Latest commit

 

History

History
251 lines (194 loc) · 7.31 KB

File metadata and controls

251 lines (194 loc) · 7.31 KB

SConfig

前言

这是一个自动配置文件处理器,支持:

  • 多种文件类型的支持:JSON / JSON with Comment / YAML / Properties / TOML / INI / NBT / SNBT
  • 自动重载
  • 自动管理配置项
  • 缺省值配置

有关于 JSON 中的 Array As Root 和 NBT 中根标签的名称 等特性,请参考#特殊格式支持

安全提醒
本类使用了 org.ini4j.ini4j ,其存在的已知高危漏洞 CVE-2022-41404 会导致潜在的拒绝服务攻击风险,请不要在 0.5.0 版本(不含)前加载未经验证的 INI 文件。
该漏洞于 0.5.0 版本改用 SuperMap/ini4j 以临时修复。

初始化

要想使用:

import java.io.File;
import com.github.streackmc.StreackLib.utils.SConf;
import com.github.streackmc.StreackLib.StreackLib;

SConfig conf = new SConfig(File 文件对象, String "文件类型");
// 借助静态类 SConfig.TYPES 获取可用类型

// 你也可以传入原始数据,用作转换文件格式等地方
SConfig conf2 = new SConfig(Map<String, Object> conf.getRawData(), String "文件类型", String "临时文件修饰");

这样就获取了一个SConfig对象。

如果你创建了一个临时配置文件,那么它不会占用磁盘 IO ,也不会产生临时文件,直到首次保存到文件。(惰性初始化)
所以即使大规模创建临时配置文件,也无需担心磁盘负荷。
有关该惰性初始化的特性,请见 SConfig.WRITE_MODE.MEMORY

加载与重载

在对象初始化后,其会被自动重载一次。 你也可以使用自动重载:

// 启用自动重载
conf.setAutoReload(true);
// 获取自动重载状态
Boolen status = conf.isAutoReloading();
// 禁用自动重载
conf.setAutoReload(false);

自动重载被重复启用时会自动忽略且不抛错误

或者手动重载:

conf.reload();

字符集

自 0.6.0 版本起,SConfig 全量支持字符集自定义,默认为 UTF-8 。 需要注意,字符集一经设置即无法修改,除非使用 setCharset() 获得副本。

在此之前,大部分方法都默认字符集是 UTF-8 。

解构

每次重载 SConfig 都会自动全量解构输入数据,并转储树状结构为树状 Map 结构,例如:

{
  "node1": {
    "key1": "Castorice Forever",
    "key2": 26710
  },
  "key3": true
}

↓ 解构为

  • Map<String, Object> #root
    • Map<String, Object> node1 = [...]
      • String key1 = Castorice Forever
      • Int key2 = 26710
    • Boolean key3 = true

获取原始配置文件数据

// 获取配置文件对象
File confFile = conf.getFile();
// 仅内存模式无效

// 获取数据(镜像,不会保存到SConfig中)
Map<String, Object> data = conf.getRawData();

增删查改

作为一个配置管理器,最重要的是增删查改:

// 增/改
conf.put(String "key", <T> "value");
// 删
conf.remove(String "key");
// 查
T v = (T) conf.get(String "key", <T> fallback);
// 查是否为空值
boolean isUnset = conf.isExist(String "key");

无法深入退化与防止

另外,考虑这种情况:当使用嵌套路径,但路径中某个节点并不是嵌套结构的,比如,

{
  "foo": "data"
}

此时访问(尤其是写入) foo.bar 时,很明显无法触达,这时候 SConfig 会退化顶层写入,那么就变成了:

{
  "foo": "data",
  "foo.bar": "data2"
}

为了防止这种情况,自 0.6.0 版本起,SConfig 引入了新 API 来解决此问题:

// 获取是否可触达
Boolean reachable = conf.isReachable("user.score");
// 可触达就意味着不会退化到顶层

// 可触达才写入
conf.isReachable(conf::putDouble, "user.score", 99.5);

// 或者执行你自己的操作
conf.isReachable("user.score", () -> doSomething());

严格类型

Java是一门严格类型的语言,使用刚才示例中的弃用方法是危险的,它会直接返回一个 Object 。如果你是Java高手你可以无视风险、继续使用。 你可以在put()或者get()的括号前面插入类型,比如:

conf.putString(String "key", "value");
conf.putString(String "section.key", "value"); // 分节也是可以的
String v = conf.getString(String "key", "fallback");

目前支持以下类型:

  • string
  • int
  • float
  • long
  • short
  • double
  • boolean/byte
    • 0 == false, 1 == true
  • List
  • LocalDate
  • LocalTime
  • LocalDateTime
  • BigDecimal

链式调用

putXXX()remove()系列方法均支持链式调用:

conf.putString(String "key", "value")
    .putString(String "section.key", "value");
    .remove(String "deprecated");

写入模式

SConfig支持四种写入模式,默认为自动保存模式。 设置为其它值视作手动保存模式

  • 自动保存 SConfig.WRITE_MODE.AUTOSAVE:修改后立即保存
  • 手动保存 SConfig.WRITE_MODE.INERTIA:可以修改,需要手动保存
  • 写保护 SConfig.WRITE_MODE.WRITELOCK:可以修改,但无法保存到文件
  • 只读 SConfig.WRITE_MODE.READONLY:无法修改任何东西
  • 仅内存 SConfig.WRITE_MODE.MEMORY
    • 创建临时配置文件后:修改写入模式并首次访问文件对象时才惰性初始化一个临时文件对象
    • 无论何时:所有涉及文件对象的操作均被拒绝,例如自动重载、立即重载、保存到文件……

特殊格式支持

JSON 的 Array As Root

JSON中存在一类特殊格式:

[
  "conf1": {"foo": "bar"},
  "conf2": {"foo": "bar"},
  "conf3": {"foo": "bar"}
]

这种格式的根是一个数组而非正常树结构。0.4.6版本前,SConfig会视作空文件;该版本及之后,SConfig 会将其等效视作:

{
  "_root_array": [
    "conf1": {"foo": "bar"},
    "conf2": {"foo": "bar"},
    "conf3": {"foo": "bar"}
  ]
}

同时,如果一个被 SConfig 解构后的数据也符合上述格式(根 Map 仅包含一个 key="_root_array", value=[...]),其被保存为 JSON 格式时会自动以根数组的形式保存。

请注意:

  1. 如果外部文件不是根数组但也符合该条件仍然会按根数组模式工作,因此写入时也会破坏文件结构;
  2. 如果更糟,外部文件符合该结构但_root_array的值不是数组可能会引发未预料的其它错误。

Object As Root

诸如NBT格式可能存在另外一种相似的情况,即任何值作为根。 这种写法不标准、不推荐,即使 SConfig 会将其类似地放入 _root_value 中。 **请注意:**NBT格式会将包括数组在内的数据放入 _root_value 中。

RootName

诸如NBT等格式的根数据结构也会有一个 name ,若用JSON表达就是:

{
  "data": "我是数据,这也是个正常的结构"
}

↓ 支持RootName时

"所有数据外面还有一层,而我就是 RootName": "{
  "data": "我是数据,外面被包了一层"
}"

考虑到 RootName 可能为空,尽管 SConfig 支持解析空键名,仍然分离了其处理:

conf.setRootName("name")// 文件格式不支持此特性时静默处理
    .putString("key", "value");// 支持链式调用

String rN = conf.getRootName();
// 只有不支持才会返回 null ,否则一律为字符串。