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) 067 || "off".equalsIgnoreCase(value)) 068 { 069 ret = false; 070 } 071 else 072 { 073 throw new IllegalArgumentException(value); 074 } 075 076 return ret; 077 } 078 079 public double getDouble(String section, String option) throws NoSectionException, NoOptionException, InterpolationException 080 { 081 return Double.parseDouble(get(section, option)); 082 } 083 084 public float getFloat(String section, String option) throws NoSectionException, NoOptionException, InterpolationException 085 { 086 return Float.parseFloat(get(section, option)); 087 } 088 089 public int getInt(String section, String option) throws NoSectionException, NoOptionException, InterpolationException 090 { 091 return Integer.parseInt(get(section, option)); 092 } 093 094 public long getLong(String section, String option) throws NoSectionException, NoOptionException, InterpolationException 095 { 096 return Long.parseLong(get(section, option)); 097 } 098 099 public void addSection(String section) throws DuplicateSectionException 100 { 101 if (_ini.containsKey(section)) 102 { 103 throw new DuplicateSectionException(section); 104 } 105 else if (PyIni.DEFAULT_SECTION_NAME.equalsIgnoreCase(section)) 106 { 107 throw new IllegalArgumentException(section); 108 } 109 110 _ini.add(section); 111 } 112 113 public Map<String, String> defaults() 114 { 115 return _ini.getDefaults(); 116 } 117 118 @SuppressWarnings(Warnings.UNCHECKED) 119 public String get(String section, String option) throws NoSectionException, NoOptionException, InterpolationException 120 { 121 return get(section, option, false, Collections.EMPTY_MAP); 122 } 123 124 @SuppressWarnings(Warnings.UNCHECKED) 125 public String get(String section, String option, boolean raw) throws NoSectionException, NoOptionException, InterpolationException 126 { 127 return get(section, option, raw, Collections.EMPTY_MAP); 128 } 129 130 public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException, 131 NoOptionException, InterpolationException 132 { 133 String value = requireOption(sectionName, optionName); 134 135 if (!raw && (value != null) && (value.indexOf(PyIni.SUBST_CHAR) >= 0)) 136 { 137 value = _ini.fetch(sectionName, optionName, variables); 138 } 139 140 return value; 141 } 142 143 public boolean hasOption(String sectionName, String optionName) 144 { 145 Ini.Section section = _ini.get(sectionName); 146 147 return (section != null) && section.containsKey(optionName); 148 } 149 150 public boolean hasSection(String sectionName) 151 { 152 return _ini.containsKey(sectionName); 153 } 154 155 @SuppressWarnings(Warnings.UNCHECKED) 156 public List<Map.Entry<String, String>> items(String sectionName) throws NoSectionException, InterpolationMissingOptionException 157 { 158 return items(sectionName, false, Collections.EMPTY_MAP); 159 } 160 161 @SuppressWarnings(Warnings.UNCHECKED) 162 public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException, 163 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 (InvalidFileFormatException 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 (InvalidFileFormatException 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 (InvalidFileFormatException 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 (InvalidFileFormatException 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 Ini 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()); 410 initCause(cause); 411 } 412 } 413 414 static class PyIni extends Ini 415 { 416 private static final char SUBST_CHAR = '%'; 417 private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\%\\(([^\\)]+)\\)"); 418 private static final int G_OPTION = 1; 419 protected static final String DEFAULT_SECTION_NAME = "DEFAULT"; 420 private static final long serialVersionUID = -7152857626328996122L; 421 private final Map<String, String> _defaults; 422 private Ini.Section _defaultSection; 423 424 public PyIni(Map<String, String> defaults) 425 { 426 _defaults = defaults; 427 Config cfg = getConfig().clone(); 428 429 cfg.setEscape(false); 430 cfg.setMultiOption(false); 431 cfg.setMultiSection(false); 432 cfg.setLowerCaseOption(true); 433 cfg.setLowerCaseSection(true); 434 super.setConfig(cfg); 435 } 436 437 @Override public void setConfig(Config value) 438 { 439 assert true; 440 } 441 442 public Map<String, String> getDefaults() 443 { 444 return _defaults; 445 } 446 447 @Override public Section add(String name) 448 { 449 Section section; 450 451 if (DEFAULT_SECTION_NAME.equalsIgnoreCase(name)) 452 { 453 if (_defaultSection == null) 454 { 455 _defaultSection = newSection(name); 456 } 457 458 section = _defaultSection; 459 } 460 else 461 { 462 section = super.add(name); 463 } 464 465 return section; 466 } 467 468 public String fetch(String sectionName, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException 469 { 470 return fetch(get(sectionName), optionName, variables); 471 } 472 473 protected Ini.Section getDefaultSection() 474 { 475 return _defaultSection; 476 } 477 478 protected String fetch(Ini.Section section, String optionName, Map<String, String> variables) 479 throws InterpolationMissingOptionException 480 { 481 String value = section.get(optionName); 482 483 if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0)) 484 { 485 StringBuilder buffer = new StringBuilder(value); 486 487 resolve(buffer, section, variables); 488 value = buffer.toString(); 489 } 490 491 return value; 492 } 493 494 protected void resolve(StringBuilder buffer, Ini.Section owner, Map<String, String> vars) throws InterpolationMissingOptionException 495 { 496 Matcher m = EXPRESSION.matcher(buffer); 497 498 while (m.find()) 499 { 500 String optionName = m.group(G_OPTION); 501 String value = owner.get(optionName); 502 503 if (value == null) 504 { 505 value = vars.get(optionName); 506 } 507 508 if (value == null) 509 { 510 value = _defaults.get(optionName); 511 } 512 513 if ((value == null) && (_defaultSection != null)) 514 { 515 value = _defaultSection.get(optionName); 516 } 517 518 if (value == null) 519 { 520 throw new InterpolationMissingOptionException(optionName); 521 } 522 523 buffer.replace(m.start(), m.end(), value); 524 m.reset(buffer); 525 } 526 } 527 528 @Override protected void store(IniHandler formatter) 529 { 530 formatter.startIni(); 531 if (_defaultSection != null) 532 { 533 store(formatter, _defaultSection); 534 } 535 536 for (Ini.Section s : values()) 537 { 538 store(formatter, s); 539 } 540 541 formatter.endIni(); 542 } 543 544 @Override protected void store(IniHandler formatter, Section section) 545 { 546 formatter.startSection(section.getName()); 547 for (String name : section.keySet()) 548 { 549 formatter.handleOption(name, section.get(name)); 550 } 551 552 formatter.endSection(); 553 } 554 } 555 }