<|start_filename|>src/main/java/org/wargamer2010/signshop/blocks/SignShopItemMeta.java<|end_filename|>

package org.wargamer2010.signshop.blocks;

import com.google.common.collect.ImmutableList;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.FireworkEffect;
import org.bukkit.FireworkEffect.Builder;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.*;
import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.wargamer2010.signshop.configuration.ColorUtil;
import org.wargamer2010.signshop.configuration.SignShopConfig;
import org.wargamer2010.signshop.util.itemUtil;
import org.wargamer2010.signshop.util.signshopUtil;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

import static org.wargamer2010.signshop.util.itemUtil.enchantmentsToMessageFormat;

public class SignShopItemMeta {
    private static final String listSeperator = "~";
    private static final String valueSeperator = "-";
    private static final String innerListSeperator = "^";
    private static final String filename = "books.db";

    private static  ChatColor txtColor = ChatColor.YELLOW;
    private static  ChatColor txtColorTwo = ChatColor.DARK_PURPLE;
    
    private SignShopItemMeta() {

    }

    public static void init() {
        SSDatabase db = new SSDatabase(filename);
        txtColor = SignShopConfig.getTextColor();
        txtColorTwo = SignShopConfig.getTextColorTwo();
        try {
            if(!db.tableExists("ItemMeta"))
                db.runStatement("CREATE TABLE ItemMeta ( ItemMetaID INTEGER, ItemMetaHash INT, PRIMARY KEY(ItemMetaID) )", null, false);
            if(!db.tableExists("MetaProperty"))
                db.runStatement("CREATE TABLE MetaProperty ( PropertyID INTEGER, ItemMetaID INTEGER, PropertyName TEXT NOT NULL, ProperyValue TEXT NOT NULL, PRIMARY KEY(PropertyID) )", null, false);
        } finally {
            db.close();
        }
    }

    public static String convertColorsToDisplay(List<Color> colors) {
        if(colors == null || colors.isEmpty())
            return "";
        List<String> temp = new LinkedList<>();

        for(Color color : colors) {
            temp.add(ColorUtil.getColorAsString(color));
        }

        String[] arr = new String[temp.size()];
        return signshopUtil.implode(temp.toArray(arr), ", ");
    }

    public static ChatColor getTextColor() {
        return txtColor;
    }

    private static String convertFireworkTypeToDisplay(FireworkEffect.Type type) {
        String temp = signshopUtil.capFirstLetter(type.toString().toLowerCase()).replace("_", " ");
        if(temp.contains(" ")) {
            String[] temparr = temp.split(" ");
            String bak = temparr[0]; temparr[0] = temparr[1];
            temparr[1] = bak;
            temp = signshopUtil.implode(temparr, " ");
        }
        return signshopUtil.capFirstLetter(temp);
    }

    private static boolean hasNoMeta(ItemStack stack) {
        // This seems silly but some parts of the code below function when an item has no meta data but itemmeta is not null
        return (stack.getItemMeta() == null);
    }

    private static String getDisplayName(ItemStack stack) {
        return getDisplayName(stack, txtColor);
    }

    //May need to implement my own WhatIsIt friendly name yml
    private static String getDisplayName(ItemStack stack, ChatColor color) {
        String txtcolor = txtColor.toString();
        String customcolor = (stack.getEnchantments().isEmpty() ? color.toString() : txtColorTwo.toString());

        String normal = itemUtil.formatMaterialName(stack);
        String displayname = "";

        if(stack.getItemMeta() != null) {
            String custom = (stack.getItemMeta().hasDisplayName()
                    ? (txtcolor + "\"" + customcolor + stack.getItemMeta().getDisplayName() + txtcolor + "\"") : "");
            if(custom.length() > 0)
                displayname = (custom + " (" + normal + ")" + txtcolor);
        }

        if(displayname.isEmpty())
            displayname = (txtcolor + customcolor + normal + txtcolor);

        if(stack.getType().getMaxDurability() >= 30 && stack.getDurability() != 0)
            displayname = (" Damaged " + displayname);
        if(stack.getEnchantments().size() > 0)
            displayname += (txtcolor + " " + enchantmentsToMessageFormat(stack.getEnchantments()));

        return displayname;
    }

    public static String getName(ItemStack stack) {
        if (hasNoMeta(stack))
            return getDisplayName(stack);

        ItemMeta meta = stack.getItemMeta();

        List<MetaType> metatypes = getTypesOfMeta(meta);
        for(MetaType type : metatypes) {
            if(type == MetaType.EnchantmentStorage) {
                EnchantmentStorageMeta enchantmeta = (EnchantmentStorageMeta) meta;
                if(enchantmeta.hasStoredEnchants())
                    return (getDisplayName(stack, txtColorTwo) + " " + itemUtil.enchantmentsToMessageFormat(enchantmeta.getStoredEnchants()));
            } else if(type == MetaType.LeatherArmor) {
                LeatherArmorMeta leathermeta = (LeatherArmorMeta) meta;
                return (ColorUtil.getColorAsString(leathermeta.getColor()) + " Colored " + getDisplayName(stack));
            } else if(type == MetaType.Skull) {
                String postfix = "'s Head";
                SkullMeta skullmeta = (SkullMeta) meta;
                if(skullmeta.getOwner() != null) {
                    // Name coloring support had to be dropped since there is no more link between
                    // the skull owner and the actual player
                    return (skullmeta.getOwner() + postfix);
                } else {
                    // We can no longer get a pretty name by ID (SKULL_ITEM isn't pretty, is it?)
                    // So we'll have to rely on the web lookup, if the server owner has it enabled
                    return getDisplayName(stack);
                }
            } else if(type == MetaType.Potion) {
                PotionMeta potionmeta = (PotionMeta) meta;

                boolean first = true;
                StringBuilder namebuilder = new StringBuilder(512);
                namebuilder.append(getDisplayName(stack, txtColorTwo));

                Collection<PotionEffect> effects = null;
                Potion pot = null;
                if(!potionmeta.hasCustomEffects()) {
                    try {
                        pot = Potion.fromItemStack(stack);
                        effects = pot.getEffects();
                    } catch(IllegalArgumentException ex) {
                        int EXTENDED_BIT = 0x40;
                        short damage = stack.getDurability();
                        if ((damage & EXTENDED_BIT) > 0) {
                            // Instant potions cannot be extended!
                            // So let's invert the extended bit and retry.
                            int tempint = (damage ^ EXTENDED_BIT);
                            stack.setDurability((short) tempint);
                            try {
                                pot = Potion.fromItemStack(stack);
                                effects = pot.getEffects();
                            } catch (IllegalArgumentException ex2) {
                                // I give up
                            }
                            stack.setDurability(damage);
                        }
                        if(effects == null) {
                            pot = new Potion(PotionType.WATER);
                            effects = pot.getEffects();
                        }
                    }
                } else
                    effects = potionmeta.getCustomEffects();

                namebuilder.append(" (");

                for(PotionEffect effect : effects) {
                    if(first) first = false;
                    else namebuilder.append(", ");

                    namebuilder.append(signshopUtil.capFirstLetter(effect.getType().getName().toLowerCase()));
                    if(pot != null && pot.getLevel() > 0) {
                        namebuilder.append(" ");
                        namebuilder.append(itemUtil.binaryToRoman(pot.getLevel()));
                    } else {
                        namebuilder.append(" with");
                        namebuilder.append(" amplifier: ");
                        namebuilder.append(effect.getAmplifier());
                    }
                    if(effect.getDuration() > 1) {
                        namebuilder.append(" and duration: ");
                        namebuilder.append(effect.getDuration());
                    }
                }

                namebuilder.append(")");

                return namebuilder.toString();
            } else if(type == MetaType.Fireworks) {
                FireworkMeta fireworkmeta = (FireworkMeta) meta;

                StringBuilder namebuilder = new StringBuilder(256);
                namebuilder.append(getDisplayName(stack, txtColorTwo));

                if(fireworkmeta.hasEffects()) {
                    namebuilder.append(" (");
                    namebuilder.append("Duration : ");
                    namebuilder.append(fireworkmeta.getPower());
                    for(FireworkEffect effect : fireworkmeta.getEffects()) {
                        namebuilder.append(", ");

                        namebuilder.append(convertFireworkTypeToDisplay(effect.getType()));
                        namebuilder.append(" with");
                        namebuilder.append((effect.getColors().size() > 0 ? " colors: " : ""));
                        namebuilder.append(convertColorsToDisplay(effect.getColors()));
                        namebuilder.append((effect.getFadeColors().size() > 0 ? " and fadecolors: " : ""));
                        namebuilder.append(convertColorsToDisplay(effect.getFadeColors()));

                        namebuilder.append(effect.hasFlicker() ? " +twinkle" : "");
                        namebuilder.append(effect.hasTrail()? " +trail" : "");
                    }
                    namebuilder.append(")");
                }

                return namebuilder.toString();
            }
        }

        stack.getItemMeta().hasDisplayName();
        return getDisplayName(stack);
    }

