    001    /**
    002     * This software is provided under the terms of the Minecraft Forge Public
    003     * License v1.0.
    004     */
    005
    006    package net.minecraftforge.common;
    007
    008    import java.io.*;
    009    import java.text.DateFormat;
    010    import java.util.ArrayList;
    011    import java.util.Arrays;
    012    import java.util.Collection;
    013    import java.util.Date;
    014    import java.util.Locale;
    015    import java.util.Map;
    016    import java.util.TreeMap;
    017    import java.util.regex.Matcher;
    018    import java.util.regex.Pattern;
    019
    020    import com.google.common.base.CharMatcher;
    021    import com.google.common.base.Splitter;
    022    import com.google.common.collect.Maps;
    023
    024    import cpw.mods.fml.common.FMLCommonHandler;
    025    import cpw.mods.fml.common.FMLLog;
    026    import cpw.mods.fml.common.Loader;
    027    import cpw.mods.fml.relauncher.FMLInjectionData;
    028
    029    import net.minecraft.block.Block;
    030    import net.minecraft.item.Item;
    031    import static net.minecraftforge.common.Property.Type.*;
    032
    033    /**
    034     * This class offers advanced configurations capabilities, allowing to provide
    035     * various categories for configuration variables.
    036     */
    037    public class Configuration
    038    {
    039        private static boolean[] configMarkers = new boolean[Item.itemsList.length];
    040        private static final int ITEM_SHIFT = 256;
    041        private static final int MAX_BLOCKS = 4096;
    042
    043        public static final String CATEGORY_GENERAL = "general";
    044        public static final String CATEGORY_BLOCK   = "block";
    045        public static final String CATEGORY_ITEM    = "item";
    046        public static final String ALLOWED_CHARS = "._-";
    047        public static final String DEFAULT_ENCODING = "UTF-8";
    048        public static final String CATEGORY_SPLITTER = ".";
    049        public static final String NEW_LINE;
    050        private static final Pattern CONFIG_START = Pattern.compile("START: \"([^\\\"]+)\"");
    051        private static final Pattern CONFIG_END = Pattern.compile("END: \"([^\\\"]+)\"");
    052        public static final CharMatcher allowedProperties = CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.anyOf(ALLOWED_CHARS));
    053        private static Configuration PARENT = null;
    054
    055        File file;
    056
    057        public Map<String, ConfigCategory> categories = new TreeMap<String, ConfigCategory>();
    058        private Map<String, Configuration> children = new TreeMap<String, Configuration>();
    059
    060        private boolean caseSensitiveCustomCategories;
    061        public String defaultEncoding = DEFAULT_ENCODING;
    062        private String fileName = null;
    063        public boolean isChild = false;
    064
    065        static
    066        {
    067            Arrays.fill(configMarkers, false);
    068            NEW_LINE = System.getProperty("line.separator");
    069        }
    070
    071        public Configuration(){}
    072
    073        /**
    074         * Create a configuration file for the file given in parameter.
    075         */
    076        public Configuration(File file)
    077        {
    078            this.file = file;
    079            String basePath = ((File)(FMLInjectionData.data()[6])).getAbsolutePath().replace(File.separatorChar, '/').replace("/.", "");
    080            String path = file.getAbsolutePath().replace(File.separatorChar, '/').replace("/./", "/").replace(basePath, "");
    081            if (PARENT != null)
    082            {
    083                PARENT.setChild(path, this);
    084                isChild = true;
    085            }
    086            else
    087            {
    088                fileName = path;
    089                load();
    090            }
    091        }
    092
    093        public Configuration(File file, boolean caseSensitiveCustomCategories)
    094        {
    095            this(file);
    096            this.caseSensitiveCustomCategories = caseSensitiveCustomCategories;
    097        }
    098
    099        /**
    100         * Gets or create a block id property. If the block id property key is
    101         * already in the configuration, then it will be used. Otherwise,
    102         * defaultId will be used, except if already taken, in which case this
    103         * will try to determine a free default id.
    104         */
    105        public Property getBlock(String key, int defaultID) { return getBlock(CATEGORY_BLOCK, key, defaultID, null); }
    106        public Property getBlock(String key, int defaultID, String comment) { return getBlock(CATEGORY_BLOCK, key, defaultID, comment); }
    107        public Property getBlock(String category, String key, int defaultID) { return getBlockInternal(category, key, defaultID, null, 256, Block.blocksList.length); }
    108        public Property getBlock(String category, String key, int defaultID, String comment) { return getBlockInternal(category, key, defaultID, comment, 256, Block.blocksList.length); }
    109
    110        /**
    111         * Special version of getBlock to be used when you want to garentee the ID you get is below 256
    112         * This should ONLY be used by mods who do low level terrain generation, or ones that add new
    113         * biomes.
    114         * EXA: ExtraBiomesXL
    115         *
    116         * Specifically, if your block is used BEFORE the Chunk is created, and placed in the terrain byte array directly.
    117         * If you add a new biome and you set the top/filler block, they need to be <256, nothing else.
    118         *
    119         * If you're adding a new ore, DON'T call this function.
    120         *
    121         * Normal mods such as '50 new ores' do not need to be below 256 so should use the normal getBlock
    122         */
    123        public Property getTerrainBlock(String category, String key, int defaultID, String comment)
    124        {
    125            return getBlockInternal(category, key, defaultID, comment, 0, 256);
    126        }
    127
    128        private Property getBlockInternal(String category, String key, int defaultID, String comment, int lower, int upper)
    129        {
    130            Property prop = get(category, key, -1, comment);
    131
    132            if (prop.getInt() != -1)
    133            {
    134                configMarkers[prop.getInt()] = true;
    135                return prop;
    136            }
    137            else
    138            {
    139                if (defaultID < lower)
    140                {
    141                    FMLLog.warning(
    142                        "Mod attempted to get a block ID with a default in the Terrain Generation section, " +
    143                        "mod authors should make sure there defaults are above 256 unless explicitly needed " +
    144                        "for terrain generation. Most ores do not need to be below 256.");
    145                    FMLLog.warning("Config \"%s\" Category: \"%s\" Key: \"%s\" Default: %d", fileName, category, key, defaultID);
    146                    defaultID = upper - 1;
    147                }
    148
    149                if (Block.blocksList[defaultID] == null && !configMarkers[defaultID])
    150                {
    151                    prop.value = Integer.toString(defaultID);
    152                    configMarkers[defaultID] = true;
    153                    return prop;
    154                }
    155                else
    156                {
    157                    for (int j = upper - 1; j > 0; j--)
    158                    {
    159                        if (Block.blocksList[j] == null && !configMarkers[j])
    160                        {
    161                            prop.value = Integer.toString(j);
    162                            configMarkers[j] = true;
    163                            return prop;
    164                        }
    165                    }
    166
    167                    throw new RuntimeException("No more block ids available for " + key);
    168                }
    169            }
    170        }
    171
    172        public Property getItem(String key, int defaultID) { return getItem(CATEGORY_ITEM, key, defaultID, null); }
    173        public Property getItem(String key, int defaultID, String comment) { return getItem(CATEGORY_ITEM, key, defaultID, comment); }
    174        public Property getItem(String category, String key, int defaultID) { return getItem(category, key, defaultID, null); }
    175
    176        public Property getItem(String category, String key, int defaultID, String comment)
    177        {
    178            Property prop = get(category, key, -1, comment);
    179            int defaultShift = defaultID + ITEM_SHIFT;
    180
    181            if (prop.getInt() != -1)
    182            {
    183                configMarkers[prop.getInt() + ITEM_SHIFT] = true;
    184                return prop;
    185            }
    186            else
    187            {
    188                if (defaultID < MAX_BLOCKS - ITEM_SHIFT)
    189                {
    190                    FMLLog.warning(
    191                        "Mod attempted to get a item ID with a default value in the block ID section, " +
    192                        "mod authors should make sure there defaults are above %d unless explicitly needed " +
    193                        "so that all block ids are free to store blocks.", MAX_BLOCKS - ITEM_SHIFT);
    194                    FMLLog.warning("Config \"%s\" Category: \"%s\" Key: \"%s\" Default: %d", fileName, category, key, defaultID);
    195                }
    196
    197                if (Item.itemsList[defaultShift] == null && !configMarkers[defaultShift] && defaultShift > Block.blocksList.length)
    198                {
    199                    prop.value = Integer.toString(defaultID);
    200                    configMarkers[defaultShift] = true;
    201                    return prop;
    202                }
    203                else
    204                {
    205                    for (int x = Item.itemsList.length - 1; x >= ITEM_SHIFT; x--)
    206                    {
    207                        if (Item.itemsList[x] == null && !configMarkers[x])
    208                        {
    209                            prop.value = Integer.toString(x - ITEM_SHIFT);
    210                            configMarkers[x] = true;
    211                            return prop;
    212                        }
    213                    }
    214
    215                    throw new RuntimeException("No more item ids available for " + key);
    216                }
    217            }
    218        }
    219
    220        public Property get(String category, String key, int defaultValue)
    221        {
    222            return get(category, key, defaultValue, null);
    223        }
    224
    225        public Property get(String category, String key, int defaultValue, String comment)
    226        {
    227            Property prop = get(category, key, Integer.toString(defaultValue), comment, INTEGER);
    228            if (!prop.isIntValue())
    229            {
    230                prop.value = Integer.toString(defaultValue);
    231            }
    232            return prop;
    233        }
    234
    235        public Property get(String category, String key, boolean defaultValue)
    236        {
    237            return get(category, key, defaultValue, null);
    238        }
    239
    240        public Property get(String category, String key, boolean defaultValue, String comment)
    241        {
    242            Property prop = get(category, key, Boolean.toString(defaultValue), comment, BOOLEAN);
    243            if (!prop.isBooleanValue())
    244            {
    245                prop.value = Boolean.toString(defaultValue);
    246            }
    247            return prop;
    248        }
    249
    250        public Property get(String category, String key, double defaultValue)
    251        {
    252            return get(category, key, defaultValue, null);
    253        }
    254
    255        public Property get(String category, String key, double defaultValue, String comment)
    256        {
    257            Property prop = get(category, key, Double.toString(defaultValue), comment, DOUBLE);
    258            if (!prop.isDoubleValue())
    259            {
    260                prop.value = Double.toString(defaultValue);
    261            }
    262            return prop;
    263        }
    264
    265        public Property get(String category, String key, String defaultValue)
    266        {
    267            return get(category, key, defaultValue, null);
    268        }
    269
    270        public Property get(String category, String key, String defaultValue, String comment)
    271        {
    272            return get(category, key, defaultValue, comment, STRING);
    273        }
    274
    275        public Property get(String category, String key, String[] defaultValue)
    276        {
    277            return get(category, key, defaultValue, null);
    278        }
    279
    280        public Property get(String category, String key, String[] defaultValue, String comment)
    281        {
    282            return get(category, key, defaultValue, comment, STRING);
    283        }
    284
    285        public Property get(String category, String key, int[] defaultValue)
    286        {
    287            return get(category, key, defaultValue, null);
    288        }
    289
    290        public Property get(String category, String key, int[] defaultValue, String comment)
    291        {
    292            String[] values = new String[defaultValue.length];
    293            for (int i = 0; i < defaultValue.length; i++)
    294            {
    295                values[i] = Integer.toString(defaultValue[i]);
    296            }
    297
    298            Property prop =  get(category, key, values, comment, INTEGER);
    299            if (!prop.isIntList())
    300            {
    301                prop.valueList = values;
    302            }
    303
    304            return prop;
    305        }
    306
    307        public Property get(String category, String key, double[] defaultValue)
    308        {
    309            return get(category, key, defaultValue, null);
    310        }
    311
    312        public Property get(String category, String key, double[] defaultValue, String comment)
    313        {
    314            String[] values = new String[defaultValue.length];
    315            for (int i = 0; i < defaultValue.length; i++)
    316            {
    317                values[i] = Double.toString(defaultValue[i]);
    318            }
    319
    320            Property prop =  get(category, key, values, comment, DOUBLE);
    321
    322            if (!prop.isDoubleList())
    323            {
    324                prop.valueList = values;
    325            }
    326
    327            return prop;
    328        }
    329
    330        public Property get(String category, String key, boolean[] defaultValue)
    331        {
    332            return get(category, key, defaultValue, null);
    333        }
    334
    335        public Property get(String category, String key, boolean[] defaultValue, String comment)
    336        {
    337            String[] values = new String[defaultValue.length];
    338            for (int i = 0; i < defaultValue.length; i++)
    339            {
    340                values[i] = Boolean.toString(defaultValue[i]);
    341            }
    342
    343            Property prop =  get(category, key, values, comment, BOOLEAN);
    344
    345            if (!prop.isBooleanList())
    346            {
    347                prop.valueList = values;
    348            }
    349
    350            return prop;
    351        }
    352
    353        public Property get(String category, String key, String defaultValue, String comment, Property.Type type)
    354        {
    355            if (!caseSensitiveCustomCategories)
    356            {
    357                category = category.toLowerCase(Locale.ENGLISH);
    358            }
    359
    360            ConfigCategory cat = getCategory(category);
    361
    362            if (cat.containsKey(key))
    363            {
    364                Property prop = cat.get(key);
    365
    366                if (prop.getType() == null)
    367                {
    368                    prop = new Property(prop.getName(), prop.value, type);
    369                    cat.set(key, prop);
    370                }
    371
    372                prop.comment = comment;
    373                return prop;
    374            }
    375            else if (defaultValue != null)
    376            {
    377                Property prop = new Property(key, defaultValue, type);
    378                cat.set(key, prop);
    379                prop.comment = comment;
    380                return prop;
    381            }
    382            else
    383            {
    384                return null;
    385            }
    386        }
    387
    388        public Property get(String category, String key, String[] defaultValue, String comment, Property.Type type)
    389        {
    390            if (!caseSensitiveCustomCategories)
    391            {
    392                category = category.toLowerCase(Locale.ENGLISH);
    393            }
    394
    395            ConfigCategory cat = getCategory(category);
    396
    397            if (cat.containsKey(key))
    398            {
    399                Property prop = cat.get(key);
    400
    401                if (prop.getType() == null)
    402                {
    403                    prop = new Property(prop.getName(), prop.value, type);
    404                    cat.set(key, prop);
    405                }
    406
    407                prop.comment = comment;
    408
    409                return prop;
    410            }
    411            else if (defaultValue != null)
    412            {
    413                Property prop = new Property(key, defaultValue, type);
    414                prop.comment = comment;
    415                cat.set(key, prop);
    416                return prop;
    417            }
    418            else
    419            {
    420                return null;
    421            }
    422        }
    423
    424        public boolean hasCategory(String category)
    425        {
    426            return categories.get(category) != null;
    427        }
    428
    429        public boolean hasKey(String category, String key)
    430        {
    431            ConfigCategory cat = categories.get(category);
    432            return cat != null && cat.containsKey(key);
    433        }
    434
    435        public void load()
    436        {
    437            if (PARENT != null && PARENT != this)
    438            {
    439                return;
    440            }
    441
    442            BufferedReader buffer = null;
    443            try
    444            {
    445                if (file.getParentFile() != null)
    446                {
    447                    file.getParentFile().mkdirs();
    448                }
    449
    450                if (!file.exists() && !file.createNewFile())
    451                {
    452                    return;
    453                }
    454
    455                if (file.canRead())
    456                {
    457                    UnicodeInputStreamReader input = new UnicodeInputStreamReader(new FileInputStream(file), defaultEncoding);
    458                    defaultEncoding = input.getEncoding();
    459                    buffer = new BufferedReader(input);
    460
    461                    String line;
    462                    ConfigCategory currentCat = null;
    463                    Property.Type type = null;
    464                    ArrayList<String> tmpList = null;
    465                    int lineNum = 0;
    466                    String name = null;
    467
    468                    while (true)
    469                    {
    470                        lineNum++;
    471                        line = buffer.readLine();
    472
    473                        if (line == null)
    474                        {
    475                            break;
    476                        }
    477
    478                        Matcher start = CONFIG_START.matcher(line);
    479                        Matcher end = CONFIG_END.matcher(line);
    480
    481                        if (start.matches())
    482                        {
    483                            fileName = start.group(1);
    484                            categories = new TreeMap<String, ConfigCategory>();
    485                            continue;
    486                        }
    487                        else if (end.matches())
    488                        {
    489                            fileName = end.group(1);
    490                            Configuration child = new Configuration();
    491                            child.categories = categories;
    492                            this.children.put(fileName, child);
    493                            continue;
    494                        }
    495
    496                        int nameStart = -1, nameEnd = -1;
    497                        boolean skip = false;
    498                        boolean quoted = false;
    499
    500                        for (int i = 0; i < line.length() && !skip; ++i)
    501                        {
    502                            if (Character.isLetterOrDigit(line.charAt(i)) || ALLOWED_CHARS.indexOf(line.charAt(i)) != -1 || (quoted && line.charAt(i) != '"'))
    503                            {
    504                                if (nameStart == -1)
    505                                {
    506                                    nameStart = i;
    507                                }
    508
    509                                nameEnd = i;
    510                            }
    511                            else if (Character.isWhitespace(line.charAt(i)))
    512                            {
    513                                // ignore space charaters
    514                            }
    515                            else
    516                            {
    517                                switch (line.charAt(i))
    518                                {
    519                                    case '#':
    520                                        skip = true;
    521                                        continue;
    522
    523                                    case '"':
    524                                        if (quoted)
    525                                        {
    526                                            quoted = false;
    527                                        }
    528                                        if (!quoted && nameStart == -1)
    529                                        {
    530                                            quoted = true;
    531                                        }
    532                                        break;
    533
    534                                    case '{':
    535                                        name = line.substring(nameStart, nameEnd + 1);
    536                                        String qualifiedName = ConfigCategory.getQualifiedName(name, currentCat);
    537
    538                                        ConfigCategory cat = categories.get(qualifiedName);
    539                                        if (cat == null)
    540                                        {
    541                                            currentCat = new ConfigCategory(name, currentCat);
    542                                            categories.put(qualifiedName, currentCat);
    543                                        }
    544                                        else
    545                                        {
    546                                            currentCat = cat;
    547                                        }
    548                                        name = null;
    549
    550                                        break;
    551
    552                                    case '}':
    553                                        if (currentCat == null)
    554                                        {
    555                                            throw new RuntimeException(String.format("Config file corrupt, attepted to close to many categories '%s:%d'", fileName, lineNum));
    556                                        }
    557                                        currentCat = currentCat.parent;
    558                                        break;
    559
    560                                    case '=':
    561                                        name = line.substring(nameStart, nameEnd + 1);
    562
    563                                        if (currentCat == null)
    564                                        {
    565                                            throw new RuntimeException(String.format("'%s' has no scope in '%s:%d'", name, fileName, lineNum));
    566                                        }
    567
    568                                        Property prop = new Property(name, line.substring(i + 1), type, true);
    569                                        i = line.length();
    570
    571                                        currentCat.set(name, prop);
    572
    573                                        break;
    574
    575                                    case ':':
    576                                        type = Property.Type.tryParse(line.substring(nameStart, nameEnd + 1).charAt(0));
    577                                        nameStart = nameEnd = -1;
    578                                        break;
    579
    580                                    case '<':
    581                                        if (tmpList != null)
    582                                        {
    583                                            throw new RuntimeException(String.format("Malformed list property \"%s:%d\"", fileName, lineNum));
    584                                        }
    585
    586                                        name = line.substring(nameStart, nameEnd + 1);
    587
    588                                        if (currentCat == null)
    589                                        {
    590                                            throw new RuntimeException(String.format("'%s' has no scope in '%s:%d'", name, fileName, lineNum));
    591                                        }
    592
    593                                        tmpList = new ArrayList<String>();
    594
    595                                        skip = true;
    596
    597                                        break;
    598
    599                                    case '>':
    600                                        if (tmpList == null)
    601                                        {
    602                                            throw new RuntimeException(String.format("Malformed list property \"%s:%d\"", fileName, lineNum));
    603                                        }
    604
    605                                        currentCat.set(name, new Property(name, tmpList.toArray(new String[tmpList.size()]), type));
    606                                        name = null;
    607                                        tmpList = null;
    608                                        type = null;
    609                                        break;
    610
    611                                    default:
    612                                        throw new RuntimeException(String.format("Unknown character '%s' in '%s:%d'", line.charAt(i), fileName, lineNum));
    613                                }
    614                            }
    615                        }
    616
    617                        if (quoted)
    618                        {
    619                            throw new RuntimeException(String.format("Unmatched quote in '%s:%d'", fileName, lineNum));
    620                        }
    621                        else if (tmpList != null && !skip)
    622                        {
    623                            tmpList.add(line.trim());
    624                        }
    625                    }
    626                }
    627            }
    628            catch (IOException e)
    629            {
    630                e.printStackTrace();
    631            }
    632            finally
    633            {
    634                if (buffer != null)
    635                {
    636                    try
    637                    {
    638                        buffer.close();
    639                    } catch (IOException e){}
    640                }
    641            }
    642        }
    643
    644        public void save()
    645        {
    646            if (PARENT != null && PARENT != this)
    647            {
    648                PARENT.save();
    649                return;
    650            }
    651
    652            try
    653            {
    654                if (file.getParentFile() != null)
    655                {
    656                    file.getParentFile().mkdirs();
    657                }
    658
    659                if (!file.exists() && !file.createNewFile())
    660                {
    661                    return;
    662                }
    663
    664                if (file.canWrite())
    665                {
    666                    FileOutputStream fos = new FileOutputStream(file);
    667                    BufferedWriter buffer = new BufferedWriter(new OutputStreamWriter(fos, defaultEncoding));
    668
    669                    buffer.write("# Configuration file" + NEW_LINE);
    670                    buffer.write("# Generated on " + DateFormat.getInstance().format(new Date()) + NEW_LINE + NEW_LINE);
    671
    672                    if (children.isEmpty())
    673                    {
    674                        save(buffer);
    675                    }
    676                    else
    677                    {
    678                        for (Map.Entry<String, Configuration> entry : children.entrySet())
    679                        {
    680                            buffer.write("START: \"" + entry.getKey() + "\"" + NEW_LINE);
    681                            entry.getValue().save(buffer);
    682                            buffer.write("END: \"" + entry.getKey() + "\"" + NEW_LINE + NEW_LINE);
    683                        }
    684                    }
    685
    686                    buffer.close();
    687                    fos.close();
    688                }
    689            }
    690            catch (IOException e)
    691            {
    692                e.printStackTrace();
    693            }
    694        }
    695
    696        private void save(BufferedWriter out) throws IOException
    697        {
    698            //For compatiblitties sake just in case, Thanks Atomic, to be removed next MC version
    699            //TO-DO: Remove next MC version
    700            Object[] categoryArray = categories.values().toArray();
    701            for (Object o : categoryArray)
    702            {
    703                if (o instanceof TreeMap)
    704                {
    705                    TreeMap treeMap = (TreeMap)o;
    706                    ConfigCategory converted = new ConfigCategory(file.getName());
    707                    FMLLog.warning("Forge found a Treemap saved for Configuration file " + file.getName() + ", this is deprecated behaviour!");
    708
    709                    for (Object key : treeMap.keySet())
    710                    {
    711                        FMLLog.warning("Converting Treemap to ConfigCategory, key: " + key + ", property value: " + ((Property)treeMap.get(key)).value);
    712                        converted.set((String)key, (Property)treeMap.get(key));
    713                    }
    714
    715                    categories.values().remove(o);
    716                    categories.put(file.getName(), converted);
    717                }
    718            }
    719
    720            for (ConfigCategory cat : categories.values())
    721            {
    722                if (!cat.isChild())
    723                {
    724                    cat.write(out, 0);
    725                    out.newLine();
    726                }
    727            }
    728        }
    729
    730        public ConfigCategory getCategory(String category)
    731        {
    732            ConfigCategory ret = categories.get(category);
    733
    734            if (ret == null)
    735            {
    736                if (category.contains(CATEGORY_SPLITTER))
    737                {
    738                    String[] hierarchy = category.split("\\"+CATEGORY_SPLITTER);
    739                    ConfigCategory parent = categories.get(hierarchy[0]);
    740
    741                    if (parent == null)
    742                    {
    743                        parent = new ConfigCategory(hierarchy[0]);
    744                        categories.put(parent.getQualifiedName(), parent);
    745                    }
    746
    747                    for (int i = 1; i < hierarchy.length; i++)
    748                    {
    749                        String name = ConfigCategory.getQualifiedName(hierarchy[i], parent);
    750                        ConfigCategory child = categories.get(name);
    751
    752                        if (child == null)
    753                        {
    754                            child = new ConfigCategory(hierarchy[i], parent);
    755                            categories.put(name, child);
    756                        }
    757
    758                        ret = child;
    759                        parent = child;
    760                    }
    761                }
    762                else
    763                {
    764                    ret = new ConfigCategory(category);
    765                    categories.put(category, ret);
    766                }
    767            }
    768
    769            return ret;
    770        }
    771
    772        public void addCustomCategoryComment(String category, String comment)
    773        {
    774            if (!caseSensitiveCustomCategories)
    775                category = category.toLowerCase(Locale.ENGLISH);
    776            getCategory(category).setComment(comment);
    777        }
    778
    779        private void setChild(String name, Configuration child)
    780        {
    781            if (!children.containsKey(name))
    782            {
    783                children.put(name, child);
    784            }
    785            else
    786            {
    787                Configuration old = children.get(name);
    788                child.categories = old.categories;
    789                child.fileName = old.fileName;
    790            }
    791        }
    792
    793        public static void enableGlobalConfig()
    794        {
    795            PARENT = new Configuration(new File(Loader.instance().getConfigDir(), "global.cfg"));
    796            PARENT.load();
    797        }
    798
    799        public static class UnicodeInputStreamReader extends Reader
    800        {
    801            private final InputStreamReader input;
    802            private final String defaultEnc;
    803
    804            public UnicodeInputStreamReader(InputStream source, String encoding) throws IOException
    805            {
    806                defaultEnc = encoding;
    807                String enc = encoding;
    808                byte[] data = new byte[4];
    809
    810                PushbackInputStream pbStream = new PushbackInputStream(source, data.length);
    811                int read = pbStream.read(data, 0, data.length);
    812                int size = 0;
    813
    814                int bom16 = (data[0] & 0xFF) << 8 | (data[1] & 0xFF);
    815                int bom24 = bom16 << 8 | (data[2] & 0xFF);
    816                int bom32 = bom24 << 8 | (data[3] & 0xFF);
    817
    818                if (bom24 == 0xEFBBBF)
    819                {
    820                    enc = "UTF-8";
    821                    size = 3;
    822                }
    823                else if (bom16 == 0xFEFF)
    824                {
    825                    enc = "UTF-16BE";
    826                    size = 2;
    827                }
    828                else if (bom16 == 0xFFFE)
    829                {
    830                    enc = "UTF-16LE";
    831                    size = 2;
    832                }
    833                else if (bom32 == 0x0000FEFF)
    834                {
    835                    enc = "UTF-32BE";
    836                    size = 4;
    837                }
    838                else if (bom32 == 0xFFFE0000) //This will never happen as it'll be caught by UTF-16LE,
    839                {                             //but if anyone ever runs across a 32LE file, i'd like to disect it.
    840                    enc = "UTF-32LE";
    841                    size = 4;
    842                }
    843
    844                if (size < read)
    845                {
    846                    pbStream.unread(data, size, read - size);
    847                }
    848
    849                this.input = new InputStreamReader(pbStream, enc);
    850            }
    851
    852            public String getEncoding()
    853            {
    854                return input.getEncoding();
    855            }
    856
    857            @Override
    858            public int read(char[] cbuf, int off, int len) throws IOException
    859            {
    860                return input.read(cbuf, off, len);
    861            }
    862
    863            @Override
    864            public void close() throws IOException
    865            {
    866                input.close();
    867            }
    868        }
    869    }