Modding Config(Lib) - quản lí config ez

  • Chào bạn, hãy đăng ký hoặc đăng nhập để tham gia cùng bọn mình và sử dụng được đầy đủ chức năng của diễn đàn :).

anhcraft

DEVELOPER
DONATOR
THÀNH VIÊN
18/9/16
2,326
2,918
940
.
anhcraft.dev
Do nhu cầu bản thân nên mình có tạo ra lib để quản lí config plugin, hi vọng share sẽ giúp ích mn.

Vậy vào vấn đề: Plugin bạn có config khá lớn (cỡ trên 20 settings), nếu theo cách truyền thống dùng Bukkit API thì sẽ phải thực hiện các thao tác như:
Java:
config.set("option", value);
config.get("option");
Nhìn có vẻ đơn giản nhưng với config lớn thì sẽ rất khó quản lí (load, save, transform, vvv)

=> Vậy lib này sẽ giúp bạn thao tác với config bằng Java Object. Thông qua annotation gắn ở các biến, lib này sẽ giúp parse hoặc save vào config của Bukkit.

Lợi ích:
- Gọn nhẹ quy trình quản lí config
- Giúp bạn tạo ra các object cho API và dùng lib này để load config
- Bạn có thể tạo default config trực tiếp bằng code
- Cho phép save/load config giữa Bukkit và Bungeecord
- Bạn có thể thực hiện biến đổi object trong config (Transformation)
- Kiểm config (Validation)
- Hỗ trợ List, Array, Map, Enum và một vài type của Bukkit như Location, Vector, etc


HƯỠNG DẪN CƠ BẢN

Ví dụ đơn giản mình có plugin /bc với config như sau:
Java:
public class PluginConfig {
    public boolean activated; // kích hoạt
    public String message; // nội dung thông báo
}
Giờ mình sẽ thực hiện chuyển nó thành một class Config:
Java:
@Configurable // bắt buộc phải có
public class PluginConfig {

    @Setting
    public boolean activated; // kích hoạt

    @Setting
    public String message; // nội dung thông báo

}

Save vào config

Giả sử bạn có một object của Class này và muốn lưu nó vào config chẳng hạn:
Java:
PluginConfig c = new PluginConfig();
c.message = "Hello World";
c.activated = true;

// Đầu tiên chúng ta sẽ quét class này. Bạn có thể yên tâm gọi hàm này nhiều lần vì kết quả lần sau đã được cache rồi nhé
ConfigSchema schema = SchemaScanner.scanConfig(PluginConfig.class);

// Tiếp theo tạo một serializier giúp chuyển Java Object thành Config
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();

// Thực hiện transformation
ConfigSection section = serializer.transformConfig(schema, c);

// In thành chuỗi
System.out.println(section.stringify());
Kết quả:
YAML:
activated: true
message: Hello World
Ở trên bạn sẽ thấy ConfigSection. Đây là class đại diện cho một mục trong config (có thể là mục root). Class này hỗ trợ cross-platform cho cả Bukkit, Bungeecord, và có thể thêm trong tương lai. Bạn có thể thực hiện một số thao tác cơ bản như bạn đã từng dùng Bukkit Configuration API
Java:
// Set
section.set("number", 10);
section.set("str", "lorem ipsum");
section.set("list", Arrays.asList("Orange", "Lime", "Kiwi"));
section.set("array", new double[]{-3, 5, 0.75});

// Lấy các key trong config
// Nếu set là true sẽ quét hết key của các mục con
System.out.println(String.join(", ", section.getKeys(false)));

// Get
System.out.println(section.get("number").asInt());
System.out.println(section.get("str").asString());
System.out.println(section.get("list").asList());
System.out.println(Array.get(section.get("array").getObject(), 0));
System.out.println(section.get("number").asString());

// In thành chuỗi
System.out.println(section.stringify());
Kết quả:
Mã:
activated, message, number, str, list, array
10
lorem ipsum
[Orange, Lime, Kiwi]
-3.0
null