    public static void setMetaForID(ItemStack stack, Integer ID) {
        Map<String, String> metamap = new LinkedHashMap<>();
        ItemMeta meta = stack.getItemMeta();
        SSDatabase db = new SSDatabase(filename);

        try {
            Map<Integer, Object> pars = new LinkedHashMap<>();
            pars.put(1, ID);

            ResultSet setprops = (ResultSet)db.runStatement("SELECT PropertyName, ProperyValue FROM MetaProperty WHERE ItemMetaID = ?;", pars, true);
            if(setprops == null)
                return;
            try {
                while(setprops.next())
                    metamap.put(setprops.getString("PropertyName"), setprops.getString("ProperyValue"));
            } catch(SQLException ex) {
                return;
            }

            if(metamap.isEmpty())
                return;
        } finally {
            db.close();
        }


        if(!getPropValue("displayname", metamap).isEmpty())
            meta.setDisplayName(getPropValue("displayname", metamap));
        if(!getPropValue("lore", metamap).isEmpty()) {
            List<String> temp = Arrays.asList(getPropValue("lore", metamap).split(listSeperator));
            meta.setLore(temp);
        }
        if(!getPropValue("enchants", metamap).isEmpty()) {
            for(Map.Entry<Enchantment, Integer> enchant : signshopUtil.convertStringToEnchantments(getPropValue("enchants", metamap)).entrySet()) {
                meta.addEnchant(enchant.getKey(), enchant.getValue(), true);
            }
        }

        List<MetaType> metatypes = getTypesOfMeta(meta);

        try {
            for(MetaType type : metatypes) {
                if(type == MetaType.EnchantmentStorage) {
                    EnchantmentStorageMeta enchantmentmeta = (EnchantmentStorageMeta) meta;
                    if(!getPropValue("storedenchants", metamap).isEmpty()) {
                        for(Map.Entry<Enchantment, Integer> enchant : signshopUtil.convertStringToEnchantments(getPropValue("storedenchants", metamap)).entrySet()) {
                            enchantmentmeta.addStoredEnchant(enchant.getKey(), enchant.getValue(), true);
                        }
                    }
                }
                else if(type == MetaType.LeatherArmor) {
                    LeatherArmorMeta leathermeta = (LeatherArmorMeta) meta;
                    if(!getPropValue("color", metamap).isEmpty())
                        leathermeta.setColor(Color.fromRGB(Integer.parseInt(getPropValue("color", metamap))));
                }
                else //noinspection StatementWithEmptyBody
                    if (type == MetaType.Map) {
                    // We could set scaling here but for some reason Spigot doesn't when stacks are built up
                    // Which results in items not matching anymore if we do, so we won't
                }
                else if(type == MetaType.Repairable) {
                    Repairable repairmeta = (Repairable) meta;
                    if(!getPropValue("repaircost", metamap).isEmpty())
                        repairmeta.setRepairCost(Integer.parseInt(getPropValue("repaircost", metamap)));
                }
                else if(type == MetaType.Skull) {
                    SkullMeta skullmeta = (SkullMeta) meta;
                    if(!getPropValue("owner", metamap).isEmpty())
                        skullmeta.setOwner(getPropValue("owner", metamap));
                } else if(type == MetaType.Potion) {
                    PotionMeta potionmeta = (PotionMeta) meta;
                    List<PotionEffect> effects = convertStringToPotionMeta(getPropValue("potioneffects", metamap));
                    for(PotionEffect effect : effects) {
                        potionmeta.addCustomEffect(effect, true);
                    }
                } else if(type == MetaType.Fireworks) {
                    FireworkMeta fireworkmeta = (FireworkMeta) meta;
                    fireworkmeta.addEffects(convertStringToFireworkEffects(getPropValue("fireworkeffects", metamap)));
                    fireworkmeta.setPower(Integer.parseInt(getPropValue("fireworkpower", metamap)));
                }
            }
        } catch (ClassCastException | NumberFormatException ignored) {

        }


        stack.setItemMeta(meta);
    }

    public static void storeMeta(ItemStack stack) {
        if (hasNoMeta(stack))
            return;

        SSDatabase db = new SSDatabase(filename);
        Map<String, String> metamap = getMetaAsMap(stack.getItemMeta());

        try {
            Integer existingID = getMetaID(stack, metamap);
            if(existingID > -1)
                return;

            Integer itemmetaid;
            Map<Integer, Object> pars = new LinkedHashMap<>();
            pars.put(1, metamap.hashCode());

            itemmetaid = (Integer)db.runStatement("INSERT INTO ItemMeta(ItemMetaHash) VALUES (?);", pars, false);

            if(itemmetaid == null || itemmetaid == -1)
                return;

            for(Map.Entry<String, String> metaproperty : metamap.entrySet()) {
                pars.clear();
                pars.put(1, itemmetaid);
                pars.put(2, metaproperty.getKey());
                pars.put(3, metaproperty.getValue());
                db.runStatement("INSERT INTO MetaProperty(ItemMetaID, PropertyName, ProperyValue) VALUES (?, ?, ?);", pars, false);
            }

        } finally {
            db.close();
        }
    }

    public static Integer getMetaID(ItemStack stack) {
        if (hasNoMeta(stack))
            return -1;

        return getMetaID(stack, null);
    }

    private static Integer getMetaID(ItemStack stack, Map<String, String> pMetamap) {
        Map<String, String> metamap = (pMetamap != null ? pMetamap : getMetaAsMap(stack.getItemMeta()));
        SSDatabase db = new SSDatabase(filename);
        try {
            Map<Integer, Object> pars = new LinkedHashMap<>();
            pars.put(1, metamap.hashCode());
            ResultSet set = (ResultSet)db.runStatement("SELECT ItemMetaID FROM ItemMeta WHERE ItemMetaHash = ?;", pars, true);
            if(set != null && set.next())
                return set.getInt("ItemMetaID");
        } catch (SQLException ignored) {

        } finally {
            db.close();
        }

        return -1;
    }

