HOCON-сериализаторы
AbstractMenus оборачивает HOCON-библиотеку Lightbend своими обёртками. Сериализатор - простая фабрика: принимает ConfigNode, возвращает Java-объект.
Каждый сериализатор реализует NodeSerializer<T> - в интерфейсе один метод: deserialize(Class<T> type, ConfigNode node).
Когда нужно регистрировать сериализатор
Заголовок раздела «Когда нужно регистрировать сериализатор»Обычно регистрировать сериализатор руками не приходится. Пять вызовов register(...) на реестрах типов (action, rule, item-property, activator, catalog) принимают NodeSerializer<S> и сами кладут его в общую коллекцию NodeSerializers.
Регистрировать руками нужно тогда, когда у тебя есть кастомный объект-параметр, которым пользуется твоё действие или правило, и его надо уметь читать вложенным в другую структуру через node.getValue(MyType.class).
api.serializers().register(MyType.class, new MyTypeSerializer());Регистрируй из MenuExtension.onEnable(api). К моменту, когда твой onEnable отработает, AbstractMenus ещё не успел загрузить меню - значит, всё, что ты зарегистрировал, попадёт в парсинг.
Дефолтные сериализаторы
Заголовок раздела «Дефолтные сериализаторы»Из коробки в AbstractMenus есть сериализаторы для Java-примитивов и нескольких ходовых типов:
BooleanIntegerLongFloatDoubleStringUUID
Плюс свои типы значений: TypeBool, TypeInt, TypeDouble, TypeString, TypeMaterial, TypeLocation, TypeSlot и т.д.
Пример 1. Десериализация простого объекта
Заголовок раздела «Пример 1. Десериализация простого объекта»public class User { public String name; public int age;}user { name: "Notch" age: 42}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); return user; }}ConfigNode - распарсенная HOCON-структура. Сериализатор читает у node именованные поля и заливает их в целевой тип.
Пример 2. Десериализация вложенных объектов
Заголовок раздела «Пример 2. Десериализация вложенных объектов»Если у поля свой сериализатор - просто зови getValue(SomeType.class), и зарегистрированный сериализатор SomeType отработает сам:
user { name: "Notch" age: 42 friend { name: "Alex" age: 38 }}public class User { public String name; public int age; public User friend;}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); user.friend = node.node("friend").getValue(User.class); return user; }}getValue(User.class) найдёт зарегистрированный UserSerializer и натравит его на вложенную ноду. Главное, чтобы UserSerializer был зарегистрирован до парсинга HOCON, который на него ссылается - иначе getValue кинет исключение.
Пример 3. Десериализация коллекций
Заголовок раздела «Пример 3. Десериализация коллекций»В HOCON есть списки. API умеет их для любого зарегистрированного типа.
user { name: "Notch" age: 42 friends: [ { name: "Petya", age: 34 }, { name: "Alex", age: 38 } ]}public class User { public String name; public int age; public List<User> friends;}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); user.friends = node.node("friends").getList(User.class); return user; }}getList(SomeType.class) работает для любого типа с зарегистрированным сериализатором, в том числе для примитивов. node.getList(String.class) вернёт List<String>.
Шорткаты ConfigNode
Заголовок раздела «Шорткаты ConfigNode»У ConfigNode ожидаемый набор методов чтения:
node.getString() // примитив-строкаnode.getString("default") // примитив-строка со значением по умолчаниюnode.getInt()node.getInt(0)node.getBoolean()node.getDouble()node.getList(String.class)node.isNull()node.isPrimitive()node.isMap()node.isList()
node.node("path") // дочерняя нода по dotted-пути (один или несколько сегментов)node.child("name") // одношаговый поиск ребёнкаnode.childrenList() // List<ConfigNode> для нод-списковnode.childrenMap() // Map<String, ConfigNode> для нод-картnode.hasChildren()node.key() // имя этой ноды в родителеnode.path() // полный dotted-путь от корняnode.parent() // родительский ConfigNode или nullnode.getValue(MyType.class) // запустить зарегистрированный сериализаторnode.getValue(MyType.class, fallback)isNull() - самый дешёвый способ проверить, есть ли опциональное поле. Стандартный паттерн: node.node("optional").isNull() ? defaultValue : node.node("optional").getInt().
С нодами-списками и нодами-картами постоянно используешь childrenList() / childrenMap() - именно так встроенные сериализаторы обходят предметы, действия и биндинги.