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