    public static Map<String, String> getMetaAsMap(ItemMeta meta) {
        Map<String, String> metamap = new LinkedHashMap<>();
        if(meta == null)
            return metamap;
        List<MetaType> types = getTypesOfMeta(meta);

        if(meta.getDisplayName() != null)
            metamap.put("displayname", meta.getDisplayName());
        if(meta.getEnchants() != null && !meta.getEnchants().isEmpty())
            metamap.put("enchants", signshopUtil.convertEnchantmentsToString(meta.getEnchants()));
        if(meta.getLore() != null && !meta.getLore().isEmpty()) {
            String[] lorearr = new String[meta.getLore().size()];
            metamap.put("lore", signshopUtil.implode(meta.getLore().toArray(lorearr), listSeperator));
        }

        for(MetaType type : types) {
            if(type == MetaType.EnchantmentStorage) {
                EnchantmentStorageMeta enchantmentmeta = (EnchantmentStorageMeta) meta;
                if(enchantmentmeta.hasStoredEnchants())
                    metamap.put("storedenchants", signshopUtil.convertEnchantmentsToString(enchantmentmeta.getStoredEnchants()));
            }
            else if(type == MetaType.LeatherArmor) {
                LeatherArmorMeta leathermeta = (LeatherArmorMeta) meta;
                metamap.put("color", Integer.toString(leathermeta.getColor().asRGB()));
            }
            else if(type == MetaType.Map) {
                MapMeta mapmeta = (MapMeta) meta;
                metamap.put("scaling", Boolean.toString(mapmeta.isScaling()));
            }
            else if(type == MetaType.Skull) {
                SkullMeta skullmeta = (SkullMeta) meta;
                if(skullmeta.hasOwner()) {
                    metamap.put("owner", skullmeta.getOwner());
                }
            }
            else if(type == MetaType.Repairable) {
                if(meta instanceof Repairable) {
                    Repairable repairmeta = (Repairable) meta;
                    if(repairmeta.hasRepairCost()) {
                        metamap.put("repaircost", Integer.toString(repairmeta.getRepairCost()));
                    }
                }
            } else if(type == MetaType.Potion) {
                PotionMeta potionmeta = (PotionMeta) meta;
                if(potionmeta.hasCustomEffects()) {
                    metamap.put("potioneffects", convertPotionMetaToString(potionmeta));
                }
            } else if(type == MetaType.Fireworks) {
                FireworkMeta fireworkmeta = (FireworkMeta) meta;
                if(fireworkmeta.hasEffects()) {
                    metamap.put("fireworkeffects", convertFireworkMetaToString(fireworkmeta));
                    metamap.put("fireworkpower", Integer.toString(fireworkmeta.getPower()));
                }
            }
        }

        return metamap;
    }

    private static List<MetaType> getTypesOfMeta(ItemMeta meta) {
        List<MetaType> types = new LinkedList<>();

        if(meta instanceof org.bukkit.inventory.meta.EnchantmentStorageMeta)
            types.add(MetaType.EnchantmentStorage);
        if(meta instanceof org.bukkit.inventory.meta.LeatherArmorMeta)
            types.add(MetaType.LeatherArmor);
        if(meta instanceof org.bukkit.inventory.meta.MapMeta)
            types.add(MetaType.Map);
        if(meta instanceof org.bukkit.inventory.meta.SkullMeta)
            types.add(MetaType.Skull);
        if(meta instanceof org.bukkit.inventory.meta.Repairable)
            types.add(MetaType.Repairable);
        if(meta instanceof org.bukkit.inventory.meta.PotionMeta)
            types.add(MetaType.Potion);
        if(meta instanceof org.bukkit.inventory.meta.FireworkMeta)
            types.add(MetaType.Fireworks);
        return types;
    }

    private static String getPropValue(String name, Map<String, String> metamap) {
        return metamap.getOrDefault(name, "");
    }

    private static String convertPotionMetaToString(PotionMeta meta) {
        if(!meta.hasCustomEffects())
            return "";
        StringBuilder returnbuilder = new StringBuilder(meta.getCustomEffects().size() * 50);
        for(PotionEffect effect : meta.getCustomEffects()) {
            returnbuilder.append(effect.getType().getName());
            returnbuilder.append(valueSeperator);
            returnbuilder.append(effect.getDuration());
            returnbuilder.append(valueSeperator);
            returnbuilder.append(effect.getAmplifier());
            returnbuilder.append(valueSeperator);
            returnbuilder.append(effect.isAmbient());
            returnbuilder.append(listSeperator);
        }
        return returnbuilder.toString();
    }

    @SuppressWarnings("deprecation") // Allowed for transition purposes
    private static List<PotionEffect> convertStringToPotionMeta(String meta) {
        List<PotionEffect> effects = new LinkedList<>();
        List<String> splitted = Arrays.asList(meta.split(listSeperator));
        if(splitted.isEmpty())
            return effects;
        for(String split : splitted) {
            String[] bits = split.split(valueSeperator);
            if(bits.length == 4) {
                try {
                    int dur = Integer.parseInt(bits[1]);
                    int amp = Integer.parseInt(bits[2]);
                    boolean amb = Boolean.parseBoolean(bits[3]);
                    PotionEffect effect = null;
                    try {
                        int id = Integer.parseInt(bits[0]);
                        effect = new PotionEffect(PotionEffectType.getById(id), dur, amp, amb);
                    } catch(NumberFormatException ex) {
                        PotionEffectType type = PotionEffectType.getByName(bits[0]);
                        if(type != null)
                            effect = new PotionEffect(PotionEffectType.getByName(bits[0]), dur, amp, amb);
                    }
                    if(effect != null)
                        effects.add(effect);
                } catch (NumberFormatException ignored) {
                }


            }
        }

        return effects;
    }

    private static String getColorsAsAString(List<Color> colors) {
        List<String> temp = new LinkedList<>();
        for(Color color : colors) {
            temp.add(Integer.toString(color.asRGB()));
        }
        String[] colorarr = new String[temp.size()];
        return signshopUtil.implode(temp.toArray(colorarr), innerListSeperator);
    }

    private static ImmutableList<Color> getColorsFromString(String colors) {
        List<Color> temp = new LinkedList<>();
        String[] split = colors.split(innerListSeperator);
        for(String part : split) {
            try {
                temp.add(Color.fromRGB(Integer.parseInt(part)));
            } catch (NumberFormatException ignored) {
            }
        }

        return ImmutableList.copyOf(temp);
    }

    private static String convertFireworkMetaToString(FireworkMeta meta) {
        if(!meta.hasEffects())
            return "";
        StringBuilder returnbuilder = new StringBuilder(meta.getEffects().size() * 50);

        for(FireworkEffect effect : meta.getEffects()) {
            returnbuilder.append(effect.getType().toString());
            returnbuilder.append(valueSeperator);
            returnbuilder.append(getColorsAsAString(effect.getColors()));
            returnbuilder.append(valueSeperator);
            returnbuilder.append(getColorsAsAString(effect.getFadeColors()));
            returnbuilder.append(valueSeperator);
            returnbuilder.append(effect.hasFlicker());
            returnbuilder.append(valueSeperator);
            returnbuilder.append(effect.hasTrail());
            returnbuilder.append(listSeperator);
        }
        return returnbuilder.toString();
    }

    private static List<FireworkEffect> convertStringToFireworkEffects(String meta) {
        List<FireworkEffect> effects = new LinkedList<>();
        List<String> splitted = Arrays.asList(meta.split(listSeperator));
        if(splitted.isEmpty())
            return effects;
        for(String split : splitted) {
            String[] bits = split.split(valueSeperator);
            if(bits.length == 5) {
                try {
                    Builder builder = FireworkEffect.builder().with(FireworkEffect.Type.valueOf(bits[0]));
                    ImmutableList<Color> colors = getColorsFromString(bits[1]);
                    if(colors != null)
                        builder = builder.withColor(colors);
                    ImmutableList<Color> fadecolors = getColorsFromString(bits[2]);
                    if(fadecolors != null)
                        builder = builder.withFade(fadecolors);
                    builder = (Boolean.parseBoolean(bits[3]) ? builder.withFlicker() : builder);
                    builder = (Boolean.parseBoolean(bits[4]) ? builder.withTrail() : builder);

                    effects.add(builder.build());
                } catch (NumberFormatException ignored) {
                }
            }
        }
        return effects;
    }

    private enum MetaType {
        EnchantmentStorage,
        LeatherArmor,
        Map,
        Potion,
        Repairable,
        Fireworks,
        Skull,
        Stock,
    }
}


<|start_filename|>src/main/java/org/wargamer2010/signshop/util/DataConverter.java<|end_filename|>
package org.wargamer2010.signshop.util;

import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.FileUtil;
import org.wargamer2010.signshop.SignShop;
import org.wargamer2010.signshop.blocks.*;
import org.wargamer2010.signshop.configuration.Storage;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

public class DataConverter {
    static File sellersFile;
    static File sellersFileBackup;
    static File timingFile;
    static File timingFileBackup;

