001    package EVolve.util.settings;
002    
003    import java.io.BufferedReader;
004    import java.io.File;
005    import java.io.FileOutputStream;
006    import java.io.FileReader;
007    import java.io.IOException;
008    import java.io.PrintStream;
009    import java.util.HashSet;
010    import java.util.Iterator;
011    import java.util.LinkedHashMap;
012    import java.util.Map;
013    import java.util.Set;
014    
015    public class IniFile {
016        private Map sections;
017        private File file;
018        private boolean modified;
019    
020        public IniFile(String fileName) throws IOException {
021            this(new File(fileName));
022        }
023    
024        public IniFile(File file) throws IOException {
025            this.sections = new LinkedHashMap();
026            this.file = file;
027            
028            if (file.exists()) {
029                load();
030            }
031        }
032    
033        private static String parseValue(String value) {
034            if (value == null) {
035                return null;
036            }
037    
038            value = value.trim();
039            if (value.length() > 0) {
040                char s[] = value.toCharArray();
041                if (s[0] == '"') {
042                    // Find the next '"'
043                    boolean escaped = false;
044                    int i;
045    loop:
046                    for (i = 1; i < s.length; i++) {
047                        if (escaped) {
048                            escaped = false;
049                        } else {
050                            switch (s[i]) {
051                                case '\\':
052                                    escaped = true;
053                                    break;
054                                case '"':
055                                    break loop;
056                                default:
057                                    break;
058                            }
059                        }
060                    }
061    
062                    if (s[i] == '"') {
063                        if (i < s.length - 1) {
064                            String commentStr = value.substring(i+1).trim();
065                            if (!commentStr.startsWith(";")) {
066                                throw new RuntimeException("Expression expected to be a comment");
067                            }
068                        } 
069                        value = value.substring(1, i);
070                    }
071                }
072            }
073    
074            return unescape(value);
075        }
076    
077        public static String escape(String value) {
078            if (value == null) {
079                return null;
080            }
081    
082            String result = "";
083            char[] s = value.toCharArray();
084            for (int i = 0; i < s.length; i++) {
085                char c = s[i];
086                switch (c) {
087                    case '?':
088                        result += "\\?";
089                        break;
090                    case '\b':
091                        result += "\\b";
092                        break;
093                    case '\t':
094                        result += "\\t";
095                        break;
096                    case '\n':
097                        result += "\\n";
098                        break;
099                    case '\f':
100                        result += "\\f";
101                        break;
102                    case '\r':
103                        result += "\\r";
104                        break;
105                    case '"':
106                        result += "\\\"";
107                        break;
108                    case '\'':
109                        result += "\\'";
110                        break;
111                    case '\\':
112                        result += "\\\\";
113                        break;
114                    default:
115                        result += c;
116                        break;
117                }
118            }
119    
120            return result;
121        }
122    
123        public static String unescape(String value) {
124            if (value == null) {
125                return null;
126            }
127    
128            String result = "";
129            char[] s = value.toCharArray();
130            for (int i = 0; i < s.length; i++) {
131                char c = s[i];
132                if (c == '\\') {
133                    if (i == (s.length - 1)) {
134                        return result + '\\';
135                    } else {
136                        c = s[++i];
137                        switch (c) {
138                            case '?':
139                                result += '?';
140                                break;
141                            case 'b':
142                                result += '\b';
143                                break;
144                            case 't':
145                                result += '\t';
146                                break;
147                            case 'n':
148                                result += '\n';
149                                break;
150                            case 'f':
151                                result += '\f';
152                                break;
153                            case 'r':
154                                result += '\r';
155                            case 'x':
156                                {
157                                    i++;
158                                    String hexString = "";
159                                    while (i < s.length) {
160                                        c = s[i];
161                                        if (Character.digit(c, 16) >= 0) {
162                                            hexString += c;
163                                            i++;
164                                        } else {
165                                            break;
166                                        }
167                                    }
168    
169                                    if (hexString.length() <= 0) {
170                                        i--;
171                                        result += "\\x";
172                                    } else {
173                                        result += (char) Short.parseShort(hexString, 16);
174                                    }
175                                }
176                                break;
177                            case '0':
178                            case '1':
179                            case '2':
180                            case '3':
181                            case '4':
182                            case '5':
183                            case '6':
184                            case '7':
185                            case '8':
186                            case '9':
187                                {
188                                    String octalString = "";
189                                    while (i < s.length) {
190                                        c = s[i];
191                                        if (Character.digit(c, 8) >= 0) {
192                                            octalString += c;
193                                            i++;
194                                        } else {
195                                            break;
196                                        }
197                                    }
198    
199                                    if (octalString.length() <= 0) {
200                                        result += "\\";
201                                    } else {
202                                        result += (char) Short.parseShort(octalString, 8);
203                                    }
204                                }
205                                break;
206                            default:
207                                result += '\\' + c;
208                                break;
209                        }
210                    }
211                } else {
212                    result += c;
213                }
214            }
215    
216            return result;
217        }
218    
219        public void load() throws IOException {
220            BufferedReader in = new BufferedReader(new FileReader(file));
221            String line;
222            String currentSection = null;
223    
224            while ((line = in.readLine()) != null) {
225                line = line.trim();
226                if (line.length() <= 0) {
227                    continue;
228                }
229    
230                char c = line.charAt(0);
231    
232                switch (c) {
233                    case ';':
234                        continue;
235                    case '[':
236                        currentSection = line.substring(1, line.length() - 1);
237                        put(currentSection);
238                        break;
239                    default:
240                        {
241                            int eqPos = line.indexOf('=');
242                            if (eqPos < 0) {
243                                throw new RuntimeException("Invalid INI file format -- Expected key=value");
244                            }
245    
246                            String key = line.substring(0, eqPos).trim();
247                            String value = parseValue(line.substring(eqPos + 1));
248    
249                            put(currentSection, key, value);
250                        }
251                        break;
252                }
253            }
254    
255            modified = false;
256    
257            in.close();
258        }
259    
260        public void write() throws IOException {
261    
262            PrintStream out = new PrintStream(new FileOutputStream(file));
263            
264            Iterator sectionIt = sections.keySet().iterator();
265            while (sectionIt.hasNext()) {
266                Object sectionName = (String) sectionIt.next();
267                Map keysToValues = (Map) sections.get(sectionName);
268    
269                out.println("[" + sectionName + "]");
270                Iterator keysIt = keysToValues.keySet().iterator();
271                while (keysIt.hasNext()) {
272                    String key = (String) keysIt.next();
273                    String value = (String) keysToValues.get(key);
274                    out.println(key + "=\"" + escape(value) + "\"");
275                }
276                out.println();
277            }
278        }
279    
280        public boolean contains(String section) {
281            if (section == null) {
282                return false;
283            }
284    
285            return sections.containsKey(section);
286        }
287    
288        public boolean contains(String section, String key) {
289            if (section == null || key == null) {
290                return false;
291            }
292    
293            if (sections.containsKey(section)) {
294                Map keysToValues = (Map) sections.get(section);
295                return keysToValues.containsKey(key);
296            }
297    
298            return false;
299        }
300    
301        public boolean put(String section) {
302            if (section == null) {
303                return false;
304            }
305    
306            if (!sections.containsKey(section)) {
307                sections.put(section, new LinkedHashMap());
308            }
309    
310            modified = true;
311            return true;
312        }
313    
314        public boolean put(String section, String key, String value) {
315            if (section == null || key == null) {
316                return false;
317            }
318    
319            if (value == null) {
320                value = "";
321            }
322    
323            Map keysToValues;
324            if (sections.containsKey(section)) {
325                keysToValues = (Map) sections.get(section);
326            } else {
327                keysToValues = new LinkedHashMap();
328                sections.put(section, keysToValues);
329            }
330    
331            keysToValues.put(key, value);
332    
333            modified = true;
334            
335            return true;
336        }
337    
338        public String get(String section, String key) {
339            if (sections.containsKey(section)) {
340                Map keysToValues = (Map) sections.get(section);
341                if (keysToValues.containsKey(key)) {
342                    return (String) keysToValues.get(key);
343                }
344            }
345    
346            return null;
347        }
348    
349        public Set getSections() {
350            return new HashSet(sections.keySet());
351        }
352    
353        public Set getKeys(String section) {
354            if (section == null) {
355                return null;
356            }
357    
358            if (sections.containsKey(section)) {
359                Map keysToValues = (Map) sections.get(section);
360                return new HashSet(keysToValues.keySet());
361            }
362    
363            return null;
364        }
365    
366        public Set getValues(String section) {
367            if (section == null) {
368                return null;
369            }
370    
371            if (sections.containsKey(section)) {
372                Map keysToValues = (Map) sections.get(section);
373                return new HashSet(keysToValues.values());
374            }
375    
376            return null;
377        }
378    
379        public void close() throws IOException {
380            if (sections != null) {
381                if (modified) {
382                    this.write();
383                }
384                sections = null;
385            }
386        }
387    
388        public void finalize() {
389            try {
390                this.close();
391            } catch (IOException e) {
392                // not much we can do now...
393            }
394        }
395    }
396