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