    //TODO Make a very complex 1.8 set of data to convert.
    public static void init() {
        File dataFolder = SignShop.getInstance().getDataFolder();
        sellersFile = new File(dataFolder, "sellers.yml");
        FileConfiguration sellers = new YamlConfiguration();
        try {
            sellers.load(sellersFile);
            SignShop.log("Checking data version.", Level.INFO);
            if (sellers.getInt("DataVersion") < SignShop.DATA_VERSION) {
                sellersFileBackup = new File(dataFolder, "sellersBackup" + SSTimeUtil.getDateTimeStamp() + ".yml");
                FileUtil.copy(sellersFile, sellersFileBackup);
                convertData(sellers);
                convertTiming();
            }
            else {
                SignShop.log("Your data is current.", Level.INFO);
            }
        } catch (IOException | InvalidConfigurationException ignored) {
        }
    }

    private static void convertTiming() {
        File dataFolder = SignShop.getInstance().getDataFolder();
        timingFile = new File(dataFolder, "timing.yml");
        FileConfiguration timing = new YamlConfiguration();
        try {
            timing.load(timingFile);
            ConfigurationSection expirables = timing.getConfigurationSection("expirables");
            if (expirables != null && !expirables.getKeys(false).isEmpty()) {
                timingFileBackup = new File(dataFolder, "timingBackup" + SSTimeUtil.getDateTimeStamp() + ".yml");
                FileUtil.copy(timingFile, timingFileBackup);
                for (String key : expirables.getKeys(false)) {
                    if (key.contains("sshotel")) {
                        Map<String, Object> propertyMap = expirables.getConfigurationSection(key).getValues(true);
                        timing.createSection("expirables." + key.replace("sshotel", "signshophotel"), propertyMap);
                    }
                }
                timing.save(timingFile);
            }
        } catch (InvalidConfigurationException | IOException e) {
            e.printStackTrace();
        }

    }

    private static void convertData(FileConfiguration sellers) {

        try {
            SignShop.log("Converting old data.", Level.INFO);
            ConfigurationSection section = sellers.getConfigurationSection("sellers");
            if (section == null) {
                SignShop.log("There was a problem with the sellers.yml, attempting to fix it. If the problem persists try regenerating the SignShop folder.",Level.WARNING);
                sellers.set("sellers", new ArrayList<>());
            }
            else {
                Set<String> shops = section.getKeys(false);
                for (String shop : shops) {
                    StringBuilder itemPath = new StringBuilder().append("sellers.").append(shop).append(".items");
                    StringBuilder miscPath = new StringBuilder().append("sellers.").append(shop).append(".misc");
                    //Strip old data from items
                    List<String> items = sellers.getStringList(itemPath.toString());
                    ItemStack[] itemStacks = convertOldStringsToItemStacks(items);
                    sellers.set(itemPath.toString(), itemUtil.convertItemStacksToString(itemStacks));
                    //Strip old data from misc
                    List<String> misc = sellers.getStringList(miscPath.toString());
                    if (!misc.isEmpty()) {
                        List<String> newMisc = new ArrayList<>();

                        for (String miscString : misc) {
                            String[] keyPair = miscString.split(":", 2);
                            String key = keyPair.length >= 1 ? keyPair[0] : "";
                            String data = keyPair.length == 2 ? keyPair[1] : "";
                            //This strips the old data if it exists.
                            if (data.contains("|")) {
                                String[] dataPair = data.split("\\|", 2);
                                data = dataPair.length == 2 ? dataPair[1] : dataPair[0];
                            }
                            newMisc.add(key + ":" + data);
                        }
                        sellers.set(miscPath.toString(), newMisc);
                    }
                }
                SignShop.log("Data conversion of " + shops.size() + " shops has finished.", Level.INFO);
            }
            sellers.set("DataVersion", SignShop.DATA_VERSION);
            sellers.save(sellersFile);
        } catch (IOException e) {
            SignShop.log("Error converting data!", Level.WARNING);
        }
    }

    public static ItemStack[] convertOldStringsToItemStacks(List<String> itemStringList) {
        IItemTags itemTags = BookFactory.getItemTags();
        ItemStack[] itemStacks = new ItemStack[itemStringList.size()];
        int invalidItems = 0;

        for (int i = 0; i < itemStringList.size(); i++) {
            try {
                String[] itemProperties = itemStringList.get(i).split(Storage.getItemSeperator());
                if (itemProperties.length < 4) {
                    invalidItems++;
                    continue;
                }

                if (itemProperties.length <= 7) {
                    if (i < (itemStringList.size() - 1) && itemStringList.get(i + 1).split(Storage.getItemSeperator()).length < 4) {
                        // Bug detected, the next item will be the base64 string belonging to the current item
                        // This bug will be fixed at the next save as the ~ will be replaced with a |
                        itemProperties = (itemStringList.get(i) + "|" + itemStringList.get(i + 1)).split(Storage.getItemSeperator());
                    }
                }

                if (itemProperties.length > 7) {
                    String base64prop = itemProperties[7];
                    // The ~ and | are used to differentiate between the old NBTLib and the BukkitSerialization
                    if (base64prop != null && (base64prop.startsWith("~") || base64prop.startsWith("|"))) {
                        String joined = itemUtil.Join(itemProperties, 7).substring(1);

                        ItemStack[] convertedStacks = BukkitSerialization.itemStackArrayFromBase64(joined);
                        if (convertedStacks.length > 0 && convertedStacks[0] != null) {
                            itemStacks[i] = convertedStacks[0];
                        }
                    }
                }

                if (itemStacks[i] == null) {
                    itemStacks[i] = itemTags.getCraftItemstack(
                            Material.getMaterial(itemProperties[1]),
                            Integer.parseInt(itemProperties[0]),
                            Short.parseShort(itemProperties[2])
                    );
                    itemStacks[i].getData().setData(new Byte(itemProperties[3]));

                    if (itemProperties.length > 4)
                        itemUtil.safelyAddEnchantments(itemStacks[i], signshopUtil.convertStringToEnchantments(itemProperties[4]));
                }

                if (itemProperties.length > 5) {
                    try {
                        itemStacks[i] = SignShopBooks.addBooksProps(itemStacks[i], Integer.parseInt(itemProperties[5]));
                    } catch (NumberFormatException ignored) {

                    }
                }
                if (itemProperties.length > 6) {
                    try {
                        SignShopItemMeta.setMetaForID(itemStacks[i], Integer.parseInt(itemProperties[6]));
                    } catch (NumberFormatException ignored) {

                    }
                }
            } catch (Exception ignored) {

            }
        }

        if (invalidItems > 0) {
            ItemStack[] temp = new ItemStack[itemStringList.size() - invalidItems];
            int counter = 0;
            for (ItemStack i : itemStacks) {
                if (i != null) {
                    temp[counter] = i;
                    counter++;
                }
            }

            itemStacks = temp;
        }


        return itemStacks;
    }

    //Probably won't need this but saving it anyway.
    public static String[] convertItemStacksToOldString(ItemStack[] itemStackArray) {
        List<String> itemStringList = new ArrayList<>();
        if (itemStackArray == null)
            return new String[1];

        ItemStack currentItemStack;
        for (ItemStack itemStack : itemStackArray) {
            if (itemStack != null) {
                currentItemStack = itemStack;
                String ID = "";
                if (itemUtil.isWriteableBook(currentItemStack))
                    ID = SignShopBooks.getBookID(currentItemStack).toString();
                String metaID = SignShopItemMeta.getMetaID(currentItemStack).toString();
                if (metaID.equals("-1"))
                    metaID = "";
                ItemStack[] stacks = new ItemStack[1];
                stacks[0] = currentItemStack;

                itemStringList.add(BukkitSerialization.itemStackArrayToBase64(stacks));

                itemStringList.add((currentItemStack.getAmount() + Storage.getItemSeperator()
                        + currentItemStack.getType() + Storage.getItemSeperator()
                        + currentItemStack.getDurability() + Storage.getItemSeperator()
                        + currentItemStack.getData().getData() + Storage.getItemSeperator()
                        + signshopUtil.convertEnchantmentsToString(currentItemStack.getEnchantments()) + Storage.getItemSeperator()
                        + ID + Storage.getItemSeperator()
                        + metaID + Storage.getItemSeperator()
                        + "|" + BukkitSerialization.itemStackArrayToBase64(stacks)));
            }

        }
        String[] items = new String[itemStringList.size()];
        itemStringList.toArray(items);
        return items;
    }
}