Load từ config

Vậy làm sao để load từ config? Hãy xem vd sau đây:
Java:
//YamlConfiguration bukkitConfig = YamlConfiguration.loadConfiguration(file);
YamlConfiguration bukkitConfig = new YamlConfiguration();
bukkitConfig.set("message", "Event nap the cuc soc 20k 20 xu");
bukkitConfig.set("activated", true);

// Chúng ta sẽ wrap Configuration của Bukkit bằng ConfigSection
ConfigSection config = new YamlConfigSection(bukkitConfig);

// Quét class (ví đã quét ở vd trên rồi nên kết quả sẽ lấy ngay từ cache)
ConfigSchema schema = SchemaScanner.scanConfig(PluginConfig.class);

// Tiếp đó chúng ta sẽ tạo Deserializer giúp đọc từ Config và chuyển sang Java Object
ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();

// Thực hiện đọc config
PluginConfig c = deserializer.transformConfig(schema, config);

// Test kết quả nào!
System.out.printf("%s; %b", c.message, c.activated);
Kết quả:
Mã:
Event nap the cuc soc 20k 20 xu; true
OK vậy là bạn đã có thể bắt đầu sử dụng lib dc rùi đó.
Một lưu ý là tên settings trong config sẽ lấy từ tên biến nhé, giả sử bạn muốn đặt tên thì dùng annotation Path:
Mình sẽ thêm annotation này vào trên biến activated
Java:
@Setting
@Path("enabled")
public boolean activated; // kích hoạt
Quay lại ví dụ save config, khi chạy chúng ta sẽ được config như sau
YAML:
enabled: true
message: Hello World
Bạn có thể dùng annotation Path này để sắp xếp config, bằng cách dùng dấu chấm để tạo thành đường dẫn nhiều cấp:
Ví dụ và kết quả:
Java:
@Setting
@Path("a.b.c.d.e.f.g.enabled")
public boolean activated; // kích hoạt
YAML:
a:
  b:
    c:
      d:
        e:
          f:
            g:
              enabled: true
message: Hello World



HƯỠNG DẪN TRUNG CẤP

Bây giờ chúng ta sẽ xét một vd khác rối rắm hơn.
Mình nghĩ là thao tác vs các type cơ bản như int, byte, String,vvv là ok rồi, giờ ta sẽ sử dụng List, Map, Enum kết hợp object Config đan xen nhau.
Giả sử mình có plugin Menu và config như sau:
Java:
@Configurable
public class Menu {
    @Setting
    public String title;

    @Setting
    public Map<Integer, Slot> slots;
}

@Configurable
public class Slot {
    @Setting
    @Path("item")
    public Material material;

    @Setting
    @Path("on-click.commands")
    public List<String> commands;

    public static Slot build(Material material, String... commands) {
        Slot slot = new Slot();
        slot.material = material;
        slot.commands = Arrays.asList(commands);
        return slot;
    }
}
Bây giờ chúng ta sẽ thử tạo một object, lưu nó vào config, sau đó thử parse để kiểm tra:
Java:
Menu menu = new Menu();
menu.title = "Example menu";
menu.slots = new HashMap<>();
menu.slots.put(0, Slot.build(Material.DIAMOND));
menu.slots.put(8, Slot.build(Material.EMERALD, "give %player% emerald 1"));

ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
System.out.println(section.stringify());

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
Menu c = deserializer.transformConfig(schema, section);
System.out.println(c.title);
System.out.println(c.slots.entrySet().stream()
        .map(e ->
                String.format(
                        "%d: %s %s",
                        e.getKey(), e.getValue().material, String.join(" | ", e.getValue().commands)
                )
        )
        .collect(Collectors.joining("\n")));
