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 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 */ 1268 public ObjectMap getObjectMap(String key) throws ParseException { 1269 return getObject(key, ObjectMap.class); 1270 } 1271 1272 /** 1273 * Convenience method for returning a config entry as an {@link ObjectMap}. 1274 * 1275 * @param key The key. 1276 * @param def The default value. 1277 * @return The value, or the default value if the section or key does not exist. 1278 * @throws ParseException Malformed input encountered. 1279 */ 1280 public ObjectMap getObjectMap(String key, ObjectMap def) throws ParseException { 1281 return getObjectWithDefault(key, def, ObjectMap.class); 1282 } 1283 1284 /** 1285 * Convenience method for returning a config entry as an {@link ObjectList}. 1286 * 1287 * @param key The key. 1288 * @return The value, or <jk>null</jk> if the section or key does not exist. 1289 * @throws ParseException Malformed input encountered. 1290 */ 1291 public ObjectList getObjectList(String key) throws ParseException { 1292 return getObject(key, ObjectList.class); 1293 } 1294 1295 /** 1296 * Convenience method for returning a config entry as an {@link ObjectList}. 1297 * 1298 * @param key The key. 1299 * @param def The default value. 1300 * @return The value, or the default value if the section or key does not exist. 1301 * @throws ParseException Malformed input encountered. 1302 */ 1303 public ObjectList getObjectList(String key, ObjectList def) throws ParseException { 1304 return getObjectWithDefault(key, def, ObjectList.class); 1305 } 1306 1307 /** 1308 * Returns the keys of the entries in the specified section. 1309 * 1310 * @param section 1311 * The section name to write from. 1312 * <br>If empty, refers to the default section. 1313 * <br>Must not be <jk>null</jk>. 1314 * @return 1315 * An unmodifiable set of keys, or an empty set if the section doesn't exist. 1316 */ 1317 public Set<String> getKeys(String section) { 1318 return configMap.getKeys(section(section)); 1319 } 1320 1321 /** 1322 * Copies the entries in a section to the specified bean by calling the public setters on that bean. 1323 * 1324 * @param section 1325 * The section name to write from. 1326 * <br>If empty, refers to the default section. 1327 * <br>Must not be <jk>null</jk>. 1328 * @param bean The bean to set the properties on. 1329 * @param ignoreUnknownProperties 1330 * If <jk>true</jk>, don't throw an {@link IllegalArgumentException} if this section contains a key that doesn't 1331 * correspond to a setter method. 1332 * @return An object map of the changes made to the bean. 1333 * @throws ParseException If parser was not set on this config file or invalid properties were found in the section. 1334 * @throws UnsupportedOperationException If configuration is read only. 1335 */ 1336 public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties) throws ParseException { 1337 checkWrite(); 1338 assertFieldNotNull(bean, "bean"); 1339 section = section(section); 1340 1341 Set<String> keys = configMap.getKeys(section); 1342 if (keys == null) 1343 throw new IllegalArgumentException("Section '"+section+"' not found in configuration."); 1344 1345 BeanMap<?> bm = beanSession.toBeanMap(bean); 1346 for (String k : keys) { 1347 BeanPropertyMeta bpm = bm.getPropertyMeta(k); 1348 if (bpm == null) { 1349 if (! ignoreUnknownProperties) 1350 throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); 1351 } else { 1352 bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); 1353 } 1354 } 1355 1356 return this; 1357 } 1358 1359 /** 1360 * Shortcut for calling <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>. 1361 * 1362 * @param section 1363 * The section name to write from. 1364 * <br>If empty, refers to the default section. 1365 * <br>Must not be <jk>null</jk>. 1366 * @param c The bean class to create. 1367 * @return A new bean instance. 1368 * @throws ParseException Malformed input encountered. 1369 */ 1370 public <T> T getSectionAsBean(String section, Class<T> c) throws ParseException { 1371 return getSectionAsBean(section, c, false); 1372 } 1373 1374 /** 1375 * Converts this config file section to the specified bean instance. 1376 * 1377 * <p> 1378 * Key/value pairs in the config file section get copied as bean property values to the specified bean class. 1379 * 1380 * <h5 class='figure'>Example config file</h5> 1381 * <p class='bcode w800'> 1382 * <cs>[MyAddress]</cs> 1383 * <ck>name</ck> = <cv>John Smith</cv> 1384 * <ck>street</ck> = <cv>123 Main Street</cv> 1385 * <ck>city</ck> = <cv>Anywhere</cv> 1386 * <ck>state</ck> = <cv>NY</cv> 1387 * <ck>zip</ck> = <cv>12345</cv> 1388 * </p> 1389 * 1390 * <h5 class='figure'>Example bean</h5> 1391 * <p class='bcode w800'> 1392 * <jk>public class</jk> Address { 1393 * public String name, street, city; 1394 * public StateEnum state; 1395 * public int zip; 1396 * } 1397 * </p> 1398 * 1399 * <h5 class='figure'>Example usage</h5> 1400 * <p class='bcode w800'> 1401 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 1402 * Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>); 1403 * </p> 1404 * 1405 * @param section 1406 * The section name to write from. 1407 * <br>If empty, refers to the default section. 1408 * <br>Must not be <jk>null</jk>. 1409 * @param c The bean class to create. 1410 * @param ignoreUnknownProperties 1411 * If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property 1412 * name. 1413 * @return A new bean instance, or <jk>null</jk> if the section doesn't exist. 1414 * @throws ParseException Unknown property was encountered in section. 1415 */ 1416 public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties) throws ParseException { 1417 assertFieldNotNull(c, "c"); 1418 section = section(section); 1419 1420 if (! configMap.hasSection(section)) 1421 return null; 1422 1423 Set<String> keys = configMap.getKeys(section); 1424 1425 BeanMap<T> bm = beanSession.newBeanMap(c); 1426 for (String k : keys) { 1427 BeanPropertyMeta bpm = bm.getPropertyMeta(k); 1428 if (bpm == null) { 1429 if (! ignoreUnknownProperties) 1430 throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); 1431 } else { 1432 bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); 1433 } 1434 } 1435 1436 return bm.getBean(); 1437 } 1438 1439 /** 1440 * Returns a section of this config copied into an {@link ObjectMap}. 1441 * 1442 * @param section 1443 * The section name to write from. 1444 * <br>If empty, refers to the default section. 1445 * <br>Must not be <jk>null</jk>. 1446 * @return A new {@link ObjectMap}, or <jk>null</jk> if the section doesn't exist. 1447 * @throws ParseException Malformed input encountered. 1448 */ 1449 public ObjectMap getSectionAsMap(String section) throws ParseException { 1450 section = section(section); 1451 1452 if (! configMap.hasSection(section)) 1453 return null; 1454 1455 Set<String> keys = configMap.getKeys(section); 1456 1457 ObjectMap om = new ObjectMap(); 1458 for (String k : keys) 1459 om.put(k, getObject(section + '/' + k, Object.class)); 1460 return om; 1461 } 1462 1463 /** 1464 * Returns the section names defined in this config. 1465 * 1466 * @return The section names defined in this config. 1467 */ 1468 public Set<String> getSections() { 1469 return Collections.unmodifiableSet(configMap.getSections()); 1470 } 1471 1472 /** 1473 * Wraps a config file section inside a Java interface so that values in the section can be read and 1474 * write using getters and setters. 1475 * 1476 * <h5 class='figure'>Example config file</h5> 1477 * <p class='bcode w800'> 1478 * <cs>[MySection]</cs> 1479 * <ck>string</ck> = <cv>foo</cv> 1480 * <ck>int</ck> = <cv>123</cv> 1481 * <ck>enum</ck> = <cv>ONE</cv> 1482 * <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv> 1483 * <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv> 1484 * <ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv> 1485 * </p> 1486 * 1487 * <h5 class='figure'>Example interface</h5> 1488 * <p class='bcode w800'> 1489 * <jk>public interface</jk> MyConfigInterface { 1490 * 1491 * String getString(); 1492 * <jk>void</jk> setString(String x); 1493 * 1494 * <jk>int</jk> getInt(); 1495 * <jk>void</jk> setInt(<jk>int</jk> x); 1496 * 1497 * MyEnum getEnum(); 1498 * <jk>void</jk> setEnum(MyEnum x); 1499 * 1500 * MyBean getBean(); 1501 * <jk>void</jk> setBean(MyBean x); 1502 * 1503 * <jk>int</jk>[][][] getInt3dArray(); 1504 * <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x); 1505 * 1506 * Map<String,List<MyBean[][][]>> getBean1d3dListMap(); 1507 * <jk>void</jk> setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); 1508 * } 1509 * </p> 1510 * 1511 * <h5 class='figure'>Example usage</h5> 1512 * <p class='bcode w800'> 1513 * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 1514 * 1515 * MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>); 1516 * 1517 * <jk>int</jk> myInt = ci.getInt(); 1518 * 1519 * ci.setBean(<jk>new</jk> MyBean()); 1520 * 1521 * cf.save(); 1522 * </p> 1523 * 1524 * <ul class='notes'> 1525 * <li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown. 1526 * </ul> 1527 * 1528 * @param section 1529 * The section name to write from. 1530 * <br>If empty, refers to the default section. 1531 * <br>Must not be <jk>null</jk>. 1532 * @param c The proxy interface class. 1533 * @return The proxy interface. 1534 */ 1535 @SuppressWarnings("unchecked") 1536 public <T> T getSectionAsInterface(String section, final Class<T> c) { 1537 assertFieldNotNull(c, "c"); 1538 final String section2 = section(section); 1539 1540 if (! c.isInterface()) 1541 throw new IllegalArgumentException("Class '"+c.getName()+"' passed to getSectionAsInterface() is not an interface."); 1542 1543 InvocationHandler h = new InvocationHandler() { 1544 1545 @Override 1546 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1547 BeanInfo bi = Introspector.getBeanInfo(c, null); 1548 for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { 1549 Method rm = pd.getReadMethod(), wm = pd.getWriteMethod(); 1550 if (method.equals(rm)) 1551 return Config.this.getObject(section2 + '/' + pd.getName(), rm.getGenericReturnType()); 1552 if (method.equals(wm)) 1553 return Config.this.set(section2 + '/' + pd.getName(), args[0]); 1554 } 1555 throw new UnsupportedOperationException("Unsupported interface method. method='" + method + "'"); 1556 } 1557 }; 1558 1559 return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h); 1560 } 1561 1562 /** 1563 * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 1564 * 1565 * @param key The key. 1566 * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 1567 */ 1568 public boolean exists(String key) { 1569 return isNotEmpty(getString(key, null)); 1570 } 1571 1572 /** 1573 * Creates the specified section if it doesn't exist. 1574 * 1575 * <p> 1576 * Returns the existing section if it already exists. 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 * @return The appended or existing section. 1586 * @throws UnsupportedOperationException If configuration is read only. 1587 */ 1588 public Config setSection(String name, List<String> preLines) { 1589 try { 1590 return setSection(section(name), preLines, null); 1591 } catch (SerializeException e) { 1592 throw new RuntimeException(e); // Impossible. 1593 } 1594 } 1595 1596 /** 1597 * Creates the specified section if it doesn't exist. 1598 * 1599 * @param name 1600 * The section name. 1601 * <br>Must not be <jk>null</jk>. 1602 * <br>Use blank for the default section. 1603 * @param preLines 1604 * Optional comment and blank lines to add immediately before the section. 1605 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1606 * @param contents 1607 * Values to set in the new section. 1608 * <br>Can be <jk>null</jk>. 1609 * @return The appended or existing section. 1610 * @throws SerializeException Contents could not be serialized. 1611 * @throws UnsupportedOperationException If configuration is read only. 1612 */ 1613 public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException { 1614 checkWrite(); 1615 configMap.setSection(section(name), preLines); 1616 1617 if (contents != null) 1618 for (Map.Entry<String,Object> e : contents.entrySet()) 1619 set(section(name) + '/' + e.getKey(), e.getValue()); 1620 1621 return this; 1622 } 1623 1624 /** 1625 * Removes the section with the specified name. 1626 * 1627 * @param name The name of the section to remove 1628 * @return This object (for method chaining). 1629 * @throws UnsupportedOperationException If configuration is read only. 1630 */ 1631 public Config removeSection(String name) { 1632 checkWrite(); 1633 configMap.removeSection(name); 1634 return this; 1635 } 1636 1637 /** 1638 * Creates the specified import statement if it doesn't exist. 1639 * 1640 * @param sectionName 1641 * The section name where to place the import statement. 1642 * <br>Must not be <jk>null</jk>. 1643 * <br>Use blank for the default section. 1644 * @param importName 1645 * The import name. 1646 * <br>Must not be <jk>null</jk>. 1647 * @param preLines 1648 * Optional comment and blank lines to add immediately before the import statement. 1649 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1650 * @return The appended or existing import statement. 1651 * @throws UnsupportedOperationException If configuration is read only. 1652 */ 1653 public Config setImport(String sectionName, String importName, List<String> preLines) { 1654 checkWrite(); 1655 configMap.setImport(section(name), importName, preLines); 1656 return this; 1657 } 1658 1659 /** 1660 * Removes the import statement with the specified name from the specified section. 1661 * 1662 * @param sectionName 1663 * The section name where to place the import statement. 1664 * <br>Must not be <jk>null</jk>. 1665 * <br>Use blank for the default section. 1666 * @param importName 1667 * The import name. 1668 * <br>Must not be <jk>null</jk>. 1669 * @return This object (for method chaining). 1670 * @throws UnsupportedOperationException If configuration is read only. 1671 */ 1672 public Config removeImport(String sectionName, String importName) { 1673 checkWrite(); 1674 configMap.removeImport(sectionName, importName); 1675 return this; 1676 } 1677 1678 /** 1679 * Loads the contents of the specified map of maps into this config. 1680 * 1681 * @param m The maps to load. 1682 * @return This object (for method chaining). 1683 * @throws SerializeException Value could not be serialized. 1684 */ 1685 public Config load(Map<String,Map<String,Object>> m) throws SerializeException { 1686 if (m != null) 1687 for (Map.Entry<String,Map<String,Object>> e : m.entrySet()) { 1688 setSection(e.getKey(), null, e.getValue()); 1689 } 1690 return this; 1691 } 1692 1693 /** 1694 * Commit the changes in this config to the store. 1695 * 1696 * @return This object (for method chaining). 1697 * @throws IOException Thrown by underlying stream. 1698 * @throws UnsupportedOperationException If configuration is read only. 1699 */ 1700 public Config commit() throws IOException { 1701 checkWrite(); 1702 configMap.commit(); 1703 return this; 1704 } 1705 1706 /** 1707 * Saves this config file to the specified writer as an INI file. 1708 * 1709 * <p> 1710 * The writer will automatically be closed. 1711 * 1712 * @param w The writer to send the output to. 1713 * @return This object (for method chaining). 1714 * @throws IOException If a problem occurred trying to send contents to the writer. 1715 */ 1716 @Override /* Writable */ 1717 public Writer writeTo(Writer w) throws IOException { 1718 return configMap.writeTo(w); 1719 } 1720 1721 /** 1722 * Add a listener to this config to react to modification events. 1723 * 1724 * <p> 1725 * Listeners should be removed using {@link #removeListener(ConfigEventListener)}. 1726 * 1727 * @param listener The new listener to add. 1728 * @return This object (for method chaining). 1729 */ 1730 public synchronized Config addListener(ConfigEventListener listener) { 1731 listeners.add(listener); 1732 return this; 1733 } 1734 1735 /** 1736 * Removes a listener from this config. 1737 * 1738 * @param listener The listener to remove. 1739 * @return This object (for method chaining). 1740 */ 1741 public synchronized Config removeListener(ConfigEventListener listener) { 1742 listeners.remove(listener); 1743 return this; 1744 } 1745 1746 /** 1747 * Closes this configuration object by unregistering it from the underlying config map. 1748 * 1749 * @throws IOException Thrown by underlying stream. 1750 */ 1751 public void close() throws IOException { 1752 configMap.unregister(this); 1753 } 1754 1755 /** 1756 * Overwrites the contents of the config file. 1757 * 1758 * @param contents The new contents of the config file. 1759 * @param synchronous Wait until the change has been persisted before returning this map. 1760 * @return This object (for method chaining). 1761 * @throws IOException Thrown by underlying stream. 1762 * @throws InterruptedException Thread was interrupted. 1763 * @throws UnsupportedOperationException If configuration is read only. 1764 */ 1765 public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException { 1766 checkWrite(); 1767 configMap.load(IOUtils.read(contents), synchronous); 1768 return this; 1769 } 1770 1771 /** 1772 * Overwrites the contents of the config file. 1773 * 1774 * @param contents The new contents of the config file. 1775 * @param synchronous Wait until the change has been persisted before returning this map. 1776 * @return This object (for method chaining). 1777 * @throws IOException Thrown by underlying stream. 1778 * @throws InterruptedException Thread was interrupted. 1779 * @throws UnsupportedOperationException If configuration is read only. 1780 */ 1781 public Config load(String contents, boolean synchronous) throws IOException, InterruptedException { 1782 checkWrite(); 1783 configMap.load(contents, synchronous); 1784 return this; 1785 } 1786 1787 /** 1788 * Does a rollback of any changes on this config currently in memory. 1789 * 1790 * @return This object (for method chaining). 1791 * @throws UnsupportedOperationException If configuration is read only. 1792 */ 1793 public Config rollback() { 1794 checkWrite(); 1795 configMap.rollback(); 1796 return this; 1797 } 1798 1799 /** 1800 * Returns the values in this config map as a map of maps. 1801 * 1802 * <p> 1803 * This is considered a snapshot copy of the config map. 1804 * 1805 * <p> 1806 * The returned map is modifiable, but modifications to the returned map are not reflected in the config map. 1807 * 1808 * @return A copy of this config as a map of maps. 1809 */ 1810 @Override /* Context */ 1811 public ObjectMap toMap() { 1812 return configMap.asMap(); 1813 } 1814 1815 1816 //----------------------------------------------------------------------------------------------------------------- 1817 // Test methods 1818 //----------------------------------------------------------------------------------------------------------------- 1819 1820 ConfigMap getConfigMap() { 1821 return configMap; 1822 } 1823 1824 List<ConfigEventListener> getListeners() { 1825 return Collections.unmodifiableList(listeners); 1826 } 1827 1828 1829 //----------------------------------------------------------------------------------------------------------------- 1830 // Interface methods 1831 //----------------------------------------------------------------------------------------------------------------- 1832 1833 /** 1834 * Unused. 1835 */ 1836 @Override /* Context */ 1837 public Session createSession(SessionArgs args) { 1838 throw new UnsupportedOperationException(); 1839 } 1840 1841 /** 1842 * Unused. 1843 */ 1844 @Override /* Context */ 1845 public SessionArgs createDefaultSessionArgs() { 1846 throw new UnsupportedOperationException(); 1847 } 1848 1849 @Override /* ConfigEventListener */ 1850 public void onConfigChange(ConfigEvents events) { 1851 for (ConfigEventListener l : listeners) 1852 l.onConfigChange(events); 1853 } 1854 1855 @Override /* Writable */ 1856 public MediaType getMediaType() { 1857 return MediaType.PLAIN; 1858 } 1859 1860 1861 //----------------------------------------------------------------------------------------------------------------- 1862 // Private methods 1863 //----------------------------------------------------------------------------------------------------------------- 1864 1865 private String serialize(Object value, Serializer serializer) throws SerializeException { 1866 if (value == null) 1867 return ""; 1868 if (serializer == null) 1869 serializer = this.serializer; 1870 Class<?> c = value.getClass(); 1871 if (value instanceof CharSequence) 1872 return nlIfMl((CharSequence)value); 1873 if (isSimpleType(c)) 1874 return value.toString(); 1875 1876 if (value instanceof byte[]) { 1877 String s = null; 1878 byte[] b = (byte[])value; 1879 if (binaryFormat == BinaryFormat.HEX) 1880 s = toHex(b); 1881 else if (binaryFormat == BinaryFormat.SPACED_HEX) 1882 s = toSpacedHex(b); 1883 else 1884 s = base64Encode(b); 1885 int l = binaryLineLength; 1886 if (l <= 0 || s.length() <= l) 1887 return s; 1888 StringBuilder sb = new StringBuilder(); 1889 for (int i = 0; i < s.length(); i += l) 1890 sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l))); 1891 return sb.toString(); 1892 } 1893 1894 String r = null; 1895 if (multiLineValuesOnSeparateLines) 1896 r = "\n" + (String)serializer.serialize(value); 1897 else 1898 r = (String)serializer.serialize(value); 1899 1900 if (r.startsWith("'")) 1901 return r.substring(1, r.length()-1); 1902 return r; 1903 } 1904 1905 private String nlIfMl(CharSequence cs) { 1906 String s = cs.toString(); 1907 if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines) 1908 return "\n" + s; 1909 return s; 1910 } 1911 1912 @SuppressWarnings({ "unchecked" }) 1913 private <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException { 1914 1915 if (isEmpty(s)) 1916 return null; 1917 1918 if (isSimpleType(type)) 1919 return (T)beanSession.convertToType(s, (Class<?>)type); 1920 1921 if (type == byte[].class) { 1922 if (s.indexOf('\n') != -1) 1923 s = s.replaceAll("\n", ""); 1924 try { 1925 switch (binaryFormat) { 1926 case HEX: return (T)fromHex(s); 1927 case SPACED_HEX: return (T)fromSpacedHex(s); 1928 default: return (T)base64Decode(s); 1929 } 1930 } catch (Exception e) { 1931 throw new ParseException(e, "Value could not be converted to a byte array."); 1932 } 1933 } 1934 1935 if (parser == null) 1936 parser = this.parser; 1937 1938 if (parser instanceof JsonParser) { 1939 char s1 = firstNonWhitespaceChar(s); 1940 if (isArray(type) && s1 != '[') 1941 s = '[' + s + ']'; 1942 else if (s1 != '[' && s1 != '{' && ! "null".equals(s)) 1943 s = '\'' + s + '\''; 1944 } 1945 1946 return parser.parse(s, type, args); 1947 } 1948 1949 private boolean isSimpleType(Type t) { 1950 if (! (t instanceof Class)) 1951 return false; 1952 Class<?> c = (Class<?>)t; 1953 return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum()); 1954 } 1955 1956 private boolean isArray(Type t) { 1957 if (! (t instanceof Class)) 1958 return false; 1959 Class<?> c = (Class<?>)t; 1960 return (c.isArray()); 1961 } 1962 1963 private String sname(String key) { 1964 assertFieldNotNull(key, "key"); 1965 int i = key.indexOf('/'); 1966 if (i == -1) 1967 return ""; 1968 return key.substring(0, i); 1969 } 1970 1971 private String skey(String key) { 1972 int i = key.indexOf('/'); 1973 if (i == -1) 1974 return key; 1975 return key.substring(i+1); 1976 } 1977 1978 private String section(String section) { 1979 assertFieldNotNull(section, "section"); 1980 if (isEmpty(section)) 1981 return ""; 1982 return section; 1983 } 1984 1985 private void checkWrite() { 1986 if (readOnly) 1987 throw new UnsupportedOperationException("Cannot call this method on a read-only configuration."); 1988 } 1989 1990 1991 //----------------------------------------------------------------------------------------------------------------- 1992 // Other methods 1993 //----------------------------------------------------------------------------------------------------------------- 1994 1995 @Override /* Object */ 1996 public String toString() { 1997 return configMap.toString(); 1998 } 1999 2000 @Override /* Object */ 2001 protected void finalize() throws Throwable { 2002 close(); 2003 } 2004}