<|start_filename|>src/main/java/org/wargamer2010/signshop/configuration/SignShopConfig.java<|end_filename|>
package org.wargamer2010.signshop.configuration;

import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.wargamer2010.signshop.SignShop;
import org.wargamer2010.signshop.hooks.HookManager;
import org.wargamer2010.signshop.operations.SignShopOperation;
import org.wargamer2010.signshop.operations.SignShopOperationListItem;
import org.wargamer2010.signshop.operations.runCommand;
import org.wargamer2010.signshop.specialops.*;
import org.wargamer2010.signshop.util.signshopUtil;

import java.io.*;
import java.util.*;
import java.util.logging.Level;

public class SignShopConfig {
    public static final String configFilename = "config.yml";
    private static final String defaultOPPackage = "org.wargamer2010.signshop.operations";
    private static final Map<String, SignShopOperation> OperationInstances = new LinkedHashMap<>();
    private static final List<SignShopSpecialOp> SpecialsOps = new LinkedList<>();
    private static final String baseLanguage = "en_US";
    private static Map<String, String> OperationAliases;                         // Alias <-> Original
    private static Map<String, Map<String, HashMap<String, String>>> Messages;
    private static Map<String, Map<String, String>> Errors;
    private static List<Material> BlacklistedItems;
    private static Map<String, HashMap<String, Double>> PriceMultipliers;
    private static Map<String, List<String>> Commands;
    private static Map<String, List<String>> DelayedCommands;
    private static Map<String, Integer> ShopLimits;
    private static List<LinkableMaterial> LinkableMaterials;
    private static Map<String, List<String>> Operations = new HashMap<>();
    private static SignShop instance = null;
    private static Map<String, SignShopOperation> ExternalOperations = new HashMap<>();
    //Configurables
    private static FileConfiguration config;
    private static int ConfigVersionDoNotTouch = 3;
    private static int MaxSellDistance = 0;
    private static int MaxShopsPerPerson = 0;
    private static int ShopCooldown = 0;
    private static int MessageCooldown = 0;
    private static int ChunkLoadRadius = 2;
    private static int MaxChestsPerShop = 100;
    private static boolean TransactionLog = false;
    private static boolean Debugging = false;
    private static boolean MetricsEnabled = true;
    private static boolean OPOverride = true;
    private static boolean AllowUnsafeEnchantments = false;
    private static boolean AllowVariableAmounts = false;
    private static boolean AllowEnchantedRepair = true;
    private static boolean DisableEssentialsSigns = true;
    private static boolean AllowMultiWorldShops = true;
    private static boolean EnablePermits = false;
    private static boolean PreventVillagerTrade = false;
    private static boolean ProtectShopsInCreative = true;
    private static boolean ProtectShopsFromExplosions = true;
    private static boolean fixIncompleteOperations = true;
    private static boolean EnablePriceFromWorth = false;
    private static boolean EnableDynmapSupport = false;
    private static boolean EnableTutorialMessages = true;
    private static boolean EnableShopPlotSupport = true;
    private static boolean EnableShopOwnerProtection = true;
    private static boolean EnableAutomaticLock = false;
    private static boolean UseBlacklistAsWhitelist = false;
    private static boolean EnableWrittenBookFix = true;
    private static String ColorCode = "&";
    private static String ChatPrefix = "&6[SignShop]";
    private static ChatColor TextColor = ChatColor.YELLOW;
    private static ChatColor TextColorTwo = ChatColor.DARK_PURPLE;
    private static ChatColor MoneyColor = ChatColor.GREEN;
    private static String Languages = "en_US";
    private static String preferedLanguage = "";
    private static Material linkMaterial = Material.getMaterial("REDSTONE");
    private static Material updateMaterial = Material.getMaterial("INK_SAC");
    private static Material destroyMaterial = Material.getMaterial("GOLDEN_AXE");


    private SignShopConfig() {

    }

    private static List<String> getOrderedListFromArray(String[] array) {
        List<String> list = new LinkedList<>();
        for (String item : array)
            if (!list.contains(item.toLowerCase().trim()))
                list.add(item.toLowerCase().trim());
        return list;
    }


    public static void init() {
        instance = SignShop.getInstance();
        initConfig();
        updateLanguageFileNames();
        String adjustedLanguages = createAdjustedLanguages();
        List<String> aLanguages = getOrderedListFromArray(adjustedLanguages.split(","));
        if (!aLanguages.contains("config"))
            aLanguages.add("config");
        Messages = new LinkedHashMap<>();
        Errors = new LinkedHashMap<>();
        preferedLanguage = "";
        for (String language : aLanguages) {
            language = toLanguageCase(language);
            String filename = (language + ".yml");
            String languageName = (language.equals("config") ? baseLanguage : language);
            copyFileFromJar(filename, false);
            File languageFile = new File(instance.getDataFolder(), filename);
            if (languageFile.exists()) {
                FileConfiguration ymlThing = configUtil.loadYMLFromPluginFolder(filename);
                if (ymlThing != null && !language.equals("config")) {
                    configUtil.loadYMLFromJar(ymlThing, filename);
                }

                Messages.put(languageName, configUtil.fetchHasmapInHashmap("messages", ymlThing));
                if (Messages.get(languageName) == null)
                    continue;
                Errors.put(languageName, configUtil.fetchStringStringHashMap("errors", ymlThing));
                if (Errors.get(languageName) == null)
                    continue;
                if (preferedLanguage.isEmpty())
                    preferedLanguage = languageName;
            }
            else {
                SignShop.log("The languagefile " + languageFile + " for language: " + languageName + " could not be found in the plugin directory!", Level.WARNING);
            }
        }
        if (preferedLanguage.isEmpty())
            preferedLanguage = baseLanguage;
        PriceMultipliers = configUtil.fetchDoubleHasmapInHashmap("pricemultipliers", config);
        Commands = configUtil.fetchListInHashmap("commands", config);
        DelayedCommands = configUtil.fetchListInHashmap("timedCommands", config);
        ShopLimits = configUtil.fetchStringIntegerHashMap("limits", config);
        setupBlacklist();
        copyFileFromJar("SSQuickReference.pdf", true);
        setupOperations();
        fixIncompleOperations();
        setupHooks();
        setupSpecialsOps();
        setupLinkables();
    }

    public static String getPreferredLanguage() {
        return preferedLanguage;
    }

    private static void setupHooks() {

        HookManager.addHook("WorldGuard");
        HookManager.addHook("GriefPrevention");
        HookManager.addHook("Towny");
        HookManager.addHook("BentoBox");
        HookManager.addHook("Residence");
        HookManager.addHook("LWC");
        HookManager.addHook("BlockLocker");
        HookManager.addHook("Lands");

    }

    private static void setupSpecialsOps() {
        SpecialsOps.add(new ConvertChestshop());
        SpecialsOps.add(new CopySign());
        SpecialsOps.add(new LinkSpecialSign());
        SpecialsOps.add(new ChangeOwner());
        SpecialsOps.add(new LinkAdditionalBlocks());
        SpecialsOps.add(new ChangeShopItems());
    }

    private static void safeAddLinkeable(String sName, String sGroup) {
        if (sName == null || sGroup == null || sName.isEmpty())
            return;
        LinkableMaterials.add(new LinkableMaterial(sName, sGroup));
    }

