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