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 }