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 }