1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration;
19
20 import java.io.BufferedReader;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.Reader;
25 import java.io.Writer;
26 import java.net.URL;
27 import java.util.Collection;
28 import java.util.Iterator;
29 import java.util.Set;
30 import java.util.TreeSet;
31
32 import org.apache.commons.lang.StringUtils;
33
34 /***
35 * <p>
36 * An initialization or ini file is a configuration file typically found on
37 * Microsoft's Windows operating system and contains data for Windows based
38 * applications.
39 * </p>
40 *
41 * <p>
42 * Although popularized by Windows, ini files can be used on any system or
43 * platform due to the fact that they are merely text files that can easily be
44 * parsed and modified by both humans and computers.
45 * </p>
46 *
47 * <p>
48 * A typcial ini file could look something like:
49 * </p>
50 * <code>
51 * [section1]<br>
52 * ; this is a comment!<br>
53 * var1 = foo<br>
54 * var2 = bar<br>
55 *<br>
56 * [section2]<br>
57 * var1 = doo<br>
58 * </code>
59 *
60 * <p>
61 * The format of ini files is fairly straight forward and is composed of three
62 * components:<br>
63 * <ul>
64 * <li><b>Sections:</b> Ini files are split into sections, each section
65 * starting with a section declaration. A section declaration starts with a '['
66 * and ends with a ']'. Sections occur on one line only.</li>
67 * <li><b>Parameters:</b> Items in a section are known as parameters.
68 * Parameters have a typical <code>key = value</code> format.</li>
69 * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.
70 * </li>
71 * </ul>
72 * </p>
73 *
74 * <p>
75 * There are various implementations of the ini file format by various vendors
76 * which has caused a number of differences to appear. As far as possible this
77 * configuration tries to be lenient and support most of the differences.
78 * </p>
79 *
80 * <p>
81 * Some of the differences supported are as follows:
82 * <ul>
83 * <li><b>Comments:</b> The '#' character is also accepted as a comment
84 * signifier.</li>
85 * <li><b>Key value separtor:</b> The ':' character is also accepted in place
86 * of '=' to separate keys and values in parameters, for example
87 * <code>var1 : foo</code>.</li>
88 * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed ,
89 * this configuration does however support it. In the event of a duplicate
90 * section, the two section's values are merged.</li>
91 * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
92 * allowed if they are in two different sections, thus they are local to
93 * sections; this configuration simply merges duplicates; if a section has a
94 * duplicate parameter the values are then added to the key as a list. </li>
95 * </ul>
96 * </p>
97 * <p>
98 * Global parameters are also allowed; any parameters declared before a section
99 * is declared are added to a global section. It is important to note that this
100 * global section does not have a name.
101 * </p>
102 * <p>
103 * In all instances, a parameter's key is prepended with its section name and a
104 * '.' (period). Thus a parameter named "var1" in "section1" will have the key
105 * <code>section1.var1</code> in this configuration. Thus, a section's
106 * parameters can easily be retrieved using the <code>subset</code> method
107 * using the section name as the prefix.
108 * </p>
109 * <p>
110 * <h3>Implementation Details:</h3>
111 * Consider the following ini file:<br>
112 * <code>
113 * default = ok<br>
114 * <br>
115 * [section1]<br>
116 * var1 = foo<br>
117 * var2 = doodle<br>
118 * <br>
119 * [section2]<br>
120 * ; a comment<br>
121 * var1 = baz<br>
122 * var2 = shoodle<br>
123 * bad =<br>
124 * = worse<br>
125 * <br>
126 * [section3]<br>
127 * # another comment<br>
128 * var1 : foo<br>
129 * var2 : bar<br>
130 * var5 : test1<br>
131 * <br>
132 * [section3]<br>
133 * var3 = foo<br>
134 * var4 = bar<br>
135 * var5 = test2<br>
136 * </code>
137 * </p>
138 * <p>
139 * This ini file will be parsed without error. Note:
140 * <ul>
141 * <li>The parameter named "default" is added to the global section, it's value
142 * is accessed simply using <code>getProperty("default")</code>.</li>
143 * <li>Section 1's parameters can be accessed using
144 * <code>getProperty("section1.var1")</code>.</li>
145 * <li>The parameter named "bad" simply adds the parameter with an empty value.
146 * </li>
147 * <li>The empty key with value "= worse" is added using an empty key. This key
148 * is still added to section 2 and the value can be accessed using
149 * <code>getProperty("section2.")</code>, notice the period '.' following the
150 * section name.</li>
151 * <li>Section three uses both '=' and ':' to separate keys and values.</li>
152 * <li>Section 3 has a duplicate key named "var5". The value for this key is
153 * [test1, test2], and is represented as a List.</li>
154 * </ul>
155 * </p>
156 * <p>
157 * The set of sections in this configuration can be retrieved using the
158 * <code>getSections</code> method.
159 * </p>
160 * <p>
161 * <em>Note:</em> Configuration objects of this type can be read concurrently
162 * by multiple threads. However if one of these threads modifies the object,
163 * synchronization has to be performed manually.
164 * </p>
165 *
166 * @author Trevor Miller
167 * @version $Id: INIConfiguration.java 720600 2008-11-25 21:20:01Z oheger $
168 * @since 1.4
169 * @deprecated This class has been replaced by HierarchicalINIConfiguration,
170 * which provides a superset of the functionality offered by this class.
171 */
172 public class INIConfiguration extends AbstractFileConfiguration
173 {
174 /***
175 * The characters that signal the start of a comment line.
176 */
177 protected static final String COMMENT_CHARS = "#;";
178
179 /***
180 * The characters used to separate keys from values.
181 */
182 protected static final String SEPARATOR_CHARS = "=:";
183
184 /***
185 * Create a new empty INI Configuration.
186 */
187 public INIConfiguration()
188 {
189 super();
190 }
191
192 /***
193 * Create and load the ini configuration from the given file.
194 *
195 * @param filename The name pr path of the ini file to load.
196 * @throws ConfigurationException If an error occurs while loading the file
197 */
198 public INIConfiguration(String filename) throws ConfigurationException
199 {
200 super(filename);
201 }
202
203 /***
204 * Create and load the ini configuration from the given file.
205 *
206 * @param file The ini file to load.
207 * @throws ConfigurationException If an error occurs while loading the file
208 */
209 public INIConfiguration(File file) throws ConfigurationException
210 {
211 super(file);
212 }
213
214 /***
215 * Create and load the ini configuration from the given url.
216 *
217 * @param url The url of the ini file to load.
218 * @throws ConfigurationException If an error occurs while loading the file
219 */
220 public INIConfiguration(URL url) throws ConfigurationException
221 {
222 super(url);
223 }
224
225 /***
226 * Save the configuration to the specified writer.
227 *
228 * @param writer - The writer to save the configuration to.
229 * @throws ConfigurationException If an error occurs while writing the
230 * configuration
231 */
232 public void save(Writer writer) throws ConfigurationException
233 {
234 PrintWriter out = new PrintWriter(writer);
235 Iterator it = getSections().iterator();
236 while (it.hasNext())
237 {
238 String section = (String) it.next();
239 out.print("[");
240 out.print(section);
241 out.print("]");
242 out.println();
243
244 Configuration subset = subset(section);
245 Iterator keys = subset.getKeys();
246 while (keys.hasNext())
247 {
248 String key = (String) keys.next();
249 Object value = subset.getProperty(key);
250 if (value instanceof Collection)
251 {
252 Iterator values = ((Collection) value).iterator();
253 while (values.hasNext())
254 {
255 value = (Object) values.next();
256 out.print(key);
257 out.print(" = ");
258 out.print(formatValue(value.toString()));
259 out.println();
260 }
261 }
262 else
263 {
264 out.print(key);
265 out.print(" = ");
266 out.print(formatValue(value.toString()));
267 out.println();
268 }
269 }
270
271 out.println();
272 }
273
274 out.flush();
275 }
276
277 /***
278 * Load the configuration from the given reader. Note that the
279 * <code>clear</code> method is not called so the configuration read in
280 * will be merged with the current configuration.
281 *
282 * @param reader The reader to read the configuration from.
283 * @throws ConfigurationException If an error occurs while reading the
284 * configuration
285 */
286 public void load(Reader reader) throws ConfigurationException
287 {
288 try
289 {
290 BufferedReader bufferedReader = new BufferedReader(reader);
291 String line = bufferedReader.readLine();
292 String section = "";
293 while (line != null)
294 {
295 line = line.trim();
296 if (!isCommentLine(line))
297 {
298 if (isSectionLine(line))
299 {
300 section = line.substring(1, line.length() - 1) + ".";
301 }
302 else
303 {
304 String key = "";
305 String value = "";
306 int index = line.indexOf("=");
307 if (index >= 0)
308 {
309 key = section + line.substring(0, index);
310 value = parseValue(line.substring(index + 1));
311 }
312 else
313 {
314 index = line.indexOf(":");
315 if (index >= 0)
316 {
317 key = section + line.substring(0, index);
318 value = parseValue(line.substring(index + 1));
319 }
320 else
321 {
322 key = section + line;
323 }
324 }
325 addProperty(key.trim(), value);
326 }
327 }
328 line = bufferedReader.readLine();
329 }
330 }
331 catch (IOException e)
332 {
333 throw new ConfigurationException("Unable to load the configuration", e);
334 }
335 }
336
337 /***
338 * Parse the value to remove the quotes and ignoring the comment.
339 * Example:
340 *
341 * <pre>"value" ; comment -> value</pre>
342 *
343 * <pre>'value' ; comment -> value</pre>
344 *
345 * @param value
346 */
347 private String parseValue(String value)
348 {
349 value = value.trim();
350
351 boolean quoted = value.startsWith("\"") || value.startsWith("'");
352 boolean stop = false;
353 boolean escape = false;
354
355 char quote = quoted ? value.charAt(0) : 0;
356
357 int i = quoted ? 1 : 0;
358
359 StringBuffer result = new StringBuffer();
360 while (i < value.length() && !stop)
361 {
362 char c = value.charAt(i);
363
364 if (quoted)
365 {
366 if ('//' == c && !escape)
367 {
368 escape = true;
369 }
370 else if (!escape && quote == c)
371 {
372 stop = true;
373 }
374 else if (escape && quote == c)
375 {
376 escape = false;
377 result.append(c);
378 }
379 else
380 {
381 if (escape)
382 {
383 escape = false;
384 result.append('//');
385 }
386
387 result.append(c);
388 }
389 }
390 else
391 {
392 if (COMMENT_CHARS.indexOf(c) == -1)
393 {
394 result.append(c);
395 }
396 else
397 {
398 stop = true;
399 }
400 }
401
402 i++;
403 }
404
405 String v = result.toString();
406 if (!quoted)
407 {
408 v = v.trim();
409 }
410 return v;
411 }
412
413 /***
414 * Add quotes around the specified value if it contains a comment character.
415 */
416 private String formatValue(String value)
417 {
418 boolean quoted = false;
419
420 for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
421 {
422 char c = COMMENT_CHARS.charAt(i);
423 if (value.indexOf(c) != -1)
424 {
425 quoted = true;
426 }
427 }
428
429 if (quoted)
430 {
431 return '"' + StringUtils.replace(value, "\"", "//\"") + '"';
432 }
433 else
434 {
435 return value;
436 }
437 }
438
439 /***
440 * Determine if the given line is a comment line.
441 *
442 * @param line The line to check.
443 * @return true if the line is empty or starts with one of the comment
444 * characters
445 */
446 protected boolean isCommentLine(String line)
447 {
448 if (line == null)
449 {
450 return false;
451 }
452
453 return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
454 }
455
456 /***
457 * Determine if the given line is a section.
458 *
459 * @param line The line to check.
460 * @return true if the line contains a secion
461 */
462 protected boolean isSectionLine(String line)
463 {
464 if (line == null)
465 {
466 return false;
467 }
468 return line.startsWith("[") && line.endsWith("]");
469 }
470
471 /***
472 * Return a set containing the sections in this ini configuration. Note that
473 * changes to this set do not affect the configuration.
474 *
475 * @return a set containing the sections.
476 */
477 public Set getSections()
478 {
479 Set sections = new TreeSet();
480
481 Iterator keys = getKeys();
482 while (keys.hasNext())
483 {
484 String key = (String) keys.next();
485 int index = key.indexOf(".");
486 if (index >= 0)
487 {
488 sections.add(key.substring(0, index));
489 }
490 }
491
492 return sections;
493 }
494 }