Kết quả:
YAML:
title: Example menu
slots:
  '0':
    item: DIAMOND
    on-click:
      commands: []
  '8':
    item: EMERALD
    on-click:
      commands:
      - give %player% emerald 1

Example menu
0: DIAMOND
8: EMERALD give %player% emerald 1
Quy tắc khi load/save như sau:
- Map <=> ConfigSection
- List, Array <=> List (config)
- Enum <=> String (config)

Kiểm tra config

Lib cho phép bạn dùng annotation Validation để thực hiện kiểm tra null-check với mọi Object và empty-check với List, Array, String và ConfigSection
Ví dụ mình sẽ thêm annotation này vào option là Material:
Java:
@Setting
@Path("item")
@Validation(notNull = true)
public Material material;
Vẫn vd trên nhưng mình sẽ xóa settings này khỏi config
Java:
ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
section.del("slots.0.item"); // <= XÓA

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
deserializer.transformConfig(schema, section);
Lúc này khi deserializier thực hiện chuyển config sang Java Object sẽ báo lỗi:
Mã:
dev.anhcraft.config.exceptions.InvalidValueException: Value must not be null (key = item)
Tương tự với empty-check.

Default value

Trong một vài trường hợp bạn muốn giữ cho object của biến nào đó luôn non-null, tức là ở đây bạn cần tạo ra một giá trị mặc định (default value).
Chẳng hạn từ vd trên, bạn muốn material phải luôn non-null, còn nếu trong config không có sẽ lấy Material.AIR ta vẫn sẽ dùng annotation Validation nhưng ở chế độ im lặng (slient mode), khi đó không có exception nào xảy ra và thằng deserializier đơn giản chỉ skip.
Java:
@Setting
@Path("item")
@Validation(notNull = true, silent = true)
public Material material = Material.AIR;
Java:
ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
section.del("slots.0.item"); // <= XÓA

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
menu = deserializer.transformConfig(schema, section);
System.out.println(menu.slots.get(0).material); // AIR
System.out.println(menu.slots.get(8).material); // EMERALD


HƯỠNG DẪN CAO CẤP

Các bn chỉ cần hiểu hai phần trên là có thể dùng lib rồi, ở phần này mình sẽ xét một vài trường hợp

1. Truyền settings vào ConfigSection con
Lấy luôn ví dụ ở trên, ta thấy trong config Menu sẽ chứa nhiều config Slot và các config con này sẽ ở trong Map. Tuy nhiên nếu bạn chỉ có object Slot thì sao? Bạn sẽ ko biết dc nó ở vị trí nào (do key của Map chính là vị trí rồi nên trong config Slot ko cần ghi lại nữa)
Khi đó, lib cung cấp cho bạn một middleware có sẵn cho việc này đó là EntryKeyInjector.
Đầu tiên thêm settings vị trí vào Slot:
Java:
@Setting
@Path("slot")
public int position;
Chúng ta sẽ thử tạo ra một config, sau đó kết hợp middleware trên để truyền vị trí của Slot vào.
Java:
YamlConfigSection conf = new YamlConfigSection();
conf.set("title", "Example Menu");
conf.set("slots.3.item", "EMERALD");
conf.set("slots.5.item", "CAKE");

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();

// Thiết lập middleware
deserializer.setMiddleware(new EntryKeyInjector(new Function<EntrySchema, String>() {
    @Override
    public String apply(EntrySchema entrySchema) {
        // Middleware này sẽ inject {"slot": <key>} vào trong mục con của "slots"
        return entrySchema.getKey().equals("slots") ? "slot" : null;
    }
}));

Menu menu = deserializer.transformConfig(SchemaScanner.scanConfig(Menu.class), conf);
System.out.printf("%d %s\n", menu.slots.get(3).position, menu.slots.get(3).material);
System.out.printf("%d %s\n", menu.slots.get(5).position, menu.slots.get(5).material);
Kết quả:
YAML:
3 EMERALD
5 CAKE

