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    }