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.BufferedReader;
009    import java.io.BufferedWriter;
010    import java.io.File;
011    import java.io.FileInputStream;
012    import java.io.FileOutputStream;
013    import java.io.IOException;
014    import java.io.InputStreamReader;
015    import java.io.OutputStreamWriter;
016    import java.text.DateFormat;
017    import java.util.Collection;
018    import java.util.Date;
019    import java.util.Locale;
020    import java.util.Map;
021    import java.util.TreeMap;
022    
023    import com.google.common.base.Splitter;
024    import com.google.common.collect.Maps;
025    
026    import net.minecraft.src.Block;
027    
028    /**
029     * This class offers advanced configurations capabilities, allowing to provide
030     * various categories for configuration variables.
031     */
032    public class Configuration
033    {
034    
035        private boolean configBlocks[] = null;
036    
037        public static final String CATEGORY_GENERAL = "general";
038        public static final String CATEGORY_BLOCK   = "block";
039        public static final String CATEGORY_ITEM    = "item";
040    
041        File file;
042    
043        public Map<String, Map<String, Property>> categories = new TreeMap<String, Map<String, Property>>();
044    
045        public TreeMap<String, Property> blockProperties   = new TreeMap<String, Property>();
046        public TreeMap<String, Property> itemProperties    = new TreeMap<String, Property>();
047        public TreeMap<String, Property> generalProperties = new TreeMap<String, Property>();
048    
049        private Map<String,String> customCategoryComments = Maps.newHashMap();
050        private boolean caseSensitiveCustomCategories;
051        public static final String ALLOWED_CHARS = "._-";
052    
053        /**
054         * Create a configuration file for the file given in parameter.
055         */
056        public Configuration(File file)
057        {
058            this.file = file;
059            categories.put(CATEGORY_GENERAL, generalProperties);
060            categories.put(CATEGORY_BLOCK, blockProperties);
061            categories.put(CATEGORY_ITEM, itemProperties);
062        }
063    
064        public Configuration(File file, boolean caseSensitiveCustomCategories)
065        {
066            this(file);
067            this.caseSensitiveCustomCategories = caseSensitiveCustomCategories;
068        }
069        /**
070         * Gets or create a block id property. If the block id property key is
071         * already in the configuration, then it will be used. Otherwise,
072         * defaultId will be used, except if already taken, in which case this
073         * will try to determine a free default id.
074         */
075        public Property getOrCreateBlockIdProperty(String key, int defaultId)
076        {
077            if (configBlocks == null)
078            {
079                configBlocks = new boolean[Block.blocksList.length];
080    
081                for (int i = 0; i < configBlocks.length; ++i)
082                {
083                    configBlocks[i] = false;
084                }
085            }
086    
087            Map<String, Property> properties = categories.get(CATEGORY_BLOCK);
088            if (properties.containsKey(key))
089            {
090                Property property = getOrCreateIntProperty(key, Configuration.CATEGORY_BLOCK, defaultId);
091                configBlocks[Integer.parseInt(property.value)] = true;
092                return property;
093            }
094            else
095            {
096                Property property = new Property();
097                properties.put(key, property);
098                property.setName(key);
099    
100                if (Block.blocksList[defaultId] == null && !configBlocks[defaultId])
101                {
102                    property.value = Integer.toString(defaultId);
103                    configBlocks[defaultId] = true;
104                    return property;
105                }
106                else
107                {
108                    for (int j = configBlocks.length - 1; j >= 0; --j)
109                    {
110                        if (Block.blocksList[j] == null && !configBlocks[j])
111                        {
112                            property.value = Integer.toString(j);
113                            configBlocks[j] = true;
114                            return property;
115                        }
116                    }
117    
118                    throw new RuntimeException("No more block ids available for " + key);
119                }
120            }
121        }
122    
123        public Property getOrCreateIntProperty(String key, String category, int defaultValue)
124        {
125            Property prop = getOrCreateProperty(key, category, Integer.toString(defaultValue));
126            try
127            {
128                Integer.parseInt(prop.value);
129                return prop;
130            }
131            catch (NumberFormatException e)
132            {
133                prop.value = Integer.toString(defaultValue);
134                return prop;
135            }
136        }
137    
138        public Property getOrCreateBooleanProperty(String key, String category, boolean defaultValue)
139        {
140            Property prop = getOrCreateProperty(key, category, Boolean.toString(defaultValue));
141            if ("true".equals(prop.value.toLowerCase(Locale.ENGLISH)) || "false".equals(prop.value.toLowerCase(Locale.ENGLISH)))
142            {
143                return prop;
144            }
145            else
146            {
147                prop.value = Boolean.toString(defaultValue);
148                return prop;
149            }
150        }
151    
152        public Property getOrCreateProperty(String key, String category, String defaultValue)
153        {
154            if (!caseSensitiveCustomCategories)
155            {
156                category = category.toLowerCase(Locale.ENGLISH);
157            }
158            Map<String, Property> source = categories.get(category);
159    
160            if(source == null)
161            {
162                source = new TreeMap<String, Property>();
163                categories.put(category, source);
164            }
165    
166            if (source.containsKey(key))
167            {
168                return source.get(key);
169            }
170            else if (defaultValue != null)
171            {
172                Property property = new Property();
173    
174                source.put(key, property);
175                property.setName(key);
176    
177                property.value = defaultValue;
178                return property;
179            }
180            else
181            {
182                return null;
183            }
184        }
185    
186        public void load()
187        {
188            BufferedReader buffer = null;
189            try
190            {
191                if (file.getParentFile() != null)
192                {
193                    file.getParentFile().mkdirs();
194                }
195    
196                if (!file.exists() && !file.createNewFile())
197                {
198                    return;
199                }
200    
201                if (file.canRead())
202                {
203                    FileInputStream fileinputstream = new FileInputStream(file);
204                    buffer = new BufferedReader(new InputStreamReader(fileinputstream, "UTF-8"));
205    
206                    String line;
207                    Map<String, Property> currentMap = null;
208    
209                    while (true)
210                    {
211                        line = buffer.readLine();
212    
213                        if (line == null)
214                        {
215                            break;
216                        }
217    
218                        int nameStart = -1, nameEnd = -1;
219                        boolean skip = false;
220    
221                        for (int i = 0; i < line.length() && !skip; ++i)
222                        {
223                            if (Character.isLetterOrDigit(line.charAt(i)) || ALLOWED_CHARS.indexOf(line.charAt(i)) != -1)
224                            {
225                                if (nameStart == -1)
226                                {
227                                    nameStart = i;
228                                }
229    
230                                nameEnd = i;
231                            }
232                            else if (Character.isWhitespace(line.charAt(i)))
233                            {
234                                // ignore space charaters
235                            }
236                            else
237                            {
238                                switch (line.charAt(i))
239                                {
240                                    case '#':
241                                        skip = true;
242                                        continue;
243    
244                                    case '{':
245                                        String scopeName = line.substring(nameStart, nameEnd + 1);
246    
247                                        currentMap = categories.get(scopeName);
248                                        if (currentMap == null)
249                                        {
250                                            currentMap = new TreeMap<String, Property>();
251                                            categories.put(scopeName, currentMap);
252                                        }
253    
254                                        break;
255    
256                                    case '}':
257                                        currentMap = null;
258                                        break;
259    
260                                    case '=':
261                                        String propertyName = line.substring(nameStart, nameEnd + 1);
262    
263                                        if (currentMap == null)
264                                        {
265                                            throw new RuntimeException("property " + propertyName + " has no scope");
266                                        }
267    
268                                        Property prop = new Property();
269                                        prop.setName(propertyName);
270                                        prop.value = line.substring(i + 1);
271                                        i = line.length();
272    
273                                        currentMap.put(propertyName, prop);
274    
275                                        break;
276    
277                                    default:
278                                        throw new RuntimeException("unknown character " + line.charAt(i));
279                                }
280                            }
281                        }
282                    }
283                }
284            }
285            catch (IOException e)
286            {
287                e.printStackTrace();
288            }
289            finally
290            {
291                if (buffer != null)
292                {
293                    try
294                    {
295                        buffer.close();
296                    } catch (IOException e){}
297                }
298            }
299        }
300    
301        public void save()
302        {
303            try
304            {
305                if (file.getParentFile() != null)
306                {
307                    file.getParentFile().mkdirs();
308                }
309    
310                if (!file.exists() && !file.createNewFile())
311                {
312                    return;
313                }
314    
315                if (file.canWrite())
316                {
317                    FileOutputStream fos = new FileOutputStream(file);
318                    BufferedWriter buffer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
319    
320                    buffer.write("# Configuration file\r\n");
321                    buffer.write("# Generated on " + DateFormat.getInstance().format(new Date()) + "\r\n");
322                    buffer.write("\r\n");
323    
324                    for(Map.Entry<String, Map<String, Property>> category : categories.entrySet())
325                    {
326                        buffer.write("####################\r\n");
327                        buffer.write("# " + category.getKey() + " \r\n");
328                        if (customCategoryComments.containsKey(category.getKey()))
329                        {
330                            buffer.write("#===================\r\n");
331                            String comment = customCategoryComments.get(category.getKey());
332                            Splitter splitter = Splitter.onPattern("\r?\n");
333                            for (String commentLine : splitter.split(comment))
334                            {
335                                buffer.write("# ");
336                                buffer.write(commentLine+"\r\n");
337                            }
338                        }
339                        buffer.write("####################\r\n\r\n");
340    
341                        buffer.write(category.getKey() + " {\r\n");
342                        writeProperties(buffer, category.getValue().values());
343                        buffer.write("}\r\n\r\n");
344                    }
345    
346                    buffer.close();
347                    fos.close();
348                }
349            }
350            catch (IOException e)
351            {
352                e.printStackTrace();
353            }
354        }
355    
356        public void addCustomCategoryComment(String category, String comment)
357        {
358            if (!caseSensitiveCustomCategories)
359                category = category.toLowerCase(Locale.ENGLISH);
360            customCategoryComments.put(category, comment);
361        }
362    
363        private void writeProperties(BufferedWriter buffer, Collection<Property> props) throws IOException
364        {
365            for (Property property : props)
366            {
367                if (property.comment != null)
368                {
369                    Splitter splitter = Splitter.onPattern("\r?\n");
370                    for (String commentLine : splitter.split(property.comment))
371                    {
372                        buffer.write("   # " + commentLine + "\r\n");
373                    }
374                }
375    
376                buffer.write("   " + property.getName() + "=" + property.value);
377                buffer.write("\r\n");
378            }
379        }
380    }