2. Hỗ trợ các class không sử dụng lib này
Chẳng hạn các class trong Bukkit API sẽ ko thể load/save ngay được, bạn có thể mở rộng bằng cách dùng TypeAdapter.
Ví dụ mình sẽ dùng nó để hỗ trợ class Player và Enchantment.
Đầu tiên tạo ra Adapter:
Java:
public class EnchantmentAdapter implements TypeAdapter<Enchantment> {
    @Override
    public @Nullable SimpleForm simplify(@NotNull ConfigSerializer serializer, @NotNull Type sourceType, @NotNull Enchantment value) throws Exception {
        // Chúng ta sẽ lưu enchantment với tên của nó (dạng String)
        return SimpleForm.of(value.getName());
    }

    @Override
    public @Nullable Enchantment complexify(@NotNull ConfigDeserializer deserializer, @NotNull Type targetType, @NotNull SimpleForm value) throws Exception {
        if(value.isString()) {
            // Nếu là dạng String, chúng ta sẽ get Enchantment tương ứng
            return Enchantment.getByName(value.asString().toUpperCase());
        }
        return null;
    }
}
Java:
public class PlayerAdapter implements TypeAdapter<Player> {
    @Override
    public @Nullable SimpleForm simplify(@NotNull ConfigSerializer serializer, @NotNull Type sourceType, @NotNull Player value) throws Exception {
        // Để lưu player ta có thể dùng name hoặc UUID, tuy nhiên phải dùng UUID để tránh dup account
        // Do lib đã có sẵn hỗ trợ cho UUID, chúng ta sẽ gọi transformer của UUID mà ko cần code lại
        return serializer.transform(UUID.class, value.getUniqueId());
    }

    @Nullable
    @Override
    public Player complexify(@NotNull ConfigDeserializer deserializer, @NotNull Type targetType, @NotNull SimpleForm value) throws Exception {
        // Gọi UUID transformer de get Player
        return Bukkit.getPlayer((UUID) deserializer.transform(UUID.class, value));
    }
}
Sau đó register nó vào Serializer hoặc Deserializer:
Java:
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
serializer.registerTypeAdapter(Enchantment.class, new EnchantmentAdapter());
serializer.registerTypeAdapter(Player.class, new PlayerAdapter());

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
deserializer.registerTypeAdapter(Enchantment.class, new EnchantmentAdapter());
deserializer.registerTypeAdapter(Player.class, new PlayerAdapter());

3. Virtual annotation
Quay lại vd ở mục 1: Truyền settings vào ConfigSection con
Lúc này trong class có biến slot, làm sao để xóa biến này khi save vào config? Lib có annotation Virtual giúp ngăn lưu một biến nào đó vào config.
Cách dùng cực ez:
Java:
@Setting
@Path("slot")
@Virtual
public int position;
Bây giờ thử save config:
Trước khi có annotation:
YAML:
title: Example Menu
slots:
  '3':
    slot: 3
    item: EMERALD
  '5':
    slot: 5
    item: CAKE
Sau khi có:
YAML:
title: Example Menu
slots:
  '3':
    item: EMERALD
  '5':
    item: CAKE

4. Consistent annotation
Annotation này gắn vào biến nào thì biến đó sẽ ở chế độ read-only, serializier sẽ k thể thay đổi giá trị biến dc.

Cài đặt lib
Từ source: https://github.com/anhcraft/config
Hoặc Maven:
XML:
    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

<dependency>
        <groupId>com.github.anhcraft</groupId>
        <artifactId>config</artifactId>
        <version>1.0.2</version>
    </dependency>

------------------------------------------------------------------------------------------


