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