Resource Create Configs with UTF-Charset correctly

Discussion in 'Spigot Plugin Development' started by TimeoutHD, May 31, 2018.

  1. Everyone knows it, if someone try to save a special character in an Yaml it didn't work or cannot read by Bukkit / Spigot. In this short tutorial I will show you how to create UTFConfigs.

    First you need this class:
    Code (Java):
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.Writer;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.ArrayList;
    import java.util.List;

    import org.apache.commons.lang.Validate;
    import org.bukkit.Bukkit;
    import org.bukkit.configuration.InvalidConfigurationException;
    import org.bukkit.configuration.file.YamlConfiguration;
    import org.bukkit.craftbukkit.libs.jline.internal.InputStreamReader;
    import org.bukkit.entity.Player;
    import org.yaml.snakeyaml.DumperOptions;
    import org.yaml.snakeyaml.Yaml;
    import org.yaml.snakeyaml.representer.Representer;

    import com.google.common.base.Charsets;
    import com.google.common.io.Files;

    public class UTFConfig extends YamlConfiguration {
     
        public UTFConfig(File file) {
            try {
                load(file);
            } catch (IOException | InvalidConfigurationException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void save(File file) throws IOException {
            Validate.notNull(file, "File can't be null");
            Files.createParentDirs(file);
            String data = this.saveToString();
            Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8);
         
            try {
                writer.write(data);
            } finally {writer.close();}
        }
     
        @Override
        public String saveToString() {
            try {
                Field optionField = Reflections.getField(getClass(), "yamlOptions");
                Field representerField = Reflections.getField(getClass(), "yamlRepresenter");
                Field yamlField = Reflections.getField(getClass(), "yaml");
             
                optionField.setAccessible(true);
                representerField.setAccessible(true);
                yamlField.setAccessible(true);
             
                DumperOptions yamlOptions = (DumperOptions) optionField.get(this);
                Representer yamlRepresenter = (Representer) representerField.get(this);
                Yaml yaml = (Yaml) yamlField.get(this);
                DumperOptions.FlowStyle flow = DumperOptions.FlowStyle.BLOCK;
             
                yamlOptions.setIndent(this.options().indent());
                yamlOptions.setDefaultFlowStyle(flow);
                yamlOptions.setAllowUnicode(true);
                yamlRepresenter.setDefaultFlowStyle(flow);
             
                String header = this.buildHeader();
                String dump = yaml.dump(this.getValues(false));
             
                if(dump.equals("{}\n"))dump = "";
                return header + dump;
            } catch (Exception e) {e.printStackTrace();}
            return "Error: Cannot be saved to String";
        }
     
        @Override
        public void load(File file) throws IOException, InvalidConfigurationException {
            Validate.notNull(file, "File can't be null");
            this.load(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8));
        }
     
        public static class Reflections {
         
            public static Field modifiers = getField( Field.class, "modifiers" );

            {
                setAccessible( true, modifiers );
            }

            public Class< ? > getNMSClass( String name ) {
                String version = Bukkit.getServer().getClass().getPackage().getName().split( "\\." )[ 3 ];
                try {
                    return Class.forName( "net.minecraft.server." + version + "." + name );
                } catch ( Exception e ) {
                    return null;
                }
            }

            public Class< ? > getBukkitClass( String name ) {
                try {
                    String version = Bukkit.getServer().getClass().getPackage().getName().split( "\\." )[ 3 ];
                    return Class.forName( "org.bukkit.craftbukkit." + version + "." + name );
                } catch( Exception ex ) {
                    return null;
                }
            }

            public void sendPacket( Player to, Object packet ) {
                try {
                    Object playerHandle = to.getClass().getMethod( "getHandle" ).invoke( to );
                    Object playerConnection = playerHandle.getClass().getField( "playerConnection" ).get( playerHandle );
                    playerConnection.getClass().getMethod( "sendPacket", getNMSClass( "Packet" ) ).invoke( playerConnection, packet );
                } catch ( Exception e ) {
                    e.printStackTrace();
                }
            }

            public void setField( Object change, String name, Object to ) {
                try {
                    Field field = getField( change.getClass(), name );
                    setAccessible( true, field );
                    field.set( change, to );
                    setAccessible( false, field);
                } catch( Exception ex ) {
                    ex.printStackTrace();
                }
            }

            public static void setAccessible( boolean state, Field... fields ) {
                try {
                    for( Field field : fields ) {
                        field.setAccessible( state );
                        if( Modifier.isFinal( field.getModifiers() ) ) {
                            field.setAccessible(true);
                            modifiers.set( field, field.getModifiers() & ~Modifier.FINAL );
                        }
                    }
                } catch( Exception ex ) {
                    ex.printStackTrace();
                }
            }

            public static Field getField( Class< ? > clazz, String name ) {
                Field field = null;

                for( Field f : getFields( clazz ) ) {
                    if( !f.getName().equals( name ) )
                        continue;

                    field = f;
                    break;
                }

                return field;
            }

            public static List< Field > getFields( Class< ? > clazz ) {
                List< Field > buf = new ArrayList<>();

                do {
                    try {
                        for( Field f : clazz.getDeclaredFields() )
                          buf.add( f );
                    } catch( Exception ex ) {}
                } while( ( clazz = clazz.getSuperclass() ) != null );

                return buf;
            }
         
            public String getVersion() {
                String ver = Bukkit.getServer().getClass().getPackage().getName().split( "\\." )[ 3 ];
                return ver;
            }
        }
    }
     

    This is the UTFConfig.class With this class you can read and save YamlConfigurations with special Characters. This is very usefull if you want to save messages in other languages than English.

    In the second part we must overwrite the getConfig() Methods in our Mainclass.
    Code (Java):
    import java.io.File;

    import org.bukkit.plugin.java.JavaPlugin;

    public class Main extends JavaPlugin {
     
        //Instance of Mainclass
        public static Main plugin;
     
        //private config.yml UTFConfig.
        private UTFConfig config;
     
        @Override
        public void onEnable() {
            plugin = this;
         
            //Initialize config variable
            config = new UTFConfig(new File(getDataFolder(), "config.yml"));
        }
     
        @Override
        public void onDisable() {

        }

        //Override default getConfig() Method.
        @Override
        public UTFConfig getConfig() {
            return config;
        }

    }
     

    Now we have one Problem. The normal config.yml isn't exists. We must create a new File in our plugins folder.
    We need one more Class to create custom files. I call this Class ConfigCreator.
    Code (Java):
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.OutputStream;

    import org.bukkit.Bukkit;
    import org.bukkit.plugin.Plugin;

    import com.google.common.io.ByteStreams;

    public class ConfigCreator {
     
        //Instance of MainClass
        private static Main main = Main.plugin;
     
        //Must be called after Instance initialization
        public static void loadConfigs() {
            loadResource(main, "config.yml");
        }

        //Generates a file plugin's DataFolder on resourse.
        private static void loadResource(Plugin plugin, String resource) {
            File folder = plugin.getDataFolder();
            if(!folder.exists())folder.mkdirs();
            File resourceFile = new File(folder, resource);
            try {
                //generate subfolders.
                if(resource.contains("/")) {
                    String[] folders = resource.split("/");
                    resourceFile = plugin.getDataFolder();
                    for(int i = 0; i < folders.length -1; i++) {
                        resourceFile = new File(resourceFile, folders[i]);
                        if(!resourceFile.exists())resourceFile.mkdirs();
                    }
                    resourceFile = new File(resourceFile, folders[folders.length -1]);
                }
                if (!resourceFile.exists()) {
                    Bukkit.getConsoleSender().sendMessage("§a" + resource + " §7could not be loaded. §aGenerate File...");
                    resourceFile.createNewFile();
                    //Copy config.yml from jar
                    try (InputStream in = plugin.getResource(resource);
                    OutputStream out = new FileOutputStream(resourceFile)) {
                        ByteStreams.copy(in, out);
                    }
                }
                Bukkit.getConsoleSender().sendMessage("§a" + resource + " §fis loaded!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    The loadResource Method creates simple Files. If you want to create a subfolder the String should be "subfolder/filename". Don't forget the ending of the File.

    Last but not least we must call the loadConfigs() Method in our onEnable() Method. Be sure you create the files directly and after the initialization of our instance.

    Code (Text):
        @Override
        public void onEnable() {
            plugin = this;
         
            //Create needed Files
            ConfigCreator.loadConfigs();
         
            //Initialize config variable
            config = new UTFConfig(new File(getDataFolder(), "config.yml"));
        }
    Now you can write and read YamlConfigurations with UTF-Charset. You can ask, If you need any help.
     
    #1 TimeoutHD, May 31, 2018
    Last edited: May 31, 2018