Too cool not to share - Easy config

Discussion in 'Resources' started by md_5, Jun 17, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    md_5

    Code:
    sounds: true
    blocks:
    - 1
    - 2
    - 3
    - 4
    - 5
    - 6
    motd: This MOTD was set via awesome config
    my:
      cool:
        thing: 'This will be stored as ''my.cool.thing'''
    
    Well that seems like a simple enough config file doesn't it? But to load it with have to do something along the lines of:
    Code:java
    1. sounds = config.getInt("sounds");
    2. blocks = config.getIntegerList("blocks");
    3. motd = config.getString("motd");
    4. my_cool_thing = config.getString("my.cool.thing");

    It took me nearly a minute to type that pseudo code out.
    Now lets look at my config class:
    Code:java
    1. import java.util.Arrays;
    2. import java.util.List;
    3. import org.bukkit.configuration.file.FileConfiguration;
    4. import org.bukkit.plugin.Plugin;
    5.  
    6. public class Config {
    7.  
    8. public static boolean sounds = true;
    9. public static List blocks = Arrays.asList(new Integer[]{1, 2, 3, 4, 5, 6});
    10. public static String motd = "This MOTD was set via awesome config";
    11. public static String my_cool_thing = "This will be stored as 'my.cool.thing: data'";
    12. public static transient String cache = "This value will not be stored";
    13. }
    14.  

    Well there it is, the default options for my config class. But I still need to use that code to override the value I have here. Whats more if the user has deleted the value I get something dodgy like:
    Code:java
    1. cache = null

    Not really something I want. So without further ado I present the automagic config loader.
    Code:java
    1.  
    2. import java.lang.reflect.Field;
    3. import java.lang.reflect.Modifier;
    4. import java.util.Arrays;
    5. import java.util.List;
    6. import org.bukkit.configuration.file.FileConfiguration;
    7. import org.bukkit.plugin.Plugin;
    8.  
    9. public class Config {
    10.  
    11. public static boolean sounds = true;
    12. public static List blocks = Arrays.asList(new Integer[]{1, 2, 3, 4, 5, 6});
    13. public static String motd = "This MOTD was set via awesome config";
    14. public static String my_cool_thing = "This will be stored as 'my.cool.thing: data'";
    15. public static transient String cache = "This value will not be stored";
    16.  
    17. public static void load(Plugin plugin) {
    18. FileConfiguration conf = plugin.getConfig();
    19. for (Field field : Config.class.getDeclaredFields()) {
    20. if (Modifier.isStatic(field.getModifiers()) && !Modifier.isTransient(field.getModifiers())) {
    21. String path = field.getName().replaceAll("_", ".");
    22. try {
    23. if (conf.isSet(path)) {
    24. field.set(null, conf.get(path));
    25. } else {
    26. conf.set(path, field.get(null));
    27. }
    28. } catch (IllegalAccessException ex) {
    29. //
    30. }
    31. }
    32. }
    33. plugin.saveConfig();
    34. }
    35. }
    36.  

    Just stick the load(Plugin) method in your config class, add your options as static fields, and you are way. Example usage:
    Code:java
    1. @Override
    2. public void onEnable() {
    3. Config.load(this);
    4. }
    5.  

    This config api is very flexible and only 17 lines long, and creates the default config.yml for you.
    If you want to get showy you can use underscores to separate config sections, or the transient modifier to prevent it from being included in the config, just in case your config class stores other data.

    Enjoy!
     
    Potatoez_, Skyost, devilquak and 7 others like this.
  2. Offline

    LucasEmanuel

    Maybe resources would be the appropriate place for this ;)
     
  3. Offline

    md_5

    Seriously? The first user to reply spots my social experiment...
    Well
    /experiment
     
  4. Offline

    theguynextdoor

    When i first saw this i was like 'da fuq?' But now i've actually read this ... i like it, looks to be a nice little method you have there
     
  5. Offline

    obnoxint

    md_5
    When using this method with primitives and Strings, it is ok. But using this with a lot of collections could be really problematic in terms of memory management. Because of the way the PluginClassLoader works, this method will lead to memory leaks if the fields won't get nulled as soon as the plugin is being disabled.
     
  6. Offline

    md_5

    Would that be because everything is static?
    I must not I don't actually use this method... yet :p
     
  7. Offline

    obnoxint

    Yep. I was bored and just made this post on the matter. Some may find it useful.
     
  8. Offline

    codename_B

    md_5 have you considered the fields not being static? :)

    If we could get this ironed out (and create a save(this) method too) I reckon something like this could happily sit in the Bukkit api.

    Make Config an abstract class...

    Here's my offering

    Code:
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    
    import org.bukkit.configuration.InvalidConfigurationException;
    import org.bukkit.configuration.file.FileConfiguration;
    import org.bukkit.plugin.Plugin;
    /**
     * Inspired by md_5
     * 
     * An awesome super-duper-lazy Config lib!
     * Just extend it, set some (non-static) variables
     * 
     * @author codename_B
     */
    public abstract class Config {
    
        private transient Plugin plugin = null;
        
        /**
         * When using this constructor, remember that
         * config.load(); will be unavailable
         */
        public Config() {
            // don't do anything here
        }
        
        /**
         * This constructor stores an reference to your
         * plugin, so you can be even more lazy!
         */
        public Config(Plugin plugin) {
            this.plugin = plugin;
        }
        
        /**
         * Lazy load
         */
        public void load() {
            if(plugin != null) {
                try {
                    onLoad(plugin);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                new InvalidConfigurationException("Plugin cannot be null!").printStackTrace();
            }
        }
        
        /**
         * Load a specific config
         * @param plugin
         */
        public void load(Plugin plugin) {
            if(plugin != null) {
                try {
                    onLoad(plugin);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                new InvalidConfigurationException("Plugin cannot be null!").printStackTrace();
            }
        }
        
        /**
         * Lazy save
         */
        public void save() {
            if(plugin != null) {
                try {
                    onSave(plugin);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                new InvalidConfigurationException("Plugin cannot be null!").printStackTrace();
            }
        }
        
        /**
         * Save a specific config
         * @param plugin
         */
        public void save(Plugin plugin) {
            if(plugin != null) {
                try {
                    onSave(plugin);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                new InvalidConfigurationException("Plugin cannot be null!").printStackTrace();
            }
        }
        
        /**
         * Internal method - used by both load() and load(Plugin plugin)
         * @param plugin
         * @throws Exception
         */
        private void onLoad(Plugin plugin) throws Exception {
            FileConfiguration conf = plugin.getConfig();
            for(Field field : getClass().getDeclaredFields()) {
                String path = field.getName().replaceAll("_", ".");
                if(doSkip(field)) {
                    // don't touch it
                } else if(conf.isSet(path)) {
                    field.set(this, conf.get(path));
                } else {
                    conf.set(path, field.get(this));
                }
            }
            plugin.saveConfig();
        }
        
        /**
         * Internal method - used by both save() and save(Plugin plugin)
         * @param plugin
         * @throws Exception
         */
        private void onSave(Plugin plugin) throws Exception {
            FileConfiguration conf = plugin.getConfig();
            for(Field field : getClass().getDeclaredFields()) {
                String path = field.getName().replaceAll("_", ".");
                if(doSkip(field)) {
                    // don't touch it
                } else {
                    conf.set(path, field.get(this));
                }
            }
            plugin.saveConfig();
        }
        
        /**
         * A little internal method to save re-using code
         * @param field
         * @return skip
         */
        private boolean doSkip(Field field) {
            return Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers());
        }
        
    }
    
    http://pastie.org/4114135

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 26, 2016
  9. Offline

    md_5

    Thats what I thought all along.
    Normally I use Plugin.getInstance().getConf().FIELD or whatever, but I wanted to make this as a demo for others.
    Thanks, I'll take a look at that, maybe use a static factory design pattern.
     
  10. Offline

    bobacadodl

    Thanks! I was having some trouble with Config loading since I'm new to it. I unintentionally found this and its perfect :D
     
  11. Offline

    Icyene

    I really think this thread deserves to be bumped. Its one of the simplest yet most useful piece of code on Bukkit, and would probably answer alot of peoples frustrations with YAML, if only they saw it. So.. BUMP.
     
  12. Offline

    Icyene

    md_5 Is there a way to add comments to config with this method? You could treat all items with Modifier.VOLATILE as comments, and add them, but how do you add a comment with this?

    Thanks in advance!
     
  13. Offline

    md_5

    I looked at the config api and there is no way to add comments, if I get some free time I will make an entirely new version of Bukkit's config api :D
     
    iKeirNez likes this.
  14. Offline

    Deleted user

    md_5 strikes again!
     
  15. Offline

    theguynextdoor

    Are you a gift from the gods, or the most cruel internet troll? I pray for the first.
     
  16. Offline

    codename_B

    The comments go under "header"
     
  17. Offline

    theguynextdoor

    Yes this is true. But there is the case where people want comments just above the node or whatever.
    For example:
    Code:
    # Settings which affect tribes
    Tribe:
      # This is the minimum amount of people you would like people to have in a tribe
      PreferredMinimumAmount: 4
      # If true then the tribes coords will be shown when the command /t [name] is used
      Show-Coords-in-tribe-info: false
      # Whether you want claimed land to regenerate after an explosion
      Ground-Regen-From-Explosion: false
      # The amount of damage you wish to inflict on a players who break a block in a tribe they are not a resident of
      # min of 0, max of 20
      # 1 being half a heart, 2 being a full heart
      Damage-On-Block-Break: 2
      EnderPearl:
        # The amount of damage you wish to inflict on players who use enderpeals in a tribe they are not a resident of
        Damage: 0
      Spawn:
        # Allows players to deal and recive damage from other players in a tribe spawn chunk
        Allow-PvP: true
        # Allows players to deal and recive damage from mobs in a tribe spawn chunk
        Allow-PvE: true

    Although this can be achieved by making a pre-made config in your project ( and seeing as i have no need to change the settings in game i have no problem with it ) But i am just a fan of doing these things in code. Makes my life easier.

    I imagine a method such as
    config.addDefault("path", value, comment);
    That could work ... i can see it having possible flaws, but it could work.
    So

    Code:
    config.addDefault("path.subpath", true, "This is my comment");
    Would appear as
    Code:
    path:
      # This is my comment
      subpath: true
     
  18. Offline

    codename_B

    Yep, that would be very nice :)

    config.setComment(String key, String comment)
     
  19. Offline

    evilmidget38

    I've got some suggestions for serialization and deserialization, if you're in the mood.
     
  20. Offline

    md_5

    Sure, one thing I will be doing is making it fast and thread safe.
     
  21. Offline

    theguynextdoor

    Yay for fast and thread safe
     
  22. Offline

    md_5

    Fun Fun Fun. I can use snakeyaml for loading but I will need to write my own yaml writer.
     
    codename_B likes this.
  23. Offline

    Icyene

    md_5
    I have an idea for the new config API: ability to store arrays in the JSON format, with [1, 2, 3] instead of -1 -2 -3. It really makes config neater for large arrays :)
     
  24. Offline

    Acrobot

    So, md_5 - any news on the node-commenting ability?
    I really like how this works, however I must include a comment under each node.

    I think that it could like like this from the class:

    Code:
    @Comment(value = "This is a test value")
    public static String testValue = "Test";
    
     
  25. Would it be possible to use this with grouped/sub-config nodes, e.g.

    Code:
    database:
      sqlType: sqlite
      port: 3306
      ...
    I tried to do this but you cannot use any .'s in a variable names, what if you used # for a dot in a the variable name.
     
  26. Offline

    md_5

    Nope since it means me writing the yaml dumper myself, something I haven't done. I did have an old 'Annotated Config' which was basically ripped from Essentials but I wouldn't use it if I were you.
     
  27. Offline

    Icyene

    Care to share? :p
     
  28. Offline

    md_5

    Its on my github under 'AnnotatedConfig'
     
    Icyene likes this.
  29. Offline

    Icyene

    Wish granted!

    xiaomao and I had to do something among this lines today. Our solution: https://gist.github.com/3942326

    Example use:

    Code:
    @Comment(_="Itz a random String!")
    public String A__Random_String = "Haiz";
    @Comment(_="Itz a random int!")
    public int A__Random_Integer = 5;
    
    You can also specify WHERE the comment should appear:

    Code:
    @Comment(_ = "Iz teh inline!", location = Comment.CommentLocation.INLINE)
    public int String_Inlined = "Inlined!";
    @Comment(_ = "Iz teh top!", location = Comment.CommentLocation.TOP)
    public int String_Top= "Top!";
    @Comment(_ = "Iz teh bottom!", location = Comment.CommentLocation.BOTTOM)
    public int String_Bottom= "Bottom!";
    
    Lastly, it has a @LimitInteger annotation, which allows the parser to correct erroneous input. For example, if you have a variable which can ONLY be 100 and below (say, a chance), then this is what you would do:

    Code:
    @LimitInteger
    public int Must__Be__Under__ONEHUNDRED = 100;
    
    If it ever goes above 100, it will correct it to the default value, given by limit = yourLimit. It also displays a message, with %node replaced by the node and %limit by the limit. You can set this by warning = newWarningString. Lastly, there is a boolean 'correct', which if set to true will rectify to the default limit. If not, it will let it pass.

    Lastly, '_' is replaced by '.', and '__' by ' '.

    Enjoy!
     
    Acrobot and iKeirNez like this.
  30. Offline

    md_5

    Very nice, although that will fail very horribly on more complex configs.
    Also kind of inefficient and looks like it needs apache commons.
     
    Acrobot and iKeirNez like this.
Thread Status:
Not open for further replies.

Share This Page