    private static byte getDataFromString(String dur) {
        try {
            return Byte.parseByte(dur);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

    private static void setupLinkables() {
        LinkableMaterials = new ArrayList<>();
        for (Map.Entry<String, String> entry : configUtil.fetchStringStringHashMap("linkableMaterials", config, false).entrySet()) {
            safeAddLinkeable(entry.getKey(), entry.getValue());
        }
    }

    /**
     * Register a material so that it can be linked and used for a shop
     * The Alias is used when checking for DenyLink permission nodes (i.e. DenyLink.door)
     *
     * @param material Material to register
     * @param alias    Alias to use for the given material
     */
    public static void addLinkable(String material, String alias) {
        if (material == null || material.isEmpty())
            return;
        safeAddLinkeable(material.toUpperCase(), alias.toLowerCase());
    }

    private static void initConfig() {
        FileConfiguration ymlThing = configUtil.loadYMLFromPluginFolder(configFilename);
        if (ymlThing == null)
            return;
        configUtil.loadYMLFromJar(ymlThing, configFilename);

        ConfigVersionDoNotTouch = ymlThing.getInt("ConfigVersionDoNotTouch", ConfigVersionDoNotTouch);
        MaxSellDistance = ymlThing.getInt("MaxSellDistance", MaxSellDistance);
        TransactionLog = ymlThing.getBoolean("TransactionLog", TransactionLog);
        Debugging = ymlThing.getBoolean("Debugging", Debugging);
        MetricsEnabled = ymlThing.getBoolean("MetricsEnabled", MetricsEnabled);
        MaxShopsPerPerson = ymlThing.getInt("MaxShopsPerPerson", MaxShopsPerPerson);
        ChunkLoadRadius = ymlThing.getInt("ChunkLoadRadius", ChunkLoadRadius);
        ShopCooldown = ymlThing.getInt("ShopCooldownMilliseconds", ShopCooldown);
        MessageCooldown = ymlThing.getInt("MessageCooldownSeconds", MessageCooldown);
        MaxChestsPerShop = ymlThing.getInt("MaxChestsPerShop", MaxChestsPerShop);
        OPOverride = ymlThing.getBoolean("OPOverride", OPOverride);
        AllowVariableAmounts = ymlThing.getBoolean("AllowVariableAmounts", AllowVariableAmounts);
        AllowEnchantedRepair = ymlThing.getBoolean("AllowEnchantedRepair", AllowEnchantedRepair);
        DisableEssentialsSigns = ymlThing.getBoolean("DisableEssentialsSigns", DisableEssentialsSigns);
        AllowUnsafeEnchantments = ymlThing.getBoolean("AllowUnsafeEnchantments", AllowUnsafeEnchantments);
        AllowMultiWorldShops = ymlThing.getBoolean("AllowMultiWorldShops", AllowMultiWorldShops);
        EnablePermits = ymlThing.getBoolean("EnablePermits", EnablePermits);
        PreventVillagerTrade = ymlThing.getBoolean("PreventVillagerTrade", PreventVillagerTrade);
        ProtectShopsInCreative = ymlThing.getBoolean("ProtectShopsInCreative", ProtectShopsInCreative);
        ProtectShopsFromExplosions = ymlThing.getBoolean("ProtectShopsFromExplosions", ProtectShopsFromExplosions);
        fixIncompleteOperations = ymlThing.getBoolean("fixIncompleteOperations", fixIncompleteOperations);
        EnablePriceFromWorth = ymlThing.getBoolean("EnablePriceFromWorth", EnablePriceFromWorth);
        EnableDynmapSupport = ymlThing.getBoolean("EnableDynmapSupport", EnableDynmapSupport);
        EnableTutorialMessages = ymlThing.getBoolean("EnableTutorialMessages", EnableTutorialMessages);
        EnableShopPlotSupport = ymlThing.getBoolean("EnableShopPlotSupport", EnableShopPlotSupport);
        EnableShopOwnerProtection = ymlThing.getBoolean("EnableShopOwnerProtection", EnableShopOwnerProtection);
        EnableAutomaticLock = ymlThing.getBoolean("EnableAutomaticLock", EnableAutomaticLock);
        UseBlacklistAsWhitelist = ymlThing.getBoolean("UseBlacklistAsWhitelist", UseBlacklistAsWhitelist);
        EnableWrittenBookFix = ymlThing.getBoolean("EnableWrittenBookFix", EnableWrittenBookFix);
        ColorCode = ymlThing.getString("ColorCode", ColorCode);
        ChatPrefix = ymlThing.getString("ChatPrefix", ChatPrefix);
        Languages = ymlThing.getString("Languages", Languages);

        linkMaterial = getMaterial(ymlThing.getString("LinkMaterial", "REDSTONE"), Material.getMaterial("REDSTONE"));
        updateMaterial = getMaterial(ymlThing.getString("UpdateMaterial", "INK_SAC"), Material.getMaterial("INK_SAC"));
        destroyMaterial = getMaterial(ymlThing.getString("DestroyMaterial", "GOLDEN_AXE"), Material.getMaterial("GOLDEN_AXE"));

        TextColor = ChatColor.getByChar(ymlThing.getString("ItemColor", "e").replace(ColorCode,""));
        if (TextColor == null) TextColor = ChatColor.YELLOW;

        TextColorTwo = ChatColor.getByChar(ymlThing.getString("ItemColorTwo", "5").replace(ColorCode,""));
        if (TextColorTwo == null) TextColorTwo = ChatColor.DARK_PURPLE;

        MoneyColor = ChatColor.getByChar(ymlThing.getString("MoneyColor", "a").replace(ColorCode,""));
        if (MoneyColor == null) MoneyColor = ChatColor.GREEN;


        // Sanity check
        if (ChunkLoadRadius > 50 || ChunkLoadRadius < 0)
            ChunkLoadRadius = 3;

        config = ymlThing;
    }

    private static Material getMaterial(String mat, Material defaultmat) {
        String name = mat.toUpperCase();
        Material temp = Material.getMaterial(name);
        if (temp == null) {
            if (name.equals("INK_SACK") || name.equals("GOLD_AXE")) {
                SignShop.log("Material called: " + mat + " no longer exists, updating config.yml now.", Level.INFO);
                updateConfigMaterials(name);
            }
            else {
                SignShop.log("Material called: " + mat + " does not exist, please check your config.yml!", Level.WARNING);
            }
            return defaultmat;
        }
        return temp;
    }

    public static void updateConfigMaterials(String name) {
        FileConfiguration ymlThing = configUtil.loadYMLFromPluginFolder(configFilename);
        File configFile = new File(SignShop.getInstance().getDataFolder(), configFilename);
        if (name.equals("INK_SACK") && ymlThing.getString("UpdateMaterial").toUpperCase().equals("INK_SACK")) {
            ymlThing.set("UpdateMaterial", "ink_sac");
            SignShop.log("UpdateMaterial changed successfully from ink_sack to ink_sac in the config.yml", Level.INFO);
            saveConfig(ymlThing, configFile);
        }
        if (name.equals("GOLD_AXE") && ymlThing.getString("DestroyMaterial").toUpperCase().equals("GOLD_AXE")) {
            ymlThing.set("DestroyMaterial", "golden_axe");
            SignShop.log("DestroyMaterial changed successfully from gold_axe to golden_axe in the config.yml", Level.INFO);
            saveConfig(ymlThing, configFile);
        }

    }

    public static void saveConfig(FileConfiguration ymlThing, File file) {
        try {
            ymlThing.save(file);
            configUtil.loadYMLFromJar(ymlThing, configFilename);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private static void setupOperations() {
        setupOperations(configUtil.fetchStringStringHashMap("signs", config));
    }

    public static void setupOperations(Map<String, String> allSignOperations) {
        setupOperations(allSignOperations, defaultOPPackage);
    }

    private static Object getInstance(String fullClassPath) {
        try {
            if (ExternalOperations.containsKey(fullClassPath)) {
                return ExternalOperations.get(fullClassPath);
            }
            Class<?> aclass = Class.forName(fullClassPath);
            return aclass.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException notfound) {
            return null;
        }
    }

    public static void setupOperations(Map<String, String> allSignOperations, String packageName) {
        if (Operations == null)
            Operations = new HashMap<>();

        for (String sKey : allSignOperations.keySet()) {
            boolean failedOp = false;
            List<String> tempCheckedSignOperation = new LinkedList<>();

            for (String tempOperationString : allSignOperations.get(sKey).split(",")) {
                List<String> bits = signshopUtil.getParameters(tempOperationString.trim());
                String op = bits.get(0);
                Object opinstance = getInstance(packageName + "." + op.trim());
                if (opinstance == null) // Retry with default package
                    opinstance = getInstance(defaultOPPackage + "." + op.trim());
                if (opinstance == null) {
                    failedOp = true;
                    break;
                }

                if (opinstance instanceof SignShopOperation) {
                    SignShopConfig.OperationInstances.put(op.trim(), (SignShopOperation) opinstance);
                    tempCheckedSignOperation.add(tempOperationString.trim());
                }
                else {
                    failedOp = true;
                }
            }
            if (!failedOp && !tempCheckedSignOperation.isEmpty())
                Operations.put(sKey.toLowerCase(), tempCheckedSignOperation);
        }

        List<String> aLanguages = getOrderedListFromArray(Languages.split(","));
        aLanguages.remove("english");

        SignShopConfig.OperationAliases = new HashMap<>();

        for (String language : aLanguages) {
            String filename = (language + ".yml");
            File languageFile = new File(instance.getDataFolder(), filename);
            if (languageFile.exists()) {
                FileConfiguration ymlThing = new YamlConfiguration();
                try {
                    ymlThing.load(languageFile);
                } catch (IOException | InvalidConfigurationException ex) {
                    continue;
                }
                HashMap<String, String> tempSignAliases = configUtil.fetchStringStringHashMap("signs", ymlThing);
                for (Map.Entry<String, String> alias : tempSignAliases.entrySet()) {
                    if (Operations.containsKey(alias.getValue().toLowerCase())) {
                        SignShopConfig.OperationAliases.put(alias.getKey().toLowerCase(), alias.getValue().toLowerCase());
                    }
                    else {
                        SignShop.log("Could not find " + alias.getValue() + " to alias as " + alias.getKey(), Level.WARNING);
                    }

                }
            }
        }
    }

    private static String opListToString(List<String> operations) {
        return signshopUtil.implode(operations.toArray(new String[0]), ",");
    }

    private static String fetchCaseCorrectedKey(Map<String, String> map, String lowercased) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(lowercased))
                return entry.getKey();
        }
        return "";
    }

    private static void fixIncompleOperations() {
        if (!fixIncompleteOperations)
            return;
        HashMap<String, String> tempSignOperations = configUtil.fetchStringStringHashMap("signs", config, true);
        boolean changedSomething = false;
        for (Map.Entry<String, List<String>> entry : Operations.entrySet()) {
            if (SignShopConfig.Commands.containsKey(entry.getKey().toLowerCase())) {
                List<SignShopOperationListItem> tempList = signshopUtil.getSignShopOps(entry.getValue());
                boolean found = false;
                for (SignShopOperationListItem tempOp : tempList)
                    if (tempOp.getOperation() instanceof runCommand || tempOp.getParameters().contains("runCommand")) {
                        found = true;
                        break;
                    }
                if (found)
                    continue;
                entry.getValue().add("runCommand");
                String opName = fetchCaseCorrectedKey(tempSignOperations, entry.getKey());
                if (opName.isEmpty())
                    opName = entry.getKey();
                config.set(("signs." + opName), opListToString(entry.getValue()));
                if (!changedSomething)
                    changedSomething = true;
                SignShop.log("Added runCommand block to " + opName + " because it has a corresponding entry in the Commands section. Set fixIncompleteOperations to false to disable this function.", Level.INFO);
            }
        }
        if (changedSomething)
            configUtil.loadYMLFromJar(config, configFilename);
    }

    private static void closeStream(Closeable in) {
        if (in == null)
            return;
        try {
            in.close();
        } catch (IOException ignored) {

        }
    }

    private static void copyFileFromJar(String filename, Boolean delete) {
        InputStream in = SignShopConfig.class.getResourceAsStream("/" + filename);
        File file = new File(instance.getDataFolder(), filename);
        OutputStream os = null;
        if (file.exists() && delete) {
            if (!file.delete()) {
                closeStream(in);
                return;
            }
        }
        else if (file.exists() && !delete) {
            return;
        }
        try {
            if (in.available() == 0)
                return;
            file.createNewFile();
            os = new FileOutputStream(file.getPath());
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException | NullPointerException ignored) {

        }

        closeStream(in);
        closeStream(os);
    }

    public static String getError(String sType, Map<String, String> messageParts) {
        Map<String, String> localisedError = Errors.get(SignShopConfig.preferedLanguage);
        Map<String, String> defaultError = Errors.get(SignShopConfig.baseLanguage);

        String error;
        if (!localisedError.containsKey(sType) || localisedError.get(sType) == null) {
            if (!defaultError.containsKey(sType) || defaultError.get(sType) == null)
                return "";
            else
                error = defaultError.get(sType);
        }
        else
            error = localisedError.get(sType);
        return fillInBlanks(error, messageParts);
    }

    public static String getMessage(String sType, String pOperation, Map<String, String> messageParts) {
        Map<String, HashMap<String, String>> localisedMessage = Messages.get(SignShopConfig.preferedLanguage);
        Map<String, HashMap<String, String>> defaultMessage = Messages.get(SignShopConfig.baseLanguage);

        String sOperation = pOperation;
        if (OperationAliases.containsKey(sOperation))
            sOperation = OperationAliases.get(sOperation);

        String message;
        if (!localisedMessage.containsKey(sType) || !localisedMessage.get(sType).containsKey(sOperation) || localisedMessage.get(sType).get(sOperation) == null) {
            if (!defaultMessage.containsKey(sType) || !defaultMessage.get(sType).containsKey(sOperation) || defaultMessage.get(sType).get(sOperation) == null) {
                return "";
            }
            else
                message = defaultMessage.get(sType).get(sOperation);
        }
        else
            message = localisedMessage.get(sType).get(sOperation);

        return fillInBlanks(message, messageParts);
    }

    public static List<String> getBlocks(String pOp) {
        String op = pOp;
        if (OperationAliases.containsKey(op))
            op = OperationAliases.get(op);

        if (Operations.containsKey(op))
            return Operations.get(op);
        else
            return new LinkedList<>();
    }

    public static Collection<String> getOperations() {
        return Collections.unmodifiableCollection(Operations.keySet());
    }

    public static Collection<String> getAliases(String op) {
        Collection<String> aliases = new LinkedList<>();
        if (Languages.contains(baseLanguage))
            aliases.add(op); // If the baseLanguage is explicitly requested, we'll add the english OP as an alias
        for (Map.Entry<String, String> entry : OperationAliases.entrySet()) {
            if (entry.getValue().equals(op))
                aliases.add(entry.getKey());
        }
        return aliases;
    }

    public static void registerExternalOperation(SignShopOperation signShopOperation) {
        ExternalOperations.put(signShopOperation.getClass().getName(), signShopOperation);
    }

    public static boolean registerOperation(String sName, List<String> blocks) {
        if (sName != null && blocks != null && !blocks.isEmpty()) {
            Operations.put(sName, blocks);
            return true;
        }
        return false;
    }

    public static boolean registerMessage(String type, String shop, String message) {
        return registerMessage(SignShopConfig.baseLanguage, type, shop, message);
    }

    public static boolean registerMessage(String language, String type, String shop, String message) {
        if (Messages.containsKey(language) && Messages.get(language).containsKey(type)) {
            HashMap<String, String> temp = Messages.get(language).get(type);
            temp.put(shop, message);
            return true;
        }
        else {
            return false;
        }
    }

    public static boolean registerMessages(String type, Map<String, String> messagesByShop) {
        return registerMessages(SignShopConfig.baseLanguage, type, messagesByShop);
    }

    public static boolean registerMessages(String language, String type, Map<String, String> messagesByShop) {
        if (Messages.containsKey(language) && Messages.get(language).containsKey(type)) {
            HashMap<String, String> temp = Messages.get(language).get(type);
            temp.putAll(messagesByShop);
            return true;
        }
        else {
            return false;
        }
    }

    public static boolean registerErrorMessage(String type, String message) {
        return registerErrorMessage(SignShopConfig.baseLanguage, type, message);
    }

    public static boolean registerErrorMessage(String language, String type, String message) {
        if (Messages.containsKey(language)) {
            Map<String, String> temp = Errors.get(language);
            temp.put(type, message);
            return true;
        }
        else {
            return false;
        }
    }

    public static boolean registerErrorMessages(Map<String, String> messagesByType) {
        return registerErrorMessages(SignShopConfig.baseLanguage, messagesByType);
    }

    public static boolean registerErrorMessages(String language, Map<String, String> messagesByType) {
        if (Messages.containsKey(language)) {
            Errors.get(language).putAll(messagesByType);
            return true;
        }
        else {
            return false;
        }
    }

    public static String fillInBlanks(String pMessage, Map<String, String> messageParts) {
        String message = pMessage;

        if (messageParts == null)
            return message;

        TreeMap<String, String> temp = new TreeMap<>(new StringLengthComparator());
        temp.putAll(messageParts);

        for (Map.Entry<String, String> part : temp.entrySet()) {
            if (part != null && part.getKey() != null && part.getValue() != null)
                message = message.replace(part.getKey(), part.getValue());
        }
        message = message.replace("\\", "");
        return message;
    }

    private static void setupBlacklist() {
        List<String> tempList = config.getStringList("Blacklisted_items");
        BlacklistedItems = new LinkedList<>();
        for (String item : tempList) {
            Material mat = Material.getMaterial(item.toUpperCase());
            if (mat != null)
                BlacklistedItems.add(mat);
            else
                SignShop.log("Material called: " + item + " could not be added to the blacklist as it does not exist, please check your config.yml!", Level.WARNING);
        }
    }

    public static Boolean isItemOnBlacklist(Material mat) {
        return (
                (SignShopConfig.BlacklistedItems.contains(mat) && !SignShopConfig.getUseBlacklistAsWhitelist())
                        ||
                        (!SignShopConfig.BlacklistedItems.contains(mat) && SignShopConfig.getUseBlacklistAsWhitelist())
        );
    }

    public static ItemStack isAnyItemOnBlacklist(ItemStack[] stacks) {
        if (stacks == null)
            return null;
        for (ItemStack single : stacks) {
            if (single == null)
                continue;
            if (isItemOnBlacklist(single.getType())) {
                return single;
            }
        }
        return null;
    }

    public static int getMaxChestsPerShop() {
        return MaxChestsPerShop;
    }

    public static Boolean ExceedsMaxChestsPerShop(int currentAmountOfChests) {
        return MaxChestsPerShop != 0 && currentAmountOfChests > MaxChestsPerShop;
    }

    public static Map<String, SignShopOperation> getOperationInstances() {
        return Collections.unmodifiableMap(OperationInstances);
    }

    public static Map<String, HashMap<String, Double>> getPriceMultipliers() {
        return Collections.unmodifiableMap(PriceMultipliers);
    }

    public static Map<String, List<String>> getCommands() {
        return Collections.unmodifiableMap(Commands);
    }

    public static Map<String, List<String>> getDelayedCommands() {
        return Collections.unmodifiableMap(DelayedCommands);
    }

    public static Map<String, Integer> getShopLimits() {
        return Collections.unmodifiableMap(ShopLimits);
    }

    public static List<LinkableMaterial> getLinkableMaterials() {
        return Collections.unmodifiableList(LinkableMaterials);
    }

    public static List<SignShopSpecialOp> getSpecialOps() {
        return Collections.unmodifiableList(SpecialsOps);
    }

    public static int getConfigVersionDoNotTouch() {
        return ConfigVersionDoNotTouch;
    }

    public static int getMaxSellDistance() {
        return MaxSellDistance;
    }

    public static int getMaxShopsPerPerson() {
        return MaxShopsPerPerson;
    }

    public static int getShopCooldown() {
        return ShopCooldown;
    }

    public static int getChunkLoadRadius() {
        return ChunkLoadRadius;
    }

    public static int getMessageCooldown() {
        return MessageCooldown;
    }

    public static boolean getOPOverride() {
        return OPOverride;
    }

    public static boolean getAllowVariableAmounts() {
        return AllowVariableAmounts;
    }

    public static boolean getAllowEnchantedRepair() {
        return AllowEnchantedRepair;
    }

    public static boolean getAllowUnsafeEnchantments() {
        return AllowUnsafeEnchantments;
    }

    public static boolean getAllowMultiWorldShops() {
        return AllowMultiWorldShops;
    }

    public static boolean getEnablePermits() {
        return EnablePermits;
    }

    public static boolean getPreventVillagerTrade() {
        return PreventVillagerTrade;
    }

    public static boolean getProtectShopsInCreative() {
        return ProtectShopsInCreative;
    }

    public static boolean getProtectShopsFromExplosions() {
        return ProtectShopsFromExplosions;
    }

    public static boolean getTransactionLog() {
        return TransactionLog;
    }

    public static boolean getDisableEssentialsSigns() {
        return DisableEssentialsSigns;
    }

    public static boolean getEnablePriceFromWorth() {
        return EnablePriceFromWorth;
    }

    public static boolean getEnableDynmapSupport() {
        return EnableDynmapSupport;
    }

    public static boolean getEnableTutorialMessages() {
        return EnableTutorialMessages;
    }

    public static boolean getEnableShopPlotSupport() {
        return EnableShopPlotSupport;
    }

    public static boolean getEnableShopOwnerProtection() {
        return EnableShopOwnerProtection;
    }


    public static boolean getEnableAutomaticLock() {
        return EnableAutomaticLock;
    }

    public static boolean getUseBlacklistAsWhitelist() {
        return UseBlacklistAsWhitelist;
    }

    public static boolean getEnableWrittenBookFix() {
        return EnableWrittenBookFix;
    }

    public static boolean debugging() {
        return Debugging;
    }

    public static boolean metricsEnabled() {
        return MetricsEnabled;
    }

    public static boolean isOPMaterial(Material check) {
        return (check == updateMaterial || check == linkMaterial);
    }

    public static Material getLinkMaterial() {
        return linkMaterial;
    }

    public static Material getUpdateMaterial() {
        return updateMaterial;
    }

    public static Material getDestroyMaterial() {
        return destroyMaterial;
    }

    public static String getChatPrefix() {
        return ChatPrefix;
    }

    public static char getColorCode() {
        char[] code = ColorCode.toCharArray();
        return code[0];
    }
    public static ChatColor getTextColor() {
        return TextColor;
    }

    public static ChatColor getTextColorTwo() {
        return TextColorTwo;
    }

    public static ChatColor getMoneyColor() {
        return MoneyColor;
    }

    private static String toLanguageCase(String language){
        String[] languageParts = language.split("_");
        if (languageParts.length ==2){
            return (languageParts[0].toLowerCase()+"_"+languageParts[1].toUpperCase());
        }
        return language;
    }

    private static String createAdjustedLanguages() {
        String adjustedLanguages = Languages;
        for (LanguageSpelling languageSpelling : LanguageSpelling.values()) {
            adjustedLanguages = adjustedLanguages.replace(languageSpelling.oldName, languageSpelling.localeName);
        }
        adjustedLanguages = adjustedLanguages.replace("en_US","config");
        return adjustedLanguages;

    }

    private static void updateLanguageFileNames() {
        for (LanguageSpelling languageSpelling : LanguageSpelling.values()) {
            File oldLangFile = new File(instance.getDataFolder(), languageSpelling.oldName + ".yml");
            if (oldLangFile.exists()) {
                SignShop.log("Renaming deprecated language filename '" + languageSpelling.oldName + ".yml' to '" + languageSpelling.localeName + ".yml'.", Level.INFO);
                oldLangFile.renameTo(new File(instance.getDataFolder(), languageSpelling.localeName + ".yml"));

            }
        }
    }




    private enum LanguageSpelling {
        ENGLISH("english", "config"),
        CHINESE("chinese", "zh_TW"),
        DUTCH("dutch", "nl_NL"),
        FRENCH("french", "fr_FR"),
        GERMAN("german", "de_DE"),
        PORTUGUESE("portuguese", "pt_PT"),
        RUSSIAN("russian", "ru_RU"),
        SPANISH("spanish", "es_ES");

        String oldName;
        String localeName;

        LanguageSpelling(String oldName, String localeName) {
            this.oldName = oldName;
            this.localeName = localeName;
        }
    }

    /**
     * Orders strings by their length from long to short
     */
    private static class StringLengthComparator implements Comparator<String> {
        @SuppressWarnings("ComparatorMethodParameterNotUsed")
        @Override
        public int compare(String s1, String s2) {
            int s2Length = s2.length();
            int s1Length = s1.length();
            if (s2Length == s1Length)
                return -1;
            return s2Length - s1Length;
        }
    }
}