Hưỡng dẫn tới đây thôi <(")
Hi vọng lib này sẽ có ích cho ae coder
All contributions are welcome.
p/s: og nào code plugin lớn lớn mới cần thôi nhé, chứ plugin nhỏ chắc k cần đâu :D
 

BlueNatural

THÀNH VIÊN
15/12/19
470
85
310
Việt Nam
Do nhu cầu bản thân nên mình có tạo ra lib để quản lí config plugin, hi vọng share sẽ giúp ích mn.

Vậy vào vấn đề: Plugin bạn có config khá lớn (cỡ trên 20 settings), nếu theo cách truyền thống dùng Bukkit API thì sẽ phải thực hiện các thao tác như:
Java:
config.set("option", value);
config.get("option");
Nhìn có vẻ đơn giản nhưng với config lớn thì sẽ rất khó quản lí (load, save, transform, vvv)

=> Vậy lib này sẽ giúp bạn thao tác với config bằng Java Object. Thông qua annotation gắn ở các biến, lib này sẽ giúp parse hoặc save vào config của Bukkit.

Lợi ích:
- Gọn nhẹ quy trình quản lí config
- Giúp bạn tạo ra các object cho API và dùng lib này để load config
- Bạn có thể tạo default config trực tiếp bằng code
- Cho phép save/load config giữa Bukkit và Bungeecord
- Bạn có thể thực hiện biến đổi object trong config (Transformation)
- Kiểm config (Validation)
- Hỗ trợ List, Array, Map, Enum và một vài type của Bukkit như Location, Vector, etc


HƯỠNG DẪN CƠ BẢN

Ví dụ đơn giản mình có plugin /bc với config như sau:
Java:
public class PluginConfig {
    public boolean activated; // kích hoạt
    public String message; // nội dung thông báo
}
Giờ mình sẽ thực hiện chuyển nó thành một class Config:
Java:
@Configurable // bắt buộc phải có
public class PluginConfig {

    @Setting
    public boolean activated; // kích hoạt

    @Setting
    public String message; // nội dung thông báo

}

Save vào config

Giả sử bạn có một object của Class này và muốn lưu nó vào config chẳng hạn:
Java:
PluginConfig c = new PluginConfig();
c.message = "Hello World";
c.activated = true;

// Đầu tiên chúng ta sẽ quét class này. Bạn có thể yên tâm gọi hàm này nhiều lần vì kết quả lần sau đã được cache rồi nhé
ConfigSchema schema = SchemaScanner.scanConfig(PluginConfig.class);

// Tiếp theo tạo một serializier giúp chuyển Java Object thành Config
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();

// Thực hiện transformation
ConfigSection section = serializer.transformConfig(schema, c);

// In thành chuỗi
System.out.println(section.stringify());
Kết quả:
YAML:
activated: true
message: Hello World
Ở trên bạn sẽ thấy ConfigSection. Đây là class đại diện cho một mục trong config (có thể là mục root). Class này hỗ trợ cross-platform cho cả Bukkit, Bungeecord, và có thể thêm trong tương lai. Bạn có thể thực hiện một số thao tác cơ bản như bạn đã từng dùng Bukkit Configuration API
Java:
// Set
section.set("number", 10);
section.set("str", "lorem ipsum");
section.set("list", Arrays.asList("Orange", "Lime", "Kiwi"));
section.set("array", new double[]{-3, 5, 0.75});

// Lấy các key trong config
// Nếu set là true sẽ quét hết key của các mục con
System.out.println(String.join(", ", section.getKeys(false)));

// Get
System.out.println(section.get("number").asInt());
System.out.println(section.get("str").asString());
System.out.println(section.get("list").asList());
System.out.println(Array.get(section.get("array").getObject(), 0));
System.out.println(section.get("number").asString());

// In thành chuỗi
System.out.println(section.stringify());
Kết quả:
Mã:
activated, message, number, str, list, array
10
lorem ipsum
[Orange, Lime, Kiwi]
-3.0
null

Load từ config

Vậy làm sao để load từ config? Hãy xem vd sau đây:
Java:
//YamlConfiguration bukkitConfig = YamlConfiguration.loadConfiguration(file);
YamlConfiguration bukkitConfig = new YamlConfiguration();
bukkitConfig.set("message", "Event nap the cuc soc 20k 20 xu");
bukkitConfig.set("activated", true);

// Chúng ta sẽ wrap Configuration của Bukkit bằng ConfigSection
ConfigSection config = new YamlConfigSection(bukkitConfig);

// Quét class (ví đã quét ở vd trên rồi nên kết quả sẽ lấy ngay từ cache)
ConfigSchema schema = SchemaScanner.scanConfig(PluginConfig.class);

// Tiếp đó chúng ta sẽ tạo Deserializer giúp đọc từ Config và chuyển sang Java Object
ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();

// Thực hiện đọc config
PluginConfig c = deserializer.transformConfig(schema, config);

// Test kết quả nào!
System.out.printf("%s; %b", c.message, c.activated);
Kết quả:
Mã:
Event nap the cuc soc 20k 20 xu; true
OK vậy là bạn đã có thể bắt đầu sử dụng lib dc rùi đó.
Một lưu ý là tên settings trong config sẽ lấy từ tên biến nhé, giả sử bạn muốn đặt tên thì dùng annotation Path:
Mình sẽ thêm annotation này vào trên biến activated
Java:
@Setting
@Path("enabled")
public boolean activated; // kích hoạt
Quay lại ví dụ save config, khi chạy chúng ta sẽ được config như sau
YAML:
enabled: true
message: Hello World
Bạn có thể dùng annotation Path này để sắp xếp config, bằng cách dùng dấu chấm để tạo thành đường dẫn nhiều cấp:
Ví dụ và kết quả:
Java:
@Setting
@Path("a.b.c.d.e.f.g.enabled")
public boolean activated; // kích hoạt
YAML:
a:
  b:
    c:
      d:
        e:
          f:
            g:
              enabled: true
message: Hello World



HƯỠNG DẪN TRUNG CẤP

Bây giờ chúng ta sẽ xét một vd khác rối rắm hơn.
Mình nghĩ là thao tác vs các type cơ bản như int, byte, String,vvv là ok rồi, giờ ta sẽ sử dụng List, Map, Enum kết hợp object Config đan xen nhau.
Giả sử mình có plugin Menu và config như sau:
Java:
@Configurable
public class Menu {
    @Setting
    public String title;

    @Setting
    public Map<Integer, Slot> slots;
}

@Configurable
public class Slot {
    @Setting
    @Path("item")
    public Material material;

    @Setting
    @Path("on-click.commands")
    public List<String> commands;

    public static Slot build(Material material, String... commands) {
        Slot slot = new Slot();
        slot.material = material;
        slot.commands = Arrays.asList(commands);
        return slot;
    }
}
Bây giờ chúng ta sẽ thử tạo một object, lưu nó vào config, sau đó thử parse để kiểm tra:
Java:
Menu menu = new Menu();
menu.title = "Example menu";
menu.slots = new HashMap<>();
menu.slots.put(0, Slot.build(Material.DIAMOND));
menu.slots.put(8, Slot.build(Material.EMERALD, "give %player% emerald 1"));

ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
System.out.println(section.stringify());

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
Menu c = deserializer.transformConfig(schema, section);
System.out.println(c.title);
System.out.println(c.slots.entrySet().stream()
        .map(e ->
                String.format(
                        "%d: %s %s",
                        e.getKey(), e.getValue().material, String.join(" | ", e.getValue().commands)
                )
        )
        .collect(Collectors.joining("\n")));
Kết quả:
YAML:
title: Example menu
slots:
  '0':
    item: DIAMOND
    on-click:
      commands: []
  '8':
    item: EMERALD
    on-click:
      commands:
      - give %player% emerald 1

Example menu
0: DIAMOND
8: EMERALD give %player% emerald 1
Quy tắc khi load/save như sau:
- Map <=> ConfigSection
- List, Array <=> List (config)
- Enum <=> String (config)

Kiểm tra config

Lib cho phép bạn dùng annotation Validation để thực hiện kiểm tra null-check với mọi Object và empty-check với List, Array, String và ConfigSection
Ví dụ mình sẽ thêm annotation này vào option là Material:
Java:
@Setting
@Path("item")
@Validation(notNull = true)
public Material material;
Vẫn vd trên nhưng mình sẽ xóa settings này khỏi config
Java:
ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
section.del("slots.0.item"); // <= XÓA

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
deserializer.transformConfig(schema, section);
Lúc này khi deserializier thực hiện chuyển config sang Java Object sẽ báo lỗi:
Mã:
dev.anhcraft.config.exceptions.InvalidValueException: Value must not be null (key = item)
Tương tự với empty-check.

Default value

Trong một vài trường hợp bạn muốn giữ cho object của biến nào đó luôn non-null, tức là ở đây bạn cần tạo ra một giá trị mặc định (default value).
Chẳng hạn từ vd trên, bạn muốn material phải luôn non-null, còn nếu trong config không có sẽ lấy Material.AIR ta vẫn sẽ dùng annotation Validation nhưng ở chế độ im lặng (slient mode), khi đó không có exception nào xảy ra và thằng deserializier đơn giản chỉ skip.
Java:
@Setting
@Path("item")
@Validation(notNull = true, silent = true)
public Material material = Material.AIR;
Java:
ConfigSchema schema = SchemaScanner.scanConfig(Menu.class);
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
ConfigSection section = serializer.transformConfig(schema, menu);
section.del("slots.0.item"); // <= XÓA

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
menu = deserializer.transformConfig(schema, section);
System.out.println(menu.slots.get(0).material); // AIR
System.out.println(menu.slots.get(8).material); // EMERALD


HƯỠNG DẪN CAO CẤP

Các bn chỉ cần hiểu hai phần trên là có thể dùng lib rồi, ở phần này mình sẽ xét một vài trường hợp

1. Truyền settings vào ConfigSection con
Lấy luôn ví dụ ở trên, ta thấy trong config Menu sẽ chứa nhiều config Slot và các config con này sẽ ở trong Map. Tuy nhiên nếu bạn chỉ có object Slot thì sao? Bạn sẽ ko biết dc nó ở vị trí nào (do key của Map chính là vị trí rồi nên trong config Slot ko cần ghi lại nữa)
Khi đó, lib cung cấp cho bạn một middleware có sẵn cho việc này đó là EntryKeyInjector.
Đầu tiên thêm settings vị trí vào Slot:
Java:
@Setting
@Path("slot")
public int position;
Chúng ta sẽ thử tạo ra một config, sau đó kết hợp middleware trên để truyền vị trí của Slot vào.
Java:
YamlConfigSection conf = new YamlConfigSection();
conf.set("title", "Example Menu");
conf.set("slots.3.item", "EMERALD");
conf.set("slots.5.item", "CAKE");

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();

// Thiết lập middleware
deserializer.setMiddleware(new EntryKeyInjector(new Function<EntrySchema, String>() {
    @Override
    public String apply(EntrySchema entrySchema) {
        // Middleware này sẽ inject {"slot": <key>} vào trong mục con của "slots"
        return entrySchema.getKey().equals("slots") ? "slot" : null;
    }
}));

Menu menu = deserializer.transformConfig(SchemaScanner.scanConfig(Menu.class), conf);
System.out.printf("%d %s\n", menu.slots.get(3).position, menu.slots.get(3).material);
System.out.printf("%d %s\n", menu.slots.get(5).position, menu.slots.get(5).material);
Kết quả:
YAML:
3 EMERALD
5 CAKE

2. Hỗ trợ các class không sử dụng lib này
Chẳng hạn các class trong Bukkit API sẽ ko thể load/save ngay được, bạn có thể mở rộng bằng cách dùng TypeAdapter.
Ví dụ mình sẽ dùng nó để hỗ trợ class Player và Enchantment.
Đầu tiên tạo ra Adapter:
Java:
public class EnchantmentAdapter implements TypeAdapter<Enchantment> {
    @Override
    public @Nullable SimpleForm simplify(@NotNull ConfigSerializer serializer, @NotNull Type sourceType, @NotNull Enchantment value) throws Exception {
        // Chúng ta sẽ lưu enchantment với tên của nó (dạng String)
        return SimpleForm.of(value.getName());
    }

    @Override
    public @Nullable Enchantment complexify(@NotNull ConfigDeserializer deserializer, @NotNull Type targetType, @NotNull SimpleForm value) throws Exception {
        if(value.isString()) {
            // Nếu là dạng String, chúng ta sẽ get Enchantment tương ứng
            return Enchantment.getByName(value.asString().toUpperCase());
        }
        return null;
    }
}
Java:
public class PlayerAdapter implements TypeAdapter<Player> {
    @Override
    public @Nullable SimpleForm simplify(@NotNull ConfigSerializer serializer, @NotNull Type sourceType, @NotNull Player value) throws Exception {
        // Để lưu player ta có thể dùng name hoặc UUID, tuy nhiên phải dùng UUID để tránh dup account
        // Do lib đã có sẵn hỗ trợ cho UUID, chúng ta sẽ gọi transformer của UUID mà ko cần code lại
        return serializer.transform(UUID.class, value.getUniqueId());
    }

    @Nullable
    @Override
    public Player complexify(@NotNull ConfigDeserializer deserializer, @NotNull Type targetType, @NotNull SimpleForm value) throws Exception {
        // Gọi UUID transformer de get Player
        return Bukkit.getPlayer((UUID) deserializer.transform(UUID.class, value));
    }
}
Sau đó register nó vào Serializer hoặc Deserializer:
Java:
ConfigSerializer serializer = BukkitConfigProvider.YAML.createSerializer();
serializer.registerTypeAdapter(Enchantment.class, new EnchantmentAdapter());
serializer.registerTypeAdapter(Player.class, new PlayerAdapter());

ConfigDeserializer deserializer = BukkitConfigProvider.YAML.createDeserializer();
deserializer.registerTypeAdapter(Enchantment.class, new EnchantmentAdapter());
deserializer.registerTypeAdapter(Player.class, new PlayerAdapter());

3. Virtual annotation
Quay lại vd ở mục 1: Truyền settings vào ConfigSection con
Lúc này trong class có biến slot, làm sao để xóa biến này khi save vào config? Lib có annotation Virtual giúp ngăn lưu một biến nào đó vào config.
Cách dùng cực ez:
Java:
@Setting
@Path("slot")
@Virtual
public int position;
Bây giờ thử save config:
Trước khi có annotation:
YAML:
title: Example Menu
slots:
  '3':
    slot: 3
    item: EMERALD
  '5':
    slot: 5
    item: CAKE
Sau khi có:
YAML:
title: Example Menu
slots:
  '3':
    item: EMERALD
  '5':
    item: CAKE

4. Consistent annotation
Annotation này gắn vào biến nào thì biến đó sẽ ở chế độ read-only, serializier sẽ k thể thay đổi giá trị biến dc.

Cài đặt lib
Từ source: https://github.com/anhcraft/config
Hoặc Maven:
XML:
    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

<dependency>
        <groupId>com.github.anhcraft</groupId>
        <artifactId>config</artifactId>
        <version>1.0.2</version>
    </dependency>

------------------------------------------------------------------------------------------


Hưỡng dẫn tới đây thôi <(")
Hi vọng lib này sẽ có ích cho ae coder
All contributions are welcome.
p/s: og nào code plugin lớn lớn mới cần thôi nhé, chứ plugin nhỏ chắc k cần đâu :D
Informative quá,cho like cái