001 /*
002 * Copyright 2005,2009 Ivan SZKIBA
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.ini4j;
017
018 import org.ini4j.spi.IniHandler;
019 import org.ini4j.spi.Warnings;
020
021 import java.io.File;
022 import java.io.FileReader;
023 import java.io.FileWriter;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.OutputStream;
027 import java.io.Reader;
028 import java.io.Serializable;
029 import java.io.Writer;
030
031 import java.net.URL;
032
033 import java.util.ArrayList;
034 import java.util.Collections;
035 import java.util.HashMap;
036 import java.util.List;
037 import java.util.Map;
038 import java.util.regex.Matcher;
039 import java.util.regex.Pattern;
040
041 public class ConfigParser implements Serializable
042 {
043 private static final long serialVersionUID = 9118857036229164353L;
044 private PyIni _ini;
045
046 @SuppressWarnings(Warnings.UNCHECKED)
047 public ConfigParser()
048 {
049 this(Collections.EMPTY_MAP);
050 }
051
052 public ConfigParser(Map<String, String> defaults)
053 {
054 _ini = new PyIni(defaults);
055 }
056
057 public boolean getBoolean(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
058 {
059 boolean ret;
060 String value = get(section, option);
061
062 if ("1".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value))
063 {
064 ret = true;
065 }
066 else if ("0".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value))
067 {
068 ret = false;
069 }
070 else
071 {
072 throw new IllegalArgumentException(value);
073 }
074
075 return ret;
076 }
077
078 public double getDouble(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
079 {
080 return Double.parseDouble(get(section, option));
081 }
082
083 public float getFloat(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
084 {
085 return Float.parseFloat(get(section, option));
086 }
087
088 public int getInt(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
089 {
090 return Integer.parseInt(get(section, option));
091 }
092
093 public long getLong(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
094 {
095 return Long.parseLong(get(section, option));
096 }
097
098 public void addSection(String section) throws DuplicateSectionException
099 {
100 if (_ini.containsKey(section))
101 {
102 throw new DuplicateSectionException(section);
103 }
104 else if (PyIni.DEFAULT_SECTION_NAME.equalsIgnoreCase(section))
105 {
106 throw new IllegalArgumentException(section);
107 }
108
109 _ini.add(section);
110 }
111
112 public Map<String, String> defaults()
113 {
114 return _ini.getDefaults();
115 }
116
117 @SuppressWarnings(Warnings.UNCHECKED)
118 public String get(String section, String option) throws NoSectionException, NoOptionException, InterpolationException
119 {
120 return get(section, option, false, Collections.EMPTY_MAP);
121 }
122
123 @SuppressWarnings(Warnings.UNCHECKED)
124 public String get(String section, String option, boolean raw) throws NoSectionException, NoOptionException, InterpolationException
125 {
126 return get(section, option, raw, Collections.EMPTY_MAP);
127 }
128
129 public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException, NoOptionException,
130 InterpolationException
131 {
132 String value = requireOption(sectionName, optionName);
133
134 if (!raw && (value != null) && (value.indexOf(PyIni.SUBST_CHAR) >= 0))
135 {
136 value = _ini.fetch(sectionName, optionName, variables);
137 }
138
139 return value;
140 }
141
142 public boolean hasOption(String sectionName, String optionName)
143 {
144 Ini.Section section = _ini.get(sectionName);
145
146 return (section != null) && section.containsKey(optionName);
147 }
148
149 public boolean hasSection(String sectionName)
150 {
151 return _ini.containsKey(sectionName);
152 }
153
154 @SuppressWarnings(Warnings.UNCHECKED)
155 public List<Map.Entry<String, String>> items(String sectionName) throws NoSectionException, InterpolationMissingOptionException
156 {
157 return items(sectionName, false, Collections.EMPTY_MAP);
158 }
159
160 @SuppressWarnings(Warnings.UNCHECKED)
161 public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException, InterpolationMissingOptionException
162 {
163 return items(sectionName, raw, Collections.EMPTY_MAP);
164 }
165
166 public List<Map.Entry<String, String>> items(String sectionName, boolean raw, Map<String, String> variables) throws NoSectionException,
167 InterpolationMissingOptionException
168 {
169 Ini.Section section = requireSection(sectionName);
170 Map<String, String> ret;
171
172 if (raw)
173 {
174 ret = new HashMap<String, String>(section);
175 }
176 else
177 {
178 ret = new HashMap<String, String>();
179 for (String key : section.keySet())
180 {
181 ret.put(key, _ini.fetch(section, key, variables));
182 }
183 }
184
185 return new ArrayList<Map.Entry<String, String>>(ret.entrySet());
186 }
187
188 public List<String> options(String sectionName) throws NoSectionException
189 {
190 requireSection(sectionName);
191
192 return new ArrayList<String>(_ini.get(sectionName).keySet());
193 }
194
195 public void read(String... filenames) throws IOException, ParsingException
196 {
197 for (String filename : filenames)
198 {
199 read(new File(filename));
200 }
201 }
202
203 public void read(Reader reader) throws IOException, ParsingException
204 {
205 try
206 {
207 _ini.load(reader);
208 }
209 catch (InvalidFileFormatException x)
210 {
211 throw new ParsingException(x);
212 }
213 }
214
215 public void read(URL url) throws IOException, ParsingException
216 {
217 try
218 {
219 _ini.load(url);
220 }
221 catch (InvalidFileFormatException x)
222 {
223 throw new ParsingException(x);
224 }
225 }
226
227 public void read(File file) throws IOException, ParsingException
228 {
229 try
230 {
231 _ini.load(new FileReader(file));
232 }
233 catch (InvalidFileFormatException x)
234 {
235 throw new ParsingException(x);
236 }
237 }
238
239 public void read(InputStream stream) throws IOException, ParsingException
240 {
241 try
242 {
243 _ini.load(stream);
244 }
245 catch (InvalidFileFormatException x)
246 {
247 throw new ParsingException(x);
248 }
249 }
250
251 public boolean removeOption(String sectionName, String optionName) throws NoSectionException
252 {
253 Ini.Section section = requireSection(sectionName);
254 boolean ret = section.containsKey(optionName);
255
256 section.remove(optionName);
257
258 return ret;
259 }
260
261 public boolean removeSection(String sectionName)
262 {
263 boolean ret = _ini.containsKey(sectionName);
264
265 _ini.remove(sectionName);
266
267 return ret;
268 }
269
270 public List<String> sections()
271 {
272 return new ArrayList<String>(_ini.keySet());
273 }
274
275 public void set(String sectionName, String optionName, Object value) throws NoSectionException
276 {
277 Ini.Section section = requireSection(sectionName);
278
279 if (value == null)
280 {
281 section.remove(optionName);
282 }
283 else
284 {
285 section.put(optionName, value.toString());
286 }
287 }
288
289 public void write(Writer writer) throws IOException
290 {
291 _ini.store(writer);
292 }
293
294 public void write(OutputStream stream) throws IOException
295 {
296 _ini.store(stream);
297 }
298
299 public void write(File file) throws IOException
300 {
301 _ini.store(new FileWriter(file));
302 }
303
304 protected Ini getIni()
305 {
306 return _ini;
307 }
308
309 private String requireOption(String sectionName, String optionName) throws NoSectionException, NoOptionException
310 {
311 Ini.Section section = requireSection(sectionName);
312 String option = section.get(optionName);
313
314 if (option == null)
315 {
316 throw new NoOptionException(optionName);
317 }
318
319 return option;
320 }
321
322 private Ini.Section requireSection(String sectionName) throws NoSectionException
323 {
324 Ini.Section section = _ini.get(sectionName);
325
326 if (section == null)
327 {
328 throw new NoSectionException(sectionName);
329 }
330
331 return section;
332 }
333
334 public static class ConfigParserException extends Exception
335 {
336
337 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -6845546313519392093L;
338
339 public ConfigParserException(String message)
340 {
341 super(message);
342 }
343 }
344
345 public static final class DuplicateSectionException extends ConfigParserException
346 {
347
348 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -5244008445735700699L;
349
350 private DuplicateSectionException(String message)
351 {
352 super(message);
353 }
354 }
355
356 public static class InterpolationException extends ConfigParserException
357 {
358
359 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8924443303158546939L;
360
361 protected InterpolationException(String message)
362 {
363 super(message);
364 }
365 }
366
367 public static final class InterpolationMissingOptionException extends InterpolationException
368 {
369
370 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 2903136975820447879L;
371
372 private InterpolationMissingOptionException(String message)
373 {
374 super(message);
375 }
376 }
377
378 public static final class NoOptionException extends ConfigParserException
379 {
380
381 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8460082078809425858L;
382
383 private NoOptionException(String message)
384 {
385 super(message);
386 }
387 }
388
389 public static final class NoSectionException extends ConfigParserException
390 {
391
392 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = 8553627727493146118L;
393
394 private NoSectionException(String message)
395 {
396 super(message);
397 }
398 }
399
400 public static final class ParsingException extends IOException
401 {
402
403 /** Use serialVersionUID for interoperability. */ private static final long serialVersionUID = -5395990242007205038L;
404
405 private ParsingException(Throwable cause)
406 {
407 super(cause.getMessage(), cause);
408 }
409 }
410
411 static class PyIni extends Ini
412 {
413 private static final char SUBST_CHAR = '%';
414 private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\%\\(([^\\)]+)\\)");
415 private static final int G_OPTION = 1;
416 protected static final String DEFAULT_SECTION_NAME = "DEFAULT";
417 private static final long serialVersionUID = -7152857626328996122L;
418 private final Map<String, String> _defaults;
419 private Ini.Section _defaultSection;
420
421 public PyIni(Map<String, String> defaults)
422 {
423 _defaults = defaults;
424 Config cfg = getConfig().clone();
425
426 cfg.setEscape(false);
427 cfg.setMultiOption(false);
428 cfg.setMultiSection(false);
429 cfg.setLowerCaseOption(true);
430 cfg.setLowerCaseSection(true);
431 super.setConfig(cfg);
432 }
433
434 @Override public void setConfig(Config value)
435 {
436 assert true;
437 }
438
439 public Map<String, String> getDefaults()
440 {
441 return _defaults;
442 }
443
444 @Override public Section add(String name)
445 {
446 Section section;
447
448 if (DEFAULT_SECTION_NAME.equalsIgnoreCase(name))
449 {
450 if (_defaultSection == null)
451 {
452 _defaultSection = newSection(name);
453 }
454
455 section = _defaultSection;
456 }
457 else
458 {
459 section = super.add(name);
460 }
461
462 return section;
463 }
464
465 public String fetch(String sectionName, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
466 {
467 return fetch(get(sectionName), optionName, variables);
468 }
469
470 protected Ini.Section getDefaultSection()
471 {
472 return _defaultSection;
473 }
474
475 protected String fetch(Ini.Section section, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
476 {
477 String value = section.get(optionName);
478
479 if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
480 {
481 StringBuilder buffer = new StringBuilder(value);
482
483 resolve(buffer, section, variables);
484 value = buffer.toString();
485 }
486
487 return value;
488 }
489
490 protected void resolve(StringBuilder buffer, Ini.Section owner, Map<String, String> vars) throws InterpolationMissingOptionException
491 {
492 Matcher m = EXPRESSION.matcher(buffer);
493
494 while (m.find())
495 {
496 String optionName = m.group(G_OPTION);
497 String value = owner.get(optionName);
498
499 if (value == null)
500 {
501 value = vars.get(optionName);
502 }
503
504 if (value == null)
505 {
506 value = _defaults.get(optionName);
507 }
508
509 if ((value == null) && (_defaultSection != null))
510 {
511 value = _defaultSection.get(optionName);
512 }
513
514 if (value == null)
515 {
516 throw new InterpolationMissingOptionException(optionName);
517 }
518
519 buffer.replace(m.start(), m.end(), value);
520 m.reset(buffer);
521 }
522 }
523
524 @Override protected void store(IniHandler formatter)
525 {
526 formatter.startIni();
527 if (_defaultSection != null)
528 {
529 store(formatter, _defaultSection);
530 }
531
532 for (Ini.Section s : values())
533 {
534 store(formatter, s);
535 }
536
537 formatter.endIni();
538 }
539
540 @Override protected void store(IniHandler formatter, Section section)
541 {
542 formatter.startSection(section.getName());
543 for (String name : section.keySet())
544 {
545 formatter.handleOption(name, section.get(name));
546 }
547
548 formatter.endSection();
549 }
550 }
551 }