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