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