001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.config; 014 015import static org.apache.juneau.config.ConfigMod.*; 016import static org.apache.juneau.internal.StringUtils.*; 017import static org.apache.juneau.internal.ThrowableUtils.*; 018 019import java.beans.*; 020import java.io.*; 021import java.lang.reflect.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.config.encode.*; 026import org.apache.juneau.config.encode.ConfigEncoder; 027import org.apache.juneau.config.event.*; 028import org.apache.juneau.config.internal.*; 029import org.apache.juneau.config.store.*; 030import org.apache.juneau.config.vars.*; 031import org.apache.juneau.http.*; 032import org.apache.juneau.internal.*; 033import org.apache.juneau.json.*; 034import org.apache.juneau.parser.*; 035import org.apache.juneau.serializer.*; 036import org.apache.juneau.svl.*; 037 038/** 039 * Main configuration API class. 040 * 041 * <h5 class='section'>See Also:</h5> 042 * <ul class='doctree'> 043 * <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-config'>Overview > juneau-config</a> 044 * </ul> 045 */ 046public final class Config extends Context implements ConfigEventListener, Writable { 047 048 //------------------------------------------------------------------------------------------------------------------- 049 // Configurable properties 050 //------------------------------------------------------------------------------------------------------------------- 051 052 private static final String PREFIX = "Config."; 053 054 /** 055 * Configuration property: Configuration name. 056 * 057 * <h5 class='section'>Property:</h5> 058 * <ul> 059 * <li><b>Name:</b> <js>"Config.name.s"</js> 060 * <li><b>Data type:</b> <code>String</code> 061 * <li><b>Default:</b> <js>"Configuration.cfg"</js> 062 * <li><b>Methods:</b> 063 * <ul> 064 * <li class='jm'>{@link ConfigBuilder#name(String)} 065 * </ul> 066 * </ul> 067 * 068 * <h5 class='section'>Description:</h5> 069 * <p> 070 * Specifies the configuration name. 071 * <br>This is typically the configuration file name, although 072 * the name can be anything identifiable by the {@link ConfigStore} used for retrieving and storing the configuration. 073 */ 074 public static final String CONFIG_name = PREFIX + "name.s"; 075 076 /** 077 * Configuration property: Configuration store. 078 * 079 * <h5 class='section'>Property:</h5> 080 * <ul> 081 * <li><b>Name:</b> <js>"Config.store.o"</js> 082 * <li><b>Data type:</b> {@link ConfigStore} 083 * <li><b>Default:</b> {@link ConfigFileStore#DEFAULT} 084 * <li><b>Methods:</b> 085 * <ul> 086 * <li class='jm'>{@link ConfigBuilder#store(ConfigStore)} 087 * </ul> 088 * </ul> 089 * 090 * <h5 class='section'>Description:</h5> 091 * <p> 092 * The configuration store used for retrieving and storing configurations. 093 */ 094 public static final String CONFIG_store = PREFIX + "store.o"; 095 096 /** 097 * Configuration property: POJO serializer. 098 * 099 * <h5 class='section'>Property:</h5> 100 * <ul> 101 * <li><b>Name:</b> <js>"Config.serializer.o"</js> 102 * <li><b>Data type:</b> {@link WriterSerializer} 103 * <li><b>Default:</b> {@link JsonSerializer#DEFAULT_LAX} 104 * <li><b>Methods:</b> 105 * <ul> 106 * <li class='jm'>{@link ConfigBuilder#serializer(Class)} 107 * <li class='jm'>{@link ConfigBuilder#serializer(WriterSerializer)} 108 * </ul> 109 * </ul> 110 * 111 * <h5 class='section'>Description:</h5> 112 * <p> 113 * The serializer to use for serializing POJO values. 114 */ 115 public static final String CONFIG_serializer = PREFIX + "serializer.o"; 116 117 /** 118 * Configuration property: POJO parser. 119 * 120 * <h5 class='section'>Property:</h5> 121 * <ul> 122 * <li><b>Name:</b> <js>"Config.parser.o"</js> 123 * <li><b>Data type:</b> {@link ReaderParser} 124 * <li><b>Default:</b> {@link JsonParser#DEFAULT} 125 * <li><b>Methods:</b> 126 * <ul> 127 * <li class='jm'>{@link ConfigBuilder#parser(Class)} 128 * <li class='jm'>{@link ConfigBuilder#parser(ReaderParser)} 129 * </ul> 130 * </ul> 131 * 132 * <h5 class='section'>Description:</h5> 133 * <p> 134 * The parser to use for parsing values to POJOs. 135 */ 136 public static final String CONFIG_parser = PREFIX + "parser.o"; 137 138 /** 139 * Configuration property: Value encoder. 140 * 141 * <h5 class='section'>Property:</h5> 142 * <ul> 143 * <li><b>Name:</b> <js>"Config.encoder.o"</js> 144 * <li><b>Data type:</b> {@link ConfigEncoder} 145 * <li><b>Default:</b> {@link ConfigXorEncoder#INSTANCE} 146 * <li><b>Methods:</b> 147 * <ul> 148 * <li class='jm'>{@link ConfigBuilder#encoder(Class)} 149 * <li class='jm'>{@link ConfigBuilder#encoder(ConfigEncoder)} 150 * </ul> 151 * </ul> 152 * 153 * <h5 class='section'>Description:</h5> 154 * <p> 155 * The encoder to use for encoding encoded configuration values. 156 */ 157 public static final String CONFIG_encoder = PREFIX + "encoder.o"; 158 159 /** 160 * Configuration property: SVL variable resolver. 161 * 162 * <h5 class='section'>Property:</h5> 163 * <ul> 164 * <li><b>Name:</b> <js>"Config.varResolver.o"</js> 165 * <li><b>Data type:</b> {@link VarResolver} 166 * <li><b>Default:</b> {@link VarResolver#DEFAULT} 167 * <li><b>Methods:</b> 168 * <ul> 169 * <li class='jm'>{@link ConfigBuilder#varResolver(Class)} 170 * <li class='jm'>{@link ConfigBuilder#varResolver(VarResolver)} 171 * </ul> 172 * </ul> 173 * 174 * <h5 class='section'>Description:</h5> 175 * <p> 176 * The resolver to use for resolving SVL variables. 177 */ 178 public static final String CONFIG_varResolver = PREFIX + "varResolver.o"; 179 180 /** 181 * Configuration property: Binary value line length. 182 * 183 * <h5 class='section'>Property:</h5> 184 * <ul> 185 * <li><b>Name:</b> <js>"Config.binaryLineLength.i"</js> 186 * <li><b>Data type:</b> <code>Integer</code> 187 * <li><b>Default:</b> <code>-1</code> 188 * <li><b>Methods:</b> 189 * <ul> 190 * <li class='jm'>{@link ConfigBuilder#binaryLineLength(int)} 191 * </ul> 192 * </ul> 193 * 194 * <h5 class='section'>Description:</h5> 195 * <p> 196 * When serializing binary values, lines will be split after this many characters. 197 * <br>Use <code>-1</code> to represent no line splitting. 198 */ 199 public static final String CONFIG_binaryLineLength = PREFIX + "binaryLineLength.i"; 200 201 /** 202 * Configuration property: Binary value format. 203 * 204 * <h5 class='section'>Property:</h5> 205 * <ul> 206 * <li><b>Name:</b> <js>"Config.binaryFormat.s"</js> 207 * <li><b>Data type:</b> <code>String</code> 208 * <li><b>Default:</b> <js>"BASE64"</js> 209 * <li><b>Methods:</b> 210 * <ul> 211 * <li class='jm'>{@link ConfigBuilder#binaryFormat(String)} 212 * </ul> 213 * </ul> 214 * 215 * <h5 class='section'>Description:</h5> 216 * <p> 217 * The format to use when persisting byte arrays. 218 * 219 * <p> 220 * Possible values: 221 * <ul> 222 * <li><js>"BASE64"</js> - BASE64-encoded string. 223 * <li><js>"HEX"</js> - Hexadecimal. 224 * <li><js>"SPACED_HEX"</js> - Hexadecimal with spaces between bytes. 225 * </ul> 226 */ 227 public static final String CONFIG_binaryFormat = PREFIX + "binaryFormat.s"; 228 229 /** 230 * Configuration property: Multi-line values should always be on separate lines. 231 * 232 * <h5 class='section'>Property:</h5> 233 * <ul> 234 * <li><b>Name:</b> <js>"Config.multiLineValuesOnSeparateLines.b"</js> 235 * <li><b>Data type:</b> <code>Boolean</code> 236 * <li><b>Default:</b> <jk>false</jk> 237 * <li><b>Methods:</b> 238 * <ul> 239 * <li class='jm'>{@link ConfigBuilder#multiLineValuesOnSeparateLines()} 240 * </ul> 241 * </ul> 242 * 243 * <h5 class='section'>Description:</h5> 244 * <p> 245 * When enabled, multi-line values will always be placed on a separate line from the key. 246 */ 247 public static final String CONFIG_multiLineValuesOnSeparateLines = PREFIX + "multiLineValuesOnSeparateLines.b"; 248 249 /** 250 * Configuration property: Read-only. 251 * 252 * <h5 class='section'>Property:</h5> 253 * <ul> 254 * <li><b>Name:</b> <js>"Config.readOnly.b"</js> 255 * <li><b>Data type:</b> <code>Boolean</code> 256 * <li><b>Default:</b> <jk>false</jk> 257 * <li><b>Methods:</b> 258 * <ul> 259 * <li class='jm'>{@link ConfigBuilder#readOnly()} 260 * </ul> 261 * </ul> 262 * 263 * <h5 class='section'>Description:</h5> 264 * <p> 265 * When enabled, attempts to call any setters on this object will throw an {@link UnsupportedOperationException}. 266 */ 267 public static final String CONFIG_readOnly = PREFIX + "readOnly.b"; 268 269 //------------------------------------------------------------------------------------------------------------------- 270 // Instance 271 //------------------------------------------------------------------------------------------------------------------- 272 273 private final String name; 274 private final ConfigStore store; 275 private final WriterSerializer serializer; 276 private final ReaderParser parser; 277 private final ConfigEncoder encoder; 278 private final VarResolverSession varSession; 279 private final int binaryLineLength; 280 private final String binaryFormat; 281 private final boolean multiLineValuesOnSeparateLines, readOnly; 282 private final ConfigMap configMap; 283 private final BeanSession beanSession; 284 private final List<ConfigEventListener> listeners = Collections.synchronizedList(new LinkedList<ConfigEventListener>()); 285 286 287 /** 288 * Instantiates a new clean-slate {@link ConfigBuilder} object. 289 * 290 * <p> 291 * This is equivalent to simply calling <code><jk>new</jk> ConfigBuilder()</code>. 292 * 293 * @return A new {@link ConfigBuilder} object. 294 */ 295 public static ConfigBuilder create() { 296 return new ConfigBuilder(); 297 } 298 299 /** 300 * Same as {@link #create()} but initializes the builder with the specified config name. 301 * 302 * <p> 303 * This is equivalent to simply calling <code><jk>new</jk> ConfigBuilder().name(name)</code>. 304 * 305 * @param name The configuration name. 306 * @return A new {@link ConfigBuilder} object. 307 */ 308 public static ConfigBuilder create(String name) { 309 return new ConfigBuilder().name(name); 310 } 311 312 @Override /* Context */ 313 public ConfigBuilder builder() { 314 return new ConfigBuilder(getPropertyStore()); 315 } 316 317 /** 318 * Constructor. 319 * 320 * @param ps 321 * The property store containing all the settings for this object. 322 * @throws IOException 323 */ 324 public Config(PropertyStore ps) throws IOException { 325 super(ps); 326 327 name = getStringProperty(CONFIG_name, "Configuration.cfg"); 328 store = getInstanceProperty(CONFIG_store, ConfigStore.class, ConfigFileStore.DEFAULT); 329 configMap = store.getMap(name); 330 configMap.register(this); 331 serializer = getInstanceProperty(CONFIG_serializer, WriterSerializer.class, JsonSerializer.DEFAULT_LAX); 332 parser = getInstanceProperty(CONFIG_parser, ReaderParser.class, JsonParser.DEFAULT); 333 beanSession = parser.createBeanSession(); 334 encoder = getInstanceProperty(CONFIG_encoder, ConfigEncoder.class, ConfigXorEncoder.INSTANCE); 335 varSession = getInstanceProperty(CONFIG_varResolver, VarResolver.class, VarResolver.DEFAULT) 336 .builder() 337 .vars(ConfigVar.class) 338 .contextObject(ConfigVar.SESSION_config, this) 339 .build() 340 .createSession(); 341 binaryLineLength = getIntegerProperty(CONFIG_binaryLineLength, -1); 342 binaryFormat = getStringProperty(CONFIG_binaryFormat, "BASE64").toUpperCase(); 343 multiLineValuesOnSeparateLines = getBooleanProperty(CONFIG_multiLineValuesOnSeparateLines, false); 344 readOnly = getBooleanProperty(CONFIG_readOnly, false); 345 } 346 347 Config(Config copyFrom, VarResolverSession varSession) { 348 super(null); 349 name = copyFrom.name; 350 store = copyFrom.store; 351 configMap = copyFrom.configMap; 352 configMap.register(this); 353 serializer = copyFrom.serializer; 354 parser = copyFrom.parser; 355 encoder = copyFrom.encoder; 356 this.varSession = varSession; 357 binaryLineLength = copyFrom.binaryLineLength; 358 binaryFormat = copyFrom.binaryFormat; 359 multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; 360 readOnly = copyFrom.readOnly; 361 beanSession = copyFrom.beanSession; 362 } 363 364 /** 365 * Creates a copy of this config using the specified var session for resolving variables. 366 * 367 * <p> 368 * This creates a shallow copy of the config but replacing the variable resolver. 369 * 370 * @param varSession The var session used for resolving string variables. 371 * @return A new config object. 372 */ 373 public Config resolving(VarResolverSession varSession) { 374 return new Config(this, varSession); 375 } 376 377 378 //-------------------------------------------------------------------------------- 379 // Workhorse getters 380 //-------------------------------------------------------------------------------- 381 382 /** 383 * Returns the specified value as a string from the config file. 384 * 385 * <p> 386 * Unlike {@link #getString(String)}, this method doesn't replace SVL variables. 387 * 388 * @param key The key. 389 * @return The value, or <jk>null</jk> if the section or value doesn't exist. 390 */ 391 public String get(String key) { 392 393 String sname = sname(key); 394 String skey = skey(key); 395 396 ConfigEntry ce = configMap.getEntry(sname, skey); 397 398 if (ce == null || ce.getValue() == null) 399 return null; 400 401 String val = ce.getValue(); 402 for (ConfigMod m : ConfigMod.asModifiersReverse(ce.getModifiers())) { 403 if (m == ENCODED) { 404 if (encoder.isEncoded(val)) 405 val = encoder.decode(key, val); 406 } 407 } 408 409 return val; 410 } 411 412 413 //-------------------------------------------------------------------------------- 414 // Workhorse setters 415 //-------------------------------------------------------------------------------- 416 417 /** 418 * Sets a value in this config. 419 * 420 * @param key The key. 421 * @param value The value. 422 * @return This object (for method chaining). 423 * @throws UnsupportedOperationException If configuration is read only. 424 */ 425 public Config set(String key, String value) { 426 checkWrite(); 427 assertFieldNotNull(key, "key"); 428 String sname = sname(key); 429 String skey = skey(key); 430 431 ConfigEntry ce = configMap.getEntry(sname, skey); 432 if (ce == null && value == null) 433 return this; 434 435 String mod = ce == null ? "" : ce.getModifiers(); 436 437 String s = asString(value); 438 for (ConfigMod m : ConfigMod.asModifiers(mod)) { 439 if (m == ENCODED) { 440 s = encoder.encode(key, s); 441 } 442 } 443 444 configMap.setEntry(sname, skey, s, null, null, null); 445 return this; 446 } 447 448 /** 449 * Adds or replaces an entry with the specified key with a POJO serialized to a string using the registered 450 * serializer. 451 * 452 * <p> 453 * Equivalent to calling <code>put(key, value, isEncoded(key))</code>. 454 * 455 * @param key The key. 456 * @param value The new value POJO. 457 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 458 * @throws SerializeException 459 * If serializer could not serialize the value or if a serializer is not registered with this config file. 460 * @throws UnsupportedOperationException If configuration is read only. 461 */ 462 public Config set(String key, Object value) throws SerializeException { 463 return set(key, value, null); 464 } 465 466 /** 467 * Same as {@link #set(String, Object)} but allows you to specify the serializer to use to serialize the 468 * value. 469 * 470 * @param key The key. 471 * @param value The new value. 472 * @param serializer 473 * The serializer to use for serializing the object. 474 * If <jk>null</jk>, then uses the predefined serializer on the config file. 475 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 476 * @throws SerializeException 477 * If serializer could not serialize the value or if a serializer is not registered with this config file. 478 * @throws UnsupportedOperationException If configuration is read only. 479 */ 480 public Config set(String key, Object value, Serializer serializer) throws SerializeException { 481 return set(key, serialize(value, serializer)); 482 } 483 484 /** 485 * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value. 486 * 487 * @param key The key. 488 * @param value The new value. 489 * @param serializer 490 * The serializer to use for serializing the object. 491 * If <jk>null</jk>, then uses the predefined serializer on the config file. 492 * @param modifier 493 * Optional modifier to apply to the value. 494 * <br>If <jk>null</jk>, then previous value will not be replaced. 495 * @param comment 496 * Optional same-line comment to add to this value. 497 * <br>If <jk>null</jk>, then previous value will not be replaced. 498 * @param preLines 499 * Optional comment or blank lines to add before this entry. 500 * <br>If <jk>null</jk>, then previous value will not be replaced. 501 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 502 * @throws SerializeException 503 * If serializer could not serialize the value or if a serializer is not registered with this config file. 504 * @throws UnsupportedOperationException If configuration is read only. 505 */ 506 public Config set(String key, Object value, Serializer serializer, ConfigMod modifier, String comment, List<String> preLines) throws SerializeException { 507 return set(key, value, serializer, modifier == null ? null : new ConfigMod[]{modifier}, comment, preLines); 508 } 509 510 /** 511 * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value. 512 * 513 * @param key The key. 514 * @param value The new value. 515 * @param serializer 516 * The serializer to use for serializing the object. 517 * If <jk>null</jk>, then uses the predefined serializer on the config file. 518 * @param modifiers 519 * Optional modifiers to apply to the value. 520 * <br>If <jk>null</jk>, then previous value will not be replaced. 521 * @param comment 522 * Optional same-line comment to add to this value. 523 * <br>If <jk>null</jk>, then previous value will not be replaced. 524 * @param preLines 525 * Optional comment or blank lines to add before this entry. 526 * <br>If <jk>null</jk>, then previous value will not be replaced. 527 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 528 * @throws SerializeException 529 * If serializer could not serialize the value or if a serializer is not registered with this config file. 530 * @throws UnsupportedOperationException If configuration is read only. 531 */ 532 public Config set(String key, Object value, Serializer serializer, ConfigMod[] modifiers, String comment, List<String> preLines) throws SerializeException { 533 checkWrite(); 534 assertFieldNotNull(key, "key"); 535 String sname = sname(key); 536 String skey = skey(key); 537 538 String s = serialize(value, serializer); 539 if (modifiers != null) { 540 for (ConfigMod m : modifiers) { 541 if (m == ENCODED) { 542 s = encoder.encode(key, s); 543 } 544 } 545 } 546 547 configMap.setEntry(sname, skey, s, modifiers == null ? null : ConfigMod.asString(modifiers), comment, preLines); 548 return this; 549 } 550 551 /** 552 * Removes an entry with the specified key. 553 * 554 * @param key The key. 555 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 556 * @throws UnsupportedOperationException If configuration is read only. 557 */ 558 public Config remove(String key) { 559 checkWrite(); 560 String sname = sname(key); 561 String skey = skey(key); 562 configMap.removeEntry(sname, skey); 563 return this; 564 } 565 566 /** 567 * Encodes and unencoded entries in this config. 568 * 569 * <p> 570 * If any entries in the config are marked as encoded but not actually encoded, 571 * this will encode them. 572 * 573 * @return This object (for method chaining). 574 * @throws UnsupportedOperationException If configuration is read only. 575 */ 576 public Config encodeEntries() { 577 checkWrite(); 578 for (String section : configMap.getSections()) { 579 for (String key : configMap.getKeys(section)) { 580 ConfigEntry ce = configMap.getEntry(section, key); 581 if (ce != null && ce.hasModifier('*') && ! encoder.isEncoded(ce.getValue())) { 582 configMap.setEntry(section, key, encoder.encode(section + '/' + key, ce.getValue()), null, null, null); 583 } 584 } 585 } 586 587 return this; 588 } 589 590 //-------------------------------------------------------------------------------- 591 // API methods 592 //-------------------------------------------------------------------------------- 593 594 /** 595 * Gets the entry with the specified key. 596 * 597 * <p> 598 * The key can be in one of the following formats... 599 * <ul class='spaced-list'> 600 * <li> 601 * <js>"key"</js> - A value in the default section (i.e. defined above any <code>[section]</code> header). 602 * <li> 603 * <js>"section/key"</js> - A value from the specified section. 604 * </ul> 605 * 606 * @param key The key. 607 * @return The value, or <jk>null</jk> if the section or key does not exist. 608 */ 609 public String getString(String key) { 610 String s = get(key); 611 if (s == null) 612 return null; 613 if (varSession != null) 614 s = varSession.resolve(s); 615 return s; 616 } 617 618 /** 619 * Gets the entry with the specified key. 620 * 621 * <p> 622 * The key can be in one of the following formats... 623 * <ul class='spaced-list'> 624 * <li> 625 * <js>"key"</js> - A value in the default section (i.e. defined above any <code>[section]</code> header). 626 * <li> 627 * <js>"section/key"</js> - A value from the specified section. 628 * </ul> 629 * 630 * @param key The key. 631 * @param def The default value if the value does not exist. 632 * @return The value, or the default value if the section or key does not exist. 633 */ 634 public String getString(String key, String def) { 635 String s = get(key); 636 if (isEmpty(s)) 637 return def; 638 if (varSession != null) 639 s = varSession.resolve(s); 640 return s; 641 } 642 643 /** 644 * Gets the entry with the specified key, splits the value on commas, and returns the values as trimmed strings. 645 * 646 * @param key The key. 647 * @return The value, or an empty array if the section or key does not exist. 648 */ 649 public String[] getStringArray(String key) { 650 return getStringArray(key, new String[0]); 651 } 652 653 /** 654 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. 655 * 656 * @param key The key. 657 * @param def The default value if the value does not exist. 658 * @return The value, or the default value if the section or key does not exist or is blank. 659 */ 660 public String[] getStringArray(String key, String[] def) { 661 String s = getString(key); 662 if (isEmpty(s)) 663 return def; 664 String[] r = split(s); 665 return r.length == 0 ? def : r; 666 } 667 668 /** 669 * Convenience method for getting int config values. 670 * 671 * <p> 672 * <js>"K"</js>, <js>"M"</js>, and <js>"G"</js> can be used to identify kilo, mega, and giga. 673 * 674 * <h5 class='section'>Example:</h5> 675 * <ul class='spaced-list'> 676 * <li> 677 * <code><js>"100K"</js> => 1024000</code> 678 * <li> 679 * <code><js>"100M"</js> => 104857600</code> 680 * </ul> 681 * 682 * <p> 683 * Uses {@link Integer#decode(String)} underneath, so any of the following integer formats are supported: 684 * <ul> 685 * <li><js>"0x..."</js> 686 * <li><js>"0X..."</js> 687 * <li><js>"#..."</js> 688 * <li><js>"0..."</js> 689 * </ul> 690 * 691 * @param key The key. 692 * @return The value, or <code>0</code> if the value does not exist or the value is empty. 693 */ 694 public int getInt(String key) { 695 return getInt(key, 0); 696 } 697 698 /** 699 * Same as {@link #getInt(String)} but returns a default value if not set. 700 * 701 * @param key The key. 702 * @param def The default value if the value does not exist. 703 * @return The value, or the default value if the value does not exist or the value is empty. 704 */ 705 public int getInt(String key, int def) { 706 String s = getString(key); 707 if (isEmpty(s)) 708 return def; 709 return parseIntWithSuffix(s); 710 } 711 712 /** 713 * Convenience method for getting boolean config values. 714 * 715 * @param key The key. 716 * @return The value, or <jk>false</jk> if the section or key does not exist or cannot be parsed as a boolean. 717 */ 718 public boolean getBoolean(String key) { 719 return getBoolean(key, false); 720 } 721 722 /** 723 * Convenience method for getting boolean config values. 724 * 725 * @param key The key. 726 * @param def The default value if the value does not exist. 727 * @return The value, or the default value if the section or key does not exist or cannot be parsed as a boolean. 728 */ 729 public boolean getBoolean(String key, boolean def) { 730 String s = getString(key); 731 return isEmpty(s) ? def : Boolean.parseBoolean(s); 732 } 733 734 /** 735 * Convenience method for getting long config values. 736 * 737 * <p> 738 * <js>"K"</js>, <js>"M"</js>, and <js>"G"</js> can be used to identify kilo, mega, and giga. 739 * 740 * <h5 class='section'>Example:</h5> 741 * <ul class='spaced-list'> 742 * <li> 743 * <code><js>"100K"</js> => 1024000</code> 744 * <li> 745 * <code><js>"100M"</js> => 104857600</code> 746 * </ul> 747 * 748 * <p> 749 * Uses {@link Long#decode(String)} underneath, so any of the following number formats are supported: 750 * <ul> 751 * <li><js>"0x..."</js> 752 * <li><js>"0X..."</js> 753 * <li><js>"#..."</js> 754 * <li><js>"0..."</js> 755 * </ul> 756 * 757 * @param key The key. 758 * @return The value, or <code>0</code> if the value does not exist or the value is empty. 759 */ 760 public long getLong(String key) { 761 return getLong(key, 0); 762 } 763 764 /** 765 * Same as {@link #getLong(String)} but returns a default value if not set. 766 * 767 * @param key The key. 768 * @param def The default value if the value does not exist. 769 * @return The value, or the default value if the value does not exist or the value is empty. 770 */ 771 public long getLong(String key, long def) { 772 String s = getString(key); 773 if (isEmpty(s)) 774 return def; 775 return parseLongWithSuffix(s); 776 } 777 778 /** 779 * Convenience method for getting double config values. 780 * 781 * <p> 782 * Uses {@link Double#valueOf(String)} underneath, so any of the following number formats are supported: 783 * <ul> 784 * <li><js>"0x..."</js> 785 * <li><js>"0X..."</js> 786 * <li><js>"#..."</js> 787 * <li><js>"0..."</js> 788 * </ul> 789 * 790 * @param key The key. 791 * @return The value, or <code>0</code> if the value does not exist or the value is empty. 792 */ 793 public double getDouble(String key) { 794 return getDouble(key, 0); 795 } 796 797 /** 798 * Same as {@link #getDouble(String)} but returns a default value if not set. 799 * 800 * @param key The key. 801 * @param def The default value if the value does not exist. 802 * @return The value, or the default value if the value does not exist or the value is empty. 803 */ 804 public double getDouble(String key, double def) { 805 String s = getString(key); 806 if (isEmpty(s)) 807 return def; 808 return Double.valueOf(s); 809 } 810 811 /** 812 * Convenience method for getting float config values. 813 * 814 * <p> 815 * Uses {@link Float#valueOf(String)} underneath, so any of the following number formats are supported: 816 * <ul> 817 * <li><js>"0x..."</js> 818 * <li><js>"0X..."</js> 819 * <li><js>"#..."</js> 820 * <li><js>"0..."</js> 821 * </ul> 822 * 823 * @param key The key. 824 * @return The value, or <code>0</code> if the value does not exist or the value is empty. 825 */ 826 public float getFloat(String key) { 827 return getFloat(key, 0); 828 } 829 830 /** 831 * Same as {@link #getFloat(String)} but returns a default value if not set. 832 * 833 * @param key The key. 834 * @param def The default value if the value does not exist. 835 * @return The value, or the default value if the value does not exist or the value is empty. 836 */ 837 public float getFloat(String key, float def) { 838 String s = getString(key); 839 if (isEmpty(s)) 840 return def; 841 return Float.valueOf(s); 842 } 843 844 /** 845 * Convenience method for getting byte array config values. 846 * 847 * <p> 848 * This is equivalent to calling the following: 849 * <p class='bcode'> 850 * <jk>byte</jk>[] b = config.getObject(key, <jk>byte</jk>[].<jk>class</jk>); 851 * </p> 852 * 853 * Byte arrays are stored as encoded strings, typically BASE64, but dependent on the {@link #CONFIG_binaryFormat} setting. 854 * 855 * @param key The key. 856 * @return The value, or <jk>null</jk> if the section or key does not exist. 857 * @throws ParseException If value could not be converted to a byte array. 858 */ 859 public byte[] getBytes(String key) throws ParseException { 860 String s = get(key); 861 if (s == null) 862 return null; 863 if (s.isEmpty()) 864 return new byte[0]; 865 return getObject(key, byte[].class); 866 } 867 868 /** 869 * Same as {@link #getBytes(String)} but with a default value if the entry doesn't exist. 870 * 871 * @param key The key. 872 * @param def The default value if the value does not exist. 873 * @return The value, or the default value if the section or key does not exist. 874 * @throws ParseException If value could not be converted to a byte array. 875 */ 876 public byte[] getBytes(String key, byte[] def) throws ParseException { 877 String s = get(key); 878 if (s == null) 879 return def; 880 if (s.isEmpty()) 881 return def; 882 return getObjectWithDefault(key, def, byte[].class); 883 } 884 885 /** 886 * Gets the entry with the specified key and converts it to the specified value. 887 * 888 * <p> 889 * The key can be in one of the following formats... 890 * <ul class='spaced-list'> 891 * <li> 892 * <js>"key"</js> - A value in the default section (i.e. defined above any <code>[section]</code> header). 893 * <li> 894 * <js>"section/key"</js> - A value from the specified section. 895 * </ul> 896 * 897 * <p> 898 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 899 * 900 * <h5 class='section'>Examples:</h5> 901 * <p class='bcode'> 902 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 903 * 904 * <jc>// Parse into a linked-list of strings.</jc> 905 * List l = cf.getObject(<js>"MySection/myListOfStrings"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 906 * 907 * <jc>// Parse into a linked-list of beans.</jc> 908 * List l = cf.getObject(<js>"MySection/myListOfBeans"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 909 * 910 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 911 * List l = cf.getObject(<js>"MySection/my2dListOfStrings"</js>, LinkedList.<jk>class</jk>, 912 * LinkedList.<jk>class</jk>, String.<jk>class</jk>); 913 * 914 * <jc>// Parse into a map of string keys/values.</jc> 915 * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, 916 * String.<jk>class</jk>); 917 * 918 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 919 * Map m = cf.getObject(<js>"MySection/myMapOfListsOfBeans"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, 920 * List.<jk>class</jk>, MyBean.<jk>class</jk>); 921 * </p> 922 * 923 * <p> 924 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. 925 * 926 * <p> 927 * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value 928 * types. 929 * 930 * <p> 931 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 932 * 933 * <h5 class='section'>Notes:</h5> 934 * <ul class='spaced-list'> 935 * <li> 936 * Use the {@link #getObject(String, Class)} method instead if you don't need a parameterized map/collection. 937 * </ul> 938 * 939 * @param key The key. 940 * @param type 941 * The object type to create. 942 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 943 * @param args 944 * The type arguments of the class if it's a collection or map. 945 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 946 * <br>Ignored if the main type is not a map or collection. 947 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 948 * @return The value, or <jk>null</jk> if the section or key does not exist. 949 */ 950 public <T> T getObject(String key, Type type, Type...args) throws ParseException { 951 return getObject(key, (Parser)null, type, args); 952 } 953 954 /** 955 * Same as {@link #getObject(String, Type, Type...)} but allows you to specify the parser to use to parse the value. 956 * 957 * @param key The key. 958 * @param parser 959 * The parser to use for parsing the object. 960 * If <jk>null</jk>, then uses the predefined parser on the config file. 961 * @param type 962 * The object type to create. 963 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 964 * @param args 965 * The type arguments of the class if it's a collection or map. 966 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 967 * <br>Ignored if the main type is not a map or collection. 968 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 969 * @return The value, or <jk>null</jk> if the section or key does not exist. 970 */ 971 public <T> T getObject(String key, Parser parser, Type type, Type...args) throws ParseException { 972 assertFieldNotNull(type, "type"); 973 return parse(getString(key), parser, type, args); 974 } 975 976 /** 977 * Same as {@link #getObject(String, Type, Type...)} except optimized for a non-parameterized class. 978 * 979 * <p> 980 * This is the preferred parse method for simple types since you don't need to cast the results. 981 * 982 * <h5 class='section'>Examples:</h5> 983 * <p class='bcode'> 984 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 985 * 986 * <jc>// Parse into a string.</jc> 987 * String s = cf.getObject(<js>"MySection/mySimpleString"</js>, String.<jk>class</jk>); 988 * 989 * <jc>// Parse into a bean.</jc> 990 * MyBean b = cf.getObject(<js>"MySection/myBean"</js>, MyBean.<jk>class</jk>); 991 * 992 * <jc>// Parse into a bean array.</jc> 993 * MyBean[] b = cf.getObject(<js>"MySection/myBeanArray"</js>, MyBean[].<jk>class</jk>); 994 * 995 * <jc>// Parse into a linked-list of objects.</jc> 996 * List l = cf.getObject(<js>"MySection/myList"</js>, LinkedList.<jk>class</jk>); 997 * 998 * <jc>// Parse into a map of object keys/values.</jc> 999 * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>); 1000 * </p> 1001 * 1002 * @param <T> The class type of the object being created. 1003 * @param key The key. 1004 * @param type The object type to create. 1005 * @return The parsed object. 1006 * @throws ParseException 1007 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 1008 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. 1009 */ 1010 public <T> T getObject(String key, Class<T> type) throws ParseException { 1011 return getObject(key, (Parser)null, type); 1012 } 1013 1014 /** 1015 * Same as {@link #getObject(String, Class)} but allows you to specify the parser to use to parse the value. 1016 * 1017 * @param <T> The class type of the object being created. 1018 * @param key The key. 1019 * @param parser 1020 * The parser to use for parsing the object. 1021 * If <jk>null</jk>, then uses the predefined parser on the config file. 1022 * @param type The object type to create. 1023 * @return The parsed object. 1024 * @throws ParseException 1025 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 1026 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. 1027 */ 1028 public <T> T getObject(String key, Parser parser, Class<T> type) throws ParseException { 1029 assertFieldNotNull(type, "c"); 1030 return parse(getString(key), parser, type); 1031 } 1032 1033 /** 1034 * Gets the entry with the specified key and converts it to the specified value. 1035 * 1036 * <p> 1037 * Same as {@link #getObject(String, Class)}, but with a default value. 1038 * 1039 * @param key The key. 1040 * @param def The default value if the value does not exist. 1041 * @param type The class to convert the value to. 1042 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 1043 * @return The value, or <jk>null</jk> if the section or key does not exist. 1044 */ 1045 public <T> T getObjectWithDefault(String key, T def, Class<T> type) throws ParseException { 1046 return getObjectWithDefault(key, null, def, type); 1047 } 1048 1049 /** 1050 * Same as {@link #getObjectWithDefault(String, Object, Class)} but allows you to specify the parser to use to parse 1051 * the value. 1052 * 1053 * @param key The key. 1054 * @param parser 1055 * The parser to use for parsing the object. 1056 * If <jk>null</jk>, then uses the predefined parser on the config file. 1057 * @param def The default value if the value does not exist. 1058 * @param type The class to convert the value to. 1059 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 1060 * @return The value, or <jk>null</jk> if the section or key does not exist. 1061 */ 1062 public <T> T getObjectWithDefault(String key, Parser parser, T def, Class<T> type) throws ParseException { 1063 assertFieldNotNull(type, "c"); 1064 T t = parse(getString(key), parser, type); 1065 return (t == null ? def : t); 1066 } 1067 1068 /** 1069 * Gets the entry with the specified key and converts it to the specified value. 1070 * 1071 * <p> 1072 * Same as {@link #getObject(String, Type, Type...)}, but with a default value. 1073 * 1074 * @param key The key. 1075 * @param def The default value if the value does not exist. 1076 * @param type 1077 * The object type to create. 1078 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 1079 * @param args 1080 * The type arguments of the class if it's a collection or map. 1081 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 1082 * <br>Ignored if the main type is not a map or collection. 1083 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 1084 * @return The value, or <jk>null</jk> if the section or key does not exist. 1085 */ 1086 public <T> T getObjectWithDefault(String key, T def, Type type, Type...args) throws ParseException { 1087 return getObjectWithDefault(key, null, def, type, args); 1088 } 1089 1090 /** 1091 * Same as {@link #getObjectWithDefault(String, Object, Type, Type...)} but allows you to specify the parser to use 1092 * to parse the value. 1093 * 1094 * @param key The key. 1095 * @param parser 1096 * The parser to use for parsing the object. 1097 * If <jk>null</jk>, then uses the predefined parser on the config file. 1098 * @param def The default value if the value does not exist. 1099 * @param type 1100 * The object type to create. 1101 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 1102 * @param args 1103 * The type arguments of the class if it's a collection or map. 1104 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 1105 * <br>Ignored if the main type is not a map or collection. 1106 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. 1107 * @return The value, or <jk>null</jk> if the section or key does not exist. 1108 */ 1109 public <T> T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException { 1110 assertFieldNotNull(type, "type"); 1111 T t = parse(getString(key), parser, type, args); 1112 return (t == null ? def : t); 1113 } 1114 1115 /** 1116 * Returns the keys of the entries in the specified section. 1117 * 1118 * @param section 1119 * The section name to write from. 1120 * <br>If empty, refers to the default section. 1121 * <br>Must not be <jk>null</jk>. 1122 * @return 1123 * An unmodifiable set of keys, or an empty set if the section doesn't exist. 1124 */ 1125 public Set<String> getKeys(String section) { 1126 return configMap.getKeys(section(section)); 1127 } 1128 1129 /** 1130 * Copies the entries in a section to the specified bean by calling the public setters on that bean. 1131 * 1132 * @param section 1133 * The section name to write from. 1134 * <br>If empty, refers to the default section. 1135 * <br>Must not be <jk>null</jk>. 1136 * @param bean The bean to set the properties on. 1137 * @param ignoreUnknownProperties 1138 * If <jk>true</jk>, don't throw an {@link IllegalArgumentException} if this section contains a key that doesn't 1139 * correspond to a setter method. 1140 * @return An object map of the changes made to the bean. 1141 * @throws ParseException If parser was not set on this config file or invalid properties were found in the section. 1142 * @throws IllegalArgumentException 1143 * @throws IllegalAccessException 1144 * @throws InvocationTargetException 1145 * @throws UnsupportedOperationException If configuration is read only. 1146 */ 1147 public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties) throws ParseException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { 1148 checkWrite(); 1149 assertFieldNotNull(bean, "bean"); 1150 section = section(section); 1151 1152 Set<String> keys = configMap.getKeys(section); 1153 if (keys == null) 1154 throw new IllegalArgumentException("Section '"+section+"' not found in configuration."); 1155 1156 BeanMap<?> bm = beanSession.toBeanMap(bean); 1157 for (String k : keys) { 1158 BeanPropertyMeta bpm = bm.getPropertyMeta(k); 1159 if (bpm == null) { 1160 if (! ignoreUnknownProperties) 1161 throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); 1162 } else { 1163 bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); 1164 } 1165 } 1166 1167 return this; 1168 } 1169 1170 /** 1171 * Shortcut for calling <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>. 1172 * 1173 * @param section 1174 * The section name to write from. 1175 * <br>If empty, refers to the default section. 1176 * <br>Must not be <jk>null</jk>. 1177 * @param c The bean class to create. 1178 * @return A new bean instance. 1179 * @throws ParseException 1180 */ 1181 public <T> T getSectionAsBean(String section, Class<T>c) throws ParseException { 1182 return getSectionAsBean(section, c, false); 1183 } 1184 1185 /** 1186 * Converts this config file section to the specified bean instance. 1187 * 1188 * <p> 1189 * Key/value pairs in the config file section get copied as bean property values to the specified bean class. 1190 * 1191 * <h5 class='figure'>Example config file</h5> 1192 * <p class='bcode'> 1193 * <cs>[MyAddress]</cs> 1194 * <ck>name</ck> = <cv>John Smith</cv> 1195 * <ck>street</ck> = <cv>123 Main Street</cv> 1196 * <ck>city</ck> = <cv>Anywhere</cv> 1197 * <ck>state</ck> = <cv>NY</cv> 1198 * <ck>zip</ck> = <cv>12345</cv> 1199 * </p> 1200 * 1201 * <h5 class='figure'>Example bean</h5> 1202 * <p class='bcode'> 1203 * <jk>public class</jk> Address { 1204 * public String name, street, city; 1205 * public StateEnum state; 1206 * public int zip; 1207 * } 1208 * </p> 1209 * 1210 * <h5 class='figure'>Example usage</h5> 1211 * <p class='bcode'> 1212 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 1213 * Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>); 1214 * </p> 1215 * 1216 * @param section 1217 * The section name to write from. 1218 * <br>If empty, refers to the default section. 1219 * <br>Must not be <jk>null</jk>. 1220 * @param c The bean class to create. 1221 * @param ignoreUnknownProperties 1222 * If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property 1223 * name. 1224 * @return A new bean instance, or <jk>null</jk> if the section doesn't exist. 1225 * @throws ParseException 1226 */ 1227 public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties) throws ParseException { 1228 assertFieldNotNull(c, "c"); 1229 section = section(section); 1230 1231 if (! configMap.hasSection(section)) 1232 return null; 1233 1234 Set<String> keys = configMap.getKeys(section); 1235 1236 BeanMap<T> bm = beanSession.newBeanMap(c); 1237 for (String k : keys) { 1238 BeanPropertyMeta bpm = bm.getPropertyMeta(k); 1239 if (bpm == null) { 1240 if (! ignoreUnknownProperties) 1241 throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); 1242 } else { 1243 bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); 1244 } 1245 } 1246 1247 return bm.getBean(); 1248 } 1249 1250 /** 1251 * Returns a section of this config copied into an {@link ObjectMap}. 1252 * 1253 * @param section 1254 * The section name to write from. 1255 * <br>If empty, refers to the default section. 1256 * <br>Must not be <jk>null</jk>. 1257 * @return A new {@link ObjectMap}, or <jk>null</jk> if the section doesn't exist. 1258 * @throws ParseException 1259 */ 1260 public ObjectMap getSectionAsMap(String section) throws ParseException { 1261 section = section(section); 1262 1263 if (! configMap.hasSection(section)) 1264 return null; 1265 1266 Set<String> keys = configMap.getKeys(section); 1267 1268 ObjectMap om = new ObjectMap(); 1269 for (String k : keys) 1270 om.put(k, getObject(section + '/' + k, Object.class)); 1271 return om; 1272 } 1273 1274 1275 /** 1276 * Wraps a config file section inside a Java interface so that values in the section can be read and 1277 * write using getters and setters. 1278 * 1279 * <h5 class='figure'>Example config file</h5> 1280 * <p class='bcode'> 1281 * <cs>[MySection]</cs> 1282 * <ck>string</ck> = <cv>foo</cv> 1283 * <ck>int</ck> = <cv>123</cv> 1284 * <ck>enum</ck> = <cv>ONE</cv> 1285 * <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv> 1286 * <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv> 1287 * <ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv> 1288 * </p> 1289 * 1290 * <h5 class='figure'>Example interface</h5> 1291 * <p class='bcode'> 1292 * <jk>public interface</jk> MyConfigInterface { 1293 * 1294 * String getString(); 1295 * <jk>void</jk> setString(String x); 1296 * 1297 * <jk>int</jk> getInt(); 1298 * <jk>void</jk> setInt(<jk>int</jk> x); 1299 * 1300 * MyEnum getEnum(); 1301 * <jk>void</jk> setEnum(MyEnum x); 1302 * 1303 * MyBean getBean(); 1304 * <jk>void</jk> setBean(MyBean x); 1305 * 1306 * <jk>int</jk>[][][] getInt3dArray(); 1307 * <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x); 1308 * 1309 * Map<String,List<MyBean[][][]>> getBean1d3dListMap(); 1310 * <jk>void</jk> setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); 1311 * } 1312 * </p> 1313 * 1314 * <h5 class='figure'>Example usage</h5> 1315 * <p class='bcode'> 1316 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 1317 * 1318 * MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>); 1319 * 1320 * <jk>int</jk> myInt = ci.getInt(); 1321 * 1322 * ci.setBean(<jk>new</jk> MyBean()); 1323 * 1324 * cf.save(); 1325 * </p> 1326 * 1327 * <h5 class='notes'> 1328 * <ul class='spaced-list'> 1329 * <li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown. 1330 * </ul> 1331 * 1332 * @param section 1333 * The section name to write from. 1334 * <br>If empty, refers to the default section. 1335 * <br>Must not be <jk>null</jk>. 1336 * @param c The proxy interface class. 1337 * @return The proxy interface. 1338 */ 1339 @SuppressWarnings("unchecked") 1340 public <T> T getSectionAsInterface(String section, final Class<T> c) { 1341 assertFieldNotNull(c, "c"); 1342 final String section2 = section(section); 1343 1344 if (! c.isInterface()) 1345 throw new IllegalArgumentException("Class '"+c.getName()+"' passed to getSectionAsInterface() is not an interface."); 1346 1347 InvocationHandler h = new InvocationHandler() { 1348 1349 @Override 1350 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1351 BeanInfo bi = Introspector.getBeanInfo(c, null); 1352 for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { 1353 Method rm = pd.getReadMethod(), wm = pd.getWriteMethod(); 1354 if (method.equals(rm)) 1355 return Config.this.getObject(section2 + '/' + pd.getName(), rm.getGenericReturnType()); 1356 if (method.equals(wm)) 1357 return Config.this.set(section2 + '/' + pd.getName(), args[0]); 1358 } 1359 throw new UnsupportedOperationException("Unsupported interface method. method='" + method + "'"); 1360 } 1361 }; 1362 1363 return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h); 1364 } 1365 1366 /** 1367 * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 1368 * 1369 * @param key The key. 1370 * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 1371 */ 1372 public boolean exists(String key) { 1373 return ! isEmpty(getString(key, null)); 1374 } 1375 1376 /** 1377 * Creates the specified section if it doesn't exist. 1378 * 1379 * <p> 1380 * Returns the existing section if it already exists. 1381 * 1382 * @param name 1383 * The section name. 1384 * <br>Must not be <jk>null</jk>. 1385 * <br>Use blank for the default section. 1386 * @param preLines 1387 * Optional comment and blank lines to add immediately before the section. 1388 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1389 * @return The appended or existing section. 1390 * @throws UnsupportedOperationException If configuration is read only. 1391 */ 1392 public Config setSection(String name, List<String> preLines) { 1393 try { 1394 return setSection(section(name), preLines, null); 1395 } catch (SerializeException e) { 1396 throw new RuntimeException(e); // Impossible. 1397 } 1398 } 1399 1400 /** 1401 * Creates the specified section if it doesn't exist. 1402 * 1403 * @param name 1404 * The section name. 1405 * <br>Must not be <jk>null</jk>. 1406 * <br>Use blank for the default section. 1407 * @param preLines 1408 * Optional comment and blank lines to add immediately before the section. 1409 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1410 * @param contents 1411 * Values to set in the new section. 1412 * <br>Can be <jk>null</jk>. 1413 * @return The appended or existing section. 1414 * @throws SerializeException 1415 * @throws UnsupportedOperationException If configuration is read only. 1416 */ 1417 public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException { 1418 checkWrite(); 1419 configMap.setSection(section(name), preLines); 1420 1421 if (contents != null) 1422 for (Map.Entry<String,Object> e : contents.entrySet()) 1423 set(section(name) + '/' + e.getKey(), e.getValue()); 1424 1425 return this; 1426 } 1427 1428 /** 1429 * Removes the section with the specified name. 1430 * 1431 * @param name The name of the section to remove 1432 * @return This object (for method chaining). 1433 * @throws UnsupportedOperationException If configuration is read only. 1434 */ 1435 public Config removeSection(String name) { 1436 checkWrite(); 1437 configMap.removeSection(name); 1438 return this; 1439 } 1440 1441 /** 1442 * Loads the contents of the specified map of maps into this config. 1443 * 1444 * @param m The maps to load. 1445 * @return This object (for method chaining). 1446 * @throws SerializeException 1447 */ 1448 public Config load(Map<String,Map<String,Object>> m) throws SerializeException { 1449 if (m != null) 1450 for (Map.Entry<String,Map<String,Object>> e : m.entrySet()) { 1451 setSection(e.getKey(), null, e.getValue()); 1452 } 1453 return this; 1454 } 1455 1456 /** 1457 * Commit the changes in this config to the store. 1458 * 1459 * @return This object (for method chaining). 1460 * @throws IOException 1461 * @throws UnsupportedOperationException If configuration is read only. 1462 */ 1463 public Config commit() throws IOException { 1464 checkWrite(); 1465 configMap.commit(); 1466 return this; 1467 } 1468 1469 /** 1470 * Saves this config file to the specified writer as an INI file. 1471 * 1472 * <p> 1473 * The writer will automatically be closed. 1474 * 1475 * @param w The writer to send the output to. 1476 * @return This object (for method chaining). 1477 * @throws IOException If a problem occurred trying to send contents to the writer. 1478 */ 1479 @Override /* Writable */ 1480 public Writer writeTo(Writer w) throws IOException { 1481 return configMap.writeTo(w); 1482 } 1483 1484 /** 1485 * Add a listener to this config to react to modification events. 1486 * 1487 * <p> 1488 * Listeners should be removed using {@link #removeListener(ConfigEventListener)}. 1489 * 1490 * @param listener The new listener to add. 1491 * @return This object (for method chaining). 1492 */ 1493 public Config addListener(ConfigEventListener listener) { 1494 listeners.add(listener); 1495 return this; 1496 } 1497 1498 /** 1499 * Removes a listener from this config. 1500 * 1501 * @param listener The listener to remove. 1502 * @return This object (for method chaining). 1503 */ 1504 public Config removeListener(ConfigEventListener listener) { 1505 listeners.remove(listener); 1506 return this; 1507 } 1508 1509 /** 1510 * Closes this configuration object by unregistering it from the underlying config map. 1511 * 1512 * @throws IOException 1513 */ 1514 public void close() throws IOException { 1515 configMap.unregister(this); 1516 } 1517 1518 /** 1519 * Overwrites the contents of the config file. 1520 * 1521 * @param contents The new contents of the config file. 1522 * @param synchronous Wait until the change has been persisted before returning this map. 1523 * @return This object (for method chaining). 1524 * @throws IOException 1525 * @throws InterruptedException 1526 * @throws UnsupportedOperationException If configuration is read only. 1527 */ 1528 public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException { 1529 checkWrite(); 1530 configMap.load(IOUtils.read(contents), synchronous); 1531 return this; 1532 } 1533 1534 /** 1535 * Overwrites the contents of the config file. 1536 * 1537 * @param contents The new contents of the config file. 1538 * @param synchronous Wait until the change has been persisted before returning this map. 1539 * @return This object (for method chaining). 1540 * @throws IOException 1541 * @throws InterruptedException 1542 * @throws UnsupportedOperationException If configuration is read only. 1543 */ 1544 public Config load(String contents, boolean synchronous) throws IOException, InterruptedException { 1545 checkWrite(); 1546 configMap.load(contents, synchronous); 1547 return this; 1548 } 1549 1550 /** 1551 * Does a rollback of any changes on this config currently in memory. 1552 * 1553 * @return This object (for method chaining). 1554 * @throws UnsupportedOperationException If configuration is read only. 1555 */ 1556 public Config rollback() { 1557 checkWrite(); 1558 configMap.rollback(); 1559 return this; 1560 } 1561 1562 /** 1563 * Returns the values in this config map as a map of maps. 1564 * 1565 * <p> 1566 * This is considered a snapshot copy of the config map. 1567 * 1568 * <p> 1569 * The returned map is modifiable, but modifications to the returned map are not reflected in the config map. 1570 * 1571 * @return A copy of this config as a map of maps. 1572 */ 1573 @Override /* Context */ 1574 public ObjectMap asMap() { 1575 return configMap.asMap(); 1576 } 1577 1578 1579 //----------------------------------------------------------------------------------------------------------------- 1580 // Interface methods 1581 //----------------------------------------------------------------------------------------------------------------- 1582 1583 /** 1584 * Unused. 1585 */ 1586 @Override /* Context */ 1587 public Session createSession(SessionArgs args) { 1588 throw new UnsupportedOperationException(); 1589 } 1590 1591 /** 1592 * Unused. 1593 */ 1594 @Override /* Context */ 1595 public SessionArgs createDefaultSessionArgs() { 1596 throw new UnsupportedOperationException(); 1597 } 1598 1599 @Override /* ConfigEventListener */ 1600 public void onConfigChange(List<ConfigEvent> events) { 1601 for (ConfigEventListener l : listeners) 1602 l.onConfigChange(events); 1603 } 1604 1605 @Override /* Writable */ 1606 public MediaType getMediaType() { 1607 return MediaType.PLAIN; 1608 } 1609 1610 1611 //----------------------------------------------------------------------------------------------------------------- 1612 // Private methods 1613 //----------------------------------------------------------------------------------------------------------------- 1614 1615 private String serialize(Object value, Serializer serializer) throws SerializeException { 1616 if (value == null) 1617 return ""; 1618 if (serializer == null) 1619 serializer = this.serializer; 1620 Class<?> c = value.getClass(); 1621 if (value instanceof CharSequence) 1622 return nlIfMl((CharSequence)value); 1623 if (isSimpleType(c)) 1624 return value.toString(); 1625 1626 if (value instanceof byte[]) { 1627 String s = null; 1628 byte[] b = (byte[])value; 1629 if ("HEX".equals(binaryFormat)) 1630 s = toHex(b); 1631 else if ("SPACED_HEX".equals(binaryFormat)) 1632 s = toSpacedHex(b); 1633 else 1634 s = base64Encode(b); 1635 int l = binaryLineLength; 1636 if (l <= 0 || s.length() <= l) 1637 return s; 1638 StringBuilder sb = new StringBuilder(); 1639 for (int i = 0; i < s.length(); i += l) 1640 sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l))); 1641 return sb.toString(); 1642 } 1643 1644 String r = null; 1645 if (multiLineValuesOnSeparateLines) 1646 r = "\n" + (String)serializer.serialize(value); 1647 else 1648 r = (String)serializer.serialize(value); 1649 1650 if (r.startsWith("'")) 1651 return r.substring(1, r.length()-1); 1652 return r; 1653 } 1654 1655 private String nlIfMl(CharSequence cs) { 1656 String s = cs.toString(); 1657 if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines) 1658 return "\n" + s; 1659 return s; 1660 } 1661 1662 @SuppressWarnings({ "unchecked" }) 1663 private <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException { 1664 1665 if (isEmpty(s)) 1666 return null; 1667 1668 if (isSimpleType(type)) 1669 return (T)beanSession.convertToType(s, (Class<?>)type); 1670 1671 if (type == byte[].class) { 1672 if (s.indexOf('\n') != -1) 1673 s = s.replaceAll("\n", ""); 1674 try { 1675 switch (binaryFormat) { 1676 case "HEX": return (T)fromHex(s); 1677 case "SPACED_HEX": return (T)fromSpacedHex(s); 1678 default: return (T)base64Decode(s); 1679 } 1680 } catch (Exception e) { 1681 throw new ParseException("Value could not be converted to a byte array.").initCause(e); 1682 } 1683 } 1684 1685 if (parser == null) 1686 parser = this.parser; 1687 1688 if (parser instanceof JsonParser) { 1689 char s1 = firstNonWhitespaceChar(s); 1690 if (isArray(type) && s1 != '[') 1691 s = '[' + s + ']'; 1692 else if (s1 != '[' && s1 != '{' && ! "null".equals(s)) 1693 s = '\'' + s + '\''; 1694 } 1695 1696 return parser.parse(s, type, args); 1697 } 1698 1699 private boolean isSimpleType(Type t) { 1700 if (! (t instanceof Class)) 1701 return false; 1702 Class<?> c = (Class<?>)t; 1703 return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum()); 1704 } 1705 1706 private boolean isArray(Type t) { 1707 if (! (t instanceof Class)) 1708 return false; 1709 Class<?> c = (Class<?>)t; 1710 return (c.isArray()); 1711 } 1712 1713 private String sname(String key) { 1714 assertFieldNotNull(key, "key"); 1715 int i = key.indexOf('/'); 1716 if (i == -1) 1717 return ""; 1718 return key.substring(0, i); 1719 } 1720 1721 private String skey(String key) { 1722 int i = key.indexOf('/'); 1723 if (i == -1) 1724 return key; 1725 return key.substring(i+1); 1726 } 1727 1728 private String section(String section) { 1729 assertFieldNotNull(section, "section"); 1730 if (isEmpty(section)) 1731 return ""; 1732 return section; 1733 } 1734 1735 private void checkWrite() { 1736 if (readOnly) 1737 throw new UnsupportedOperationException("Cannot call this method on a read-only configuration."); 1738 } 1739 1740 1741 //----------------------------------------------------------------------------------------------------------------- 1742 // Other methods 1743 //----------------------------------------------------------------------------------------------------------------- 1744 1745 @Override /* Object */ 1746 public String toString() { 1747 return configMap.toString(); 1748 } 1749 1750 @Override /* Object */ 1751 protected void finalize() throws Throwable { 1752 close(); 1753 } 1754}