Эффективная работа с файлами конфигурации

Discussion in 'Разработка плагинов' started by fromgate, 10/2/16.

  1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
Dismiss Notice
We welcome you on our site. This site is devoted to the Nukkit project and all that is connected with him. Here you can communicate, download plugins, also many other things get acquainted! Register the account right now :3
Thread Status:
Not open for further replies.
  1. fromgate

    fromgate Administrator

    Messages:
    665
    Likes Received:
    186
    Некоторое время назад, я представил плагин Yamler (порт одноименного плагина с платформы Spigot/Bukkit, который позволяет организовать удобную работу с файлами конфигурации.
    Предлагаемый им способ гораздо удобнее стандартных методов как в bukkit (класс YamlConfiguration) так и в nukkit (класс Config).

    Однако у него есть огромный недостаток: для работы с конфигом приходится устанавливать сторонний плагин. А это сами понимаете, не самая интересная идея.

    Поэтому меня "глодала мысль" создать нечто подобное на основе станадртных классов: опробовав удобство Yamler, меня уже неустраивала громоздскость обычных конфигураторов и написание плагинов на их основе просто стало мукой ;)

    В итоге, в процессе написания плагина Regions (я надеюсь, скоро он будет готов), я написал небольшой класс который позволяет работать с конфигурацией способом подобным способам

    Итак вот сам класс - его надо будет просто создать в любом пакете Вашего плагина:
    Code:
    import cn.nukkit.utils.Config;
    
    import java.io.File;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    
    public abstract class YamlConfig {
    
    [pre][code]public boolean save(File f){
        f.getParentFile().mkdirs();
        try {
            if (f.exists()) f.delete();
            f.createNewFile();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        Config cfg = new Config(f, Config.YAML);
    
        for (Field field : this.getClass().getDeclaredFields()) {
            String path = getPath(field);
            try {
                cfg.set(path,field.get(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return false;
            }
        }
        cfg.save();
        return true;
    }
    
    public boolean load(File f){
        f.getParentFile().mkdirs();
        try {
            if (!f.exists()) f.createNewFile();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        Config cfg = new Config(f, Config.YAML);
    
        for (Field field : this.getClass().getDeclaredFields()) {
            String path = getPath(field);
            try {
                if (field.getType()==int.class||field.getType()==Integer.class)
                    field.set(this, cfg.getInt(path, field.getInt(this)));
                else if (field.getType()==boolean.class||field.getType()==Boolean.class)
                    field.set(this,cfg.getBoolean(path,field.getBoolean(this)));
                else if (field.getType() == String.class)
                    field.set(this,cfg.getString(path, (String) field.get(this)));
                else throw new UnsupportedClassVersionError("YamlConfig did not supports this class: "+field.getType().getName());
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
        }
        return false;
    }
    
    private String getPath (Field field){
        String path = null;
        if (field.isAnnotationPresent(Path.class)) {
            Path pathDefine = field.getAnnotation(Path.class);
            path = pathDefine.value();
        }
        if (path == null||path.isEmpty()) field.getName().replaceAll("_", ".");
    
        if (Modifier.isPrivate(field.getModifiers())) {
            field.setAccessible(true);
        }
        return path;
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Path {
        String value() default "";
    }
    [/pre]
    }[/code]

    Использовать это класс очень просто. Создаем класс, в котором будет храниться конфигурация плагина:
    Code:
    package ru.nukkit.regions.config;
    
    public class RegionsConfig extends YamlConfig {
    
    [pre][code]@Path (value = "claim.max-regions-per-player")
    public int maxRegionPerPlayer=5;
    
    @Path (value = "claim.max-claim-volume")
    public int maxClaimVolume=10000;
    
    @Path (value = "claim.claim-only-existing-regions")
    public boolean claimOnlyExisting;
    [/pre]
    }[/code]

    Полученный класс-конфигуратор, тоже просто в использовании.
    Переменные — это обычные переменные, к которым ожно обращаться или напрямую или (если хочется сделать их private) посредством геттеров/сеттеров.
    Code:
         cfg.load(new File(this.getDataFolder(),"config.yml"));
    cfg.claimOnlyExisting=true;
    cfg.maxClaimVolume=5000;
    cfg.save(new File(this.getDataFolder(),"config.yml"));
    Т.е. фактически нужно создать объект класс-конфигуратора. Выполнить метод load() для загрузки настроек, после чего нужно просто обращаться к полям файла.

    Если же нужно сохранить изменения в конфиге, то надо просто изменить поля в конфигураторе и вызвать метод save.

    Использование этого подхода имеет ряд преимуществ:
    • Параметры конфигурации хранятся непосредственно в переменных, а не в полях коллекции LinkedHashMap. Т.е. если мерить наносекундами, то обращение к такой переменной будет выполняться быстрее.
    • Доступ к переменной, изменение его значения означает обычное обращение к полям объекта. Название полей конфига указывается лишь в виде аннотации (@Path (value="xxx.xxx)
    • Описать класс наследующий YamlConfig гораздо проще и быстрее чем традиционная работа со стандартным конфигуратором.
    На текущий момент времени конфигуратор поддерживает лишь типы int, boolean, String - как самые распространенные. Но в процессе расширения функционала, я размещу его на гитхабе для свободного использования.
     
    Last edited: 10/2/16
    M4Gn1T likes this.
  2. fromgate

    fromgate Administrator

    Messages:
    665
    Likes Received:
    186
    В общем повозившись немного вчера, я подумал, что такой класс вообще было бы неплохо включить в Nukkit.
    Сделал пулл-реквест с предложением разработчиков. Посмотрим, одобрят или нет ;)
     
    M4Gn1T likes this.
  3. fromgate

    fromgate Administrator

    Messages:
    665
    Likes Received:
    186
    Пулл-реквест вроде принят.
    Т.е. для последней версии Nukkit отдельные классы будут не нужны. Эту тему закрою, чуть позже опишу как работать с конфигами "по-новому" ;)
     
Thread Status:
Not open for further replies.

Share This Page