[Tutorial] NBT storage - Create your own compressed NBT files

Discussion in 'Spigot Plugin Development' started by Flamedek, Jun 11, 2015.

  1. Hi everyone, today I will show you how you can create your own storage files using Minecrafts compressed NBT techniques. Now I am not sure if this is better than Bukkits included YAML parser, but here are some pros and cons.

    Pros:
    - Compressed
    - Unreadable
    Cons:
    - Not manually editable
    - Only stores primitives and Strings
    - NMS (version dependent)

    So let's get started. We are going to create an object representing the file. When you want to read from or write to a file you create a new instance and use the created NBTTagCompound.

    The setup
    Code (Text):
    public class NBTStorageFile {

        // the file to use
        private final File file;
        // the TagCompound containing the files content
        private NBTTagCompound tagCompound;
       
        public NBTStorageFile(File file) {
            this.file = file;
        }
        // two optional constructors:
        public NBTStorageFile(String folder, String name) {
            this(new File(folder, name + ".dat"));
        }

        public NBTStorageFile(String path) {
            this(new File(path));
        }
       
    }

    Reading/Writing
    We will get to using the TagCompound in a bit, but now the interesting stuff; writing the compound to a file, and filling a compound from a file. It's really simple too!
    Code (Text):
    public void read() {
            try {
                // if the file exists we read it
                if(file.exists()) {
                    FileInputStream fileinputstream = new FileInputStream(file);
                    tagCompound = NBTCompressedStreamTools.a(fileinputstream);
                    fileinputstream.close();
                   
                } else {
                // else we create an empty TagCompound
                   clear();
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    public void clear() {
            tagCompound = new NBTTagCompound();
        }
     
    Code (Text):
    public void write() {
            try {

                if(!file.exists()) {
                    file.createNewFile();
                }

                FileOutputStream fileoutputstream = new FileOutputStream(file);

                NBTCompressedStreamTools.a(tagCompound, fileoutputstream);
                fileoutputstream.close();

            } catch(IOException e) {
                e.printStackTrace();
            }

        }
    So as you can see there is not much to it. The methods that do all the work are in the NBTCompressedStreamTools class.

    Version independence
    This class obviously uses version imports, and will have to be updated per version (or use an interface to make it for multiple versions). BUT we can make using this class version independent by creating delegate methods of the NBTTagCompound. This is optional, but makes working with the object easier.
    Code (Text):
    public Set<String> getKeys() {
            return tagCompound.c();
        }
        public boolean hasKey(String key) {
            return tagCompound.hasKey(key);
        }
        public boolean isEmpty() {
            return tagCompound.isEmpty();
        }
        public void remove(String key) {
            tagCompound.remove(key);
        }

        public boolean getBoolean(String key) {
            return tagCompound.getBoolean(key);
        }
        public double getDouble(String key) {
            return tagCompound.getDouble(key);
        }
        public float getFloat(String key) {
            return tagCompound.getFloat(key);
        }
        public int getInt(String key) {
            return tagCompound.getInt(key);
        }
        public int[] getIntArray(String key) {
            return tagCompound.getIntArray(key);
        }
        public long getLong(String key) {
            return tagCompound.getLong(key);
        }
        public short getShort(String key) {
            return tagCompound.getShort(key);
        }
        public String getString(String key) {
            return tagCompound.getString(key);
        }
       
        public void setBoolean(String key, boolean value) {
            tagCompound.setBoolean(key, value);
        }
        public void setDouble(String key, double value) {
            tagCompound.setDouble(key, value);
        }
        public void setFloat(String key, float value) {
            tagCompound.setFloat(key, value);
        }
        public void setInt(String key, int value) {
            tagCompound.setInt(key, value);
        }
        public void setIntArray(String key, int[] value) {
            tagCompound.setIntArray(key, value);
        }
        public void setLong(String key, long value) {
            tagCompound.setLong(key, value);
        }
        public void setShort(String key, short value) {
            tagCompound.setShort(key, value);
        }
        public void setString(String key, String value) {
            tagCompound.setString(key, value);
        }

    These methods all bind one value to one key. But there is one more trick to do: we can store a List of values to one key. That can make it a lot more useful for data storage. I will show you how to store a list of Strings, but it also applies to the primitives.
    Code (Text):
    public void setStringList(String key, Collection<String> value) {
            // create a new List. This list can contain any NBTBase
            NBTTagList list = new NBTTagList();
           
            for(String s : value) {
                // if you want to store something else than a String, you create an other NBTbase here.
                // currently the NBTBases are: primitives, String, List and the compound itself.
                NBTTagString string = new NBTTagString(s);
                list.add(string);
            }
            tagCompound.set(key, list);
        }
       
        public List<String> getStringList(String key) {
            List<String> result = Lists.newArrayList();
           
            NBTBase base = tagCompound.get(key);
            if(base != null && base.getClass() == NBTTagList.class) {
               
                NBTTagList list = (NBTTagList) base;
                for(int i = 0; i < list.size(); i++) {
                    result.add(list.getString(i));
                }
            }
            return result;
        }

    And that is it! We can now create the file, read it from disk if it exists and use the NBTTagCompound to manage data.

    Test example
    Code (Text):
        public static void main(String[] args) {
           
            NBTStorageFile file = new NBTStorageFile("C:\\Users\\YourName\\Desktop", "compressedFile.dat");
            file.clear();
           
            file.setBoolean("TestKey", true);
           
            file.write();
        }
    Running this code will create an file on your desktop. If you open it with notepad++ it looks like this:
    [​IMG]

    Complete Code:
    http://hastebin.com/luvezaciqe.avrasm
     
    • Useful Useful x 1
  2. Use JNBT instead NMS to make version independent.
     
  3. That is a good alternative. Especially if you plan on intensively using this, just for version independance
     
  4. You can just do this with a simple serializer and a GZipOutputStream to achieve the same effect.
     
  5. Well if I'm not mistaken that is more or less what Notch did here aswell. I'm just showing how you can do this using NMS, not suggesting it's always the best option. If I look at the pros and cons I would say the cons weigh heavier.