001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.config; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.IoUtils.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024import static org.apache.juneau.commons.utils.Utils.*; 025 026import java.io.*; 027import java.lang.annotation.*; 028import java.lang.reflect.*; 029import java.util.*; 030 031import org.apache.juneau.*; 032import org.apache.juneau.collections.*; 033import org.apache.juneau.commons.collections.*; 034import org.apache.juneau.commons.collections.FluentMap; 035import org.apache.juneau.commons.function.*; 036import org.apache.juneau.config.event.*; 037import org.apache.juneau.config.internal.*; 038import org.apache.juneau.config.mod.*; 039import org.apache.juneau.config.store.*; 040import org.apache.juneau.config.vars.*; 041import org.apache.juneau.json.*; 042import org.apache.juneau.parser.*; 043import org.apache.juneau.serializer.*; 044import org.apache.juneau.svl.*; 045 046/** 047 * Main configuration API class. 048 * 049 * <h5 class='section'>Notes:</h5><ul> 050 * <li class='note'>This class is thread safe and reusable. 051 * </ul> 052 * 053 * <h5 class='section'>See Also:</h5><ul> 054 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauConfigBasics">juneau-config Basics</a> 055 * </ul> 056 */ 057public class Config extends Context implements ConfigEventListener { 058 /** 059 * Builder class. 060 */ 061 public static class Builder extends Context.Builder { 062 063 private BinaryFormat binaryFormat; 064 private boolean multiLineValuesOnSeparateLines; 065 private boolean readOnly; 066 private ConfigStore store; 067 private int binaryLineLength; 068 private Map<Character,Mod> mods; 069 private ReaderParser parser; 070 private String name; 071 private VarResolver varResolver; 072 private WriterSerializer serializer; 073 074 /** 075 * Constructor, default settings. 076 */ 077 protected Builder() { 078 binaryFormat = env("Config.binaryFormat", BinaryFormat.BASE64); 079 binaryLineLength = env("Config.binaryLineLength", -1); 080 mods = map(); 081 mods(XorEncodeMod.INSTANCE); 082 multiLineValuesOnSeparateLines = env("Config.multiLineValuesOnSeparateLines", false); 083 name = env("Config.name", "Configuration.cfg"); 084 parser = JsonParser.DEFAULT; 085 readOnly = env("Config.readOnly", false); 086 serializer = Json5Serializer.DEFAULT; 087 store = FileStore.DEFAULT; 088 varResolver = VarResolver.DEFAULT; 089 } 090 091 /** 092 * Copy constructor. 093 * 094 * @param copyFrom The builder to copy from. 095 */ 096 protected Builder(Builder copyFrom) { 097 super(copyFrom); 098 binaryFormat = copyFrom.binaryFormat; 099 binaryLineLength = copyFrom.binaryLineLength; 100 mods = copyOf(copyFrom.mods); 101 multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; 102 name = copyFrom.name; 103 parser = copyFrom.parser; 104 readOnly = copyFrom.readOnly; 105 serializer = copyFrom.serializer; 106 store = copyFrom.store; 107 varResolver = copyFrom.varResolver; 108 } 109 110 /** 111 * Copy constructor. 112 * 113 * @param copyFrom The bean to copy from. 114 */ 115 protected Builder(Config copyFrom) { 116 super(copyFrom); 117 binaryFormat = copyFrom.binaryFormat; 118 binaryLineLength = copyFrom.binaryLineLength; 119 mods = copyOf(copyFrom.mods); 120 multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; 121 name = copyFrom.name; 122 parser = copyFrom.parser; 123 readOnly = copyFrom.readOnly; 124 serializer = copyFrom.serializer; 125 store = copyFrom.store; 126 varResolver = copyFrom.varResolver; 127 } 128 129 @Override /* Overridden from Builder */ 130 public Builder annotations(Annotation...values) { 131 super.annotations(values); 132 return this; 133 } 134 135 @Override /* Overridden from Builder */ 136 public Builder annotations(List<Annotation> values) { 137 super.annotations(values); 138 return this; 139 } 140 141 @Override /* Overridden from Builder */ 142 public Builder apply(AnnotationWorkList work) { 143 super.apply(work); 144 return this; 145 } 146 147 @Override /* Overridden from Builder */ 148 public Builder applyAnnotations(Class<?>...from) { 149 super.applyAnnotations(from); 150 return this; 151 } 152 153 @Override /* Overridden from Builder */ 154 public Builder applyAnnotations(Object...from) { 155 super.applyAnnotations(from); 156 return this; 157 } 158 159 /** 160 * Binary value format. 161 * 162 * <p> 163 * The format to use when persisting byte arrays. 164 * 165 * <ul class='values'> 166 * <li>{@link BinaryFormat#BASE64} - BASE64-encoded string. 167 * <li>{@link BinaryFormat#HEX} - Hexadecimal. 168 * <li>{@link BinaryFormat#SPACED_HEX} - Hexadecimal with spaces between bytes. 169 * </ul> 170 * 171 * @param value 172 * The new value for this property. 173 * <br>The default is the first value found: 174 * <ul> 175 * <li>System property <js>"Config.binaryFormat" 176 * <li>Environment variable <js>"CONFIG_BINARYFORMAT" 177 * <li>{@link BinaryFormat#BASE64} 178 * </ul> 179 * <br>Cannot be <jk>null</jk>. 180 * @return This object. 181 */ 182 public Builder binaryFormat(BinaryFormat value) { 183 binaryFormat = assertArgNotNull("value", value); 184 return this; 185 } 186 187 /** 188 * Binary value line length. 189 * 190 * <p> 191 * When serializing binary values, lines will be split after this many characters. 192 * <br>Use <c>-1</c> to represent no line splitting. 193 * 194 * @param value 195 * The new value for this property. 196 * <br>The default is the first value found: 197 * <ul> 198 * <li>System property <js>"Config.binaryLineLength" 199 * <li>Environment variable <js>"CONFIG_BINARYLINELENGTH" 200 * <li><c>-1</c> 201 * </ul> 202 * @return This object. 203 */ 204 public Builder binaryLineLength(int value) { 205 binaryLineLength = value; 206 return this; 207 } 208 209 @Override /* Overridden from Context.Builder */ 210 public Config build() { 211 return build(Config.class); 212 } 213 214 @Override /* Overridden from Builder */ 215 public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) { 216 super.cache(value); 217 return this; 218 } 219 220 @Override /* Overridden from Context.Builder */ 221 public Builder copy() { 222 return new Builder(this); 223 } 224 225 @Override /* Overridden from Builder */ 226 public Builder debug() { 227 super.debug(); 228 return this; 229 } 230 231 @Override /* Overridden from Builder */ 232 public Builder debug(boolean value) { 233 super.debug(value); 234 return this; 235 } 236 237 @Override /* Overridden from Builder */ 238 public Builder impl(Context value) { 239 super.impl(value); 240 return this; 241 } 242 243 /** 244 * Configuration store. 245 * 246 * <p> 247 * Convenience method for calling <code>store(ConfigMemoryStore.<jsf>DEFAULT</jsf>)</code>. 248 * 249 * @return This object. 250 */ 251 public Builder memStore() { 252 store = MemoryStore.DEFAULT; 253 return this; 254 } 255 256 /** 257 * Adds a value modifier. 258 * 259 * <p> 260 * Modifiers are used to modify entry value before being persisted. 261 * 262 * @param values 263 * The mods to apply to this config. 264 * <br>Cannot contain <jk>null</jk> values. 265 * @return This object. 266 */ 267 public Builder mods(Mod...values) { 268 assertArgNoNulls("values", values); 269 for (var value : values) 270 mods.put(value.getId(), value); 271 return this; 272 } 273 274 /** 275 * Multi-line values on separate lines. 276 * 277 * <p> 278 * When enabled, multi-line values will always be placed on a separate line from the key. 279 * 280 * <p> 281 * The default is the first value found: 282 * <ul> 283 * <li>System property <js>"Config.multiLineValuesOnSeparateLine" 284 * <li>Environment variable <js>"CONFIG_MULTILINEVALUESONSEPARATELINE" 285 * <li><jk>false</jk> 286 * </ul> 287 * 288 * @return This object. 289 */ 290 public Builder multiLineValuesOnSeparateLines() { 291 multiLineValuesOnSeparateLines = true; 292 return this; 293 } 294 295 /** 296 * Configuration name. 297 * 298 * <p> 299 * Specifies the configuration name. 300 * <br>This is typically the configuration file name, although 301 * the name can be anything identifiable by the {@link ConfigStore} used for retrieving and storing the configuration. 302 * 303 * @param value 304 * The new value for this property. 305 * <br>The default is the first value found: 306 * <ul> 307 * <li>System property <js>"Config.name" 308 * <li>Environment variable <js>"CONFIG_NAME" 309 * <li><js>"Configuration.cfg"</js> 310 * </ul> 311 * <br>Cannot be <jk>null</jk>. 312 * @return This object. 313 */ 314 public Builder name(String value) { 315 name = assertArgNotNull("value", value); 316 return this; 317 } 318 319 /** 320 * POJO parser. 321 * 322 * <p> 323 * The parser to use for parsing values to POJOs. 324 * 325 * @param value 326 * The new value for this property. 327 * <br>The default is {@link JsonParser#DEFAULT}. 328 * <br>Cannot be <jk>null</jk>. 329 * @return This object. 330 */ 331 public Builder parser(ReaderParser value) { 332 parser = assertArgNotNull("value", value); 333 return this; 334 } 335 336 /** 337 * Read-only mode. 338 * 339 * <p> 340 * When enabled, attempts to call any setters on this object will throw an {@link UnsupportedOperationException}. 341 * 342 * <p> 343 * The default is the first value found: 344 * <ul> 345 * <li>System property <js>"Config.readOnly" 346 * <li>Environment variable <js>"CONFIG_READONLY" 347 * <li><jk>false</jk> 348 * </ul> 349 * 350 * @return This object. 351 */ 352 public Builder readOnly() { 353 readOnly = true; 354 return this; 355 } 356 357 /** 358 * POJO serializer. 359 * 360 * <p> 361 * The serializer to use for serializing POJO values. 362 * 363 * @param value 364 * The new value for this property. 365 * <br>The default is {@link Json5Serializer#DEFAULT} 366 * <br>Cannot be <jk>null</jk>. 367 * @return This object. 368 */ 369 public Builder serializer(WriterSerializer value) { 370 serializer = assertArgNotNull("value", value); 371 return this; 372 } 373 374 /** 375 * Configuration store. 376 * 377 * <p> 378 * The configuration store used for retrieving and storing configurations. 379 * 380 * @param value 381 * The new value for this property. 382 * <br>The default is {@link FileStore#DEFAULT}. 383 * <br>Cannot be <jk>null</jk>. 384 * @return This object. 385 */ 386 public Builder store(ConfigStore value) { 387 store = assertArgNotNull("value", value); 388 return this; 389 } 390 391 @Override /* Overridden from Builder */ 392 public Builder type(Class<? extends org.apache.juneau.Context> value) { 393 super.type(value); 394 return this; 395 } 396 397 /** 398 * SVL variable resolver. 399 * 400 * <p> 401 * The resolver to use for resolving SVL variables. 402 * 403 * @param value 404 * The new value for this property. 405 * <br>The default is {@link VarResolver#DEFAULT}. 406 * <br>Cannot be <jk>null</jk>. 407 * @return This object. 408 */ 409 public Builder varResolver(VarResolver value) { 410 varResolver = assertArgNotNull("value", value); 411 return this; 412 } 413 } 414 415 // Use set(T)/reset() for testing. 416 static final ResettableSupplier<Boolean> DISABLE_AUTO_SYSTEM_PROPS = memr(() -> Boolean.getBoolean("juneau.disableAutoSystemProps")); 417 418 // Use set(T)/reset() for testing. 419 static final ResettableSupplier<Config> SYSTEM_DEFAULT = memr(() -> findSystemDefault()); 420 421 /** 422 * Creates a new builder for this object. 423 * 424 * @return A new builder. 425 */ 426 public static Builder create() { 427 return new Builder(); 428 } 429 430 /** 431 * Same as {@link #create()} but initializes the builder with the specified config name. 432 * 433 * @param name The configuration name. 434 * @return A new builder. 435 */ 436 public static Builder create(String name) { 437 return new Builder().name(name); 438 } 439 440 /** 441 * Returns the list of candidate system default configuration file names. 442 * 443 * <p> 444 * If the <js>"juneau.configFile"</js> system property is set, returns a singleton of that value. 445 * <br>Otherwise, returns a list consisting of the following values: 446 * <ol> 447 * <li>File with same name as jar file but with <js>".cfg"</js> extension. (e.g. <js>"myjar.cfg"</js>) 448 * <li>Any file ending in <js>".cfg"</js> in the home directory (names ordered alphabetically). 449 * <li><js>"juneau.cfg"</js> 450 * <li><js>"default.cfg"</js> 451 * <li><js>"application.cfg"</js> 452 * <li><js>"app.cfg"</js> 453 * <li><js>"settings.cfg"</js> 454 * <li><js>"application.properties"</js> 455 * </ol> 456 * <p> 457 * 458 * @return 459 * A list of candidate file names. 460 * <br>The returned list is modifiable. 461 * <br>Each call constructs a new list. 462 */ 463 public static synchronized List<String> getCandidateSystemDefaultConfigNames() { 464 var l = listOf(String.class); 465 466 var s = System.getProperty("juneau.configFile"); 467 if (nn(s)) { 468 l.add(s); 469 return l; 470 } 471 472 var cmd = System.getProperty("sun.java.command", "not_found").split("\\s+")[0]; 473 if (cmd.endsWith(".jar") && ! co(cmd, "surefirebooter")) { 474 cmd = cmd.replaceAll(".*?([^\\\\\\/]+)\\.jar$", "$1"); 475 l.add(cmd + ".cfg"); 476 cmd = cmd.replaceAll("[\\.\\_].*$", ""); // Try also without version in jar name. 477 l.add(cmd + ".cfg"); 478 } 479 480 var fileArray = new File(".").listFiles(); 481 if (fileArray != null) { 482 for (var f : fileArray) 483 if (f.getName().endsWith(".cfg")) 484 l.add(f.getName()); 485 } 486 487 l.add("juneau.cfg"); 488 l.add("default.cfg"); 489 l.add("application.cfg"); 490 l.add("app.cfg"); 491 l.add("settings.cfg"); 492 l.add("application.properties"); 493 494 return l; 495 } 496 497 /** 498 * Returns the system default configuration. 499 * 500 * @return The system default configuration, or <jk>null</jk> if it doesn't exist. 501 */ 502 public static synchronized Config getSystemDefault() { return SYSTEM_DEFAULT.get(); } 503 504 /** 505 * Sets a system default configuration. 506 * 507 * @param systemDefault The new system default configuration. 508 */ 509 public static synchronized void setSystemDefault(Config systemDefault) { 510 SYSTEM_DEFAULT.set(systemDefault); 511 } 512 513 private static synchronized Config find(String name) { 514 if (name == null) 515 return null; 516 if (FileStore.DEFAULT.exists(name)) 517 return Config.create(name).store(FileStore.DEFAULT).build(); 518 if (ClasspathStore.DEFAULT.exists(name)) 519 return Config.create(name).store(ClasspathStore.DEFAULT).build(); 520 return null; 521 } 522 523 private static synchronized Config findSystemDefault() { 524 525 for (var n : getCandidateSystemDefaultConfigNames()) { 526 var config = find(n); 527 if (nn(config)) { 528 if (! DISABLE_AUTO_SYSTEM_PROPS.get()) 529 config.setSystemProperties(); 530 return config; 531 } 532 } 533 534 return null; 535 } 536 537 private static boolean isSimpleType(Type t) { 538 if (! (t instanceof Class)) 539 return false; 540 var c = (Class<?>)t; 541 return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum()); 542 } 543 private static String section(String section) { 544 assertArgNotNull("section", section); 545 if (isEmpty(section)) 546 return ""; 547 return section; 548 } 549 private static String skey(String key) { 550 var i = key.indexOf('/'); 551 if (i == -1) 552 return key; 553 return key.substring(i + 1); 554 } 555 private static String sname(String key) { 556 assertArgNotNull("key", key); 557 var i = key.indexOf('/'); 558 if (i == -1) 559 return ""; 560 return key.substring(0, i); 561 } 562 563 protected final boolean multiLineValuesOnSeparateLines; 564 protected final boolean readOnly; 565 protected final int binaryLineLength; 566 protected final BinaryFormat binaryFormat; 567 protected final BeanSession beanSession; 568 protected final ConfigStore store; 569 protected final Map<Character,Mod> mods; 570 protected final ReaderParser parser; 571 protected final String name; 572 protected final VarResolver varResolver; 573 protected final VarResolverSession varSession; 574 protected final WriterSerializer serializer; 575 576 private final ConfigMap configMap; 577 private final List<ConfigEventListener> listeners = synced(new LinkedList<>()); 578 579 /** 580 * Constructor. 581 * 582 * @param builder The builder for this object. 583 * @throws IOException Thrown by underlying stream. 584 */ 585 public Config(Builder builder) throws IOException { 586 super(builder); 587 588 binaryFormat = builder.binaryFormat; 589 binaryLineLength = builder.binaryLineLength; 590 mods = u(copyOf(builder.mods)); 591 multiLineValuesOnSeparateLines = builder.multiLineValuesOnSeparateLines; 592 name = builder.name; 593 parser = builder.parser; 594 readOnly = builder.readOnly; 595 serializer = builder.serializer; 596 store = builder.store; 597 varResolver = builder.varResolver; 598 configMap = store.getMap(name); 599 configMap.register(this); 600 beanSession = parser.getBeanContext().getSession(); 601 varSession = varResolver.copy().vars(ConfigVar.class).bean(Config.class, this).build().createSession(); 602 } 603 604 Config(Config copyFrom, VarResolverSession varSession) { 605 super(copyFrom); 606 beanSession = copyFrom.beanSession; 607 binaryFormat = copyFrom.binaryFormat; 608 binaryLineLength = copyFrom.binaryLineLength; 609 configMap = copyFrom.configMap; 610 configMap.register(this); 611 mods = copyFrom.mods; 612 multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; 613 name = copyFrom.name; 614 parser = copyFrom.parser; 615 readOnly = copyFrom.readOnly; 616 serializer = copyFrom.serializer; 617 store = copyFrom.store; 618 this.varSession = varSession; 619 varResolver = copyFrom.varResolver; 620 } 621 622 /** 623 * Add a listener to this config to react to modification events. 624 * 625 * <p> 626 * Listeners should be removed using {@link #removeListener(ConfigEventListener)}. 627 * 628 * @param listener The new listener to add. 629 * @return This object. 630 */ 631 public synchronized Config addListener(ConfigEventListener listener) { 632 listeners.add(listener); 633 return this; 634 } 635 636 /** 637 * Encodes and unencoded entries in this config. 638 * 639 * <p> 640 * If any entries in the config are marked as encoded but not actually encoded, 641 * this will encode them. 642 * 643 * @return This object. 644 * @throws UnsupportedOperationException If configuration is read only. 645 */ 646 public Config applyMods() { 647 checkWrite(); 648 for (var section : configMap.getSections()) { 649 for (var key : configMap.getKeys(section)) { 650 var ce = configMap.getEntry(section, key); 651 if (nn(ce.getModifiers())) { 652 var mods2 = ce.getModifiers(); 653 var value = ce.getValue(); 654 for (var i = 0; i < mods2.length(); i++) { 655 var mod = getMod(mods2.charAt(i)); 656 if (! mod.isApplied(value)) { 657 configMap.setEntry(section, key, mod.apply(value), null, null, null); 658 } 659 } 660 } 661 } 662 } 663 664 return this; 665 } 666 667 /** 668 * Closes this configuration object by unregistering it from the underlying config map. 669 */ 670 public void close() { 671 configMap.unregister(this); 672 } 673 674 /** 675 * Commit the changes in this config to the store. 676 * 677 * @return This object. 678 * @throws IOException Thrown by underlying stream. 679 * @throws UnsupportedOperationException If configuration is read only. 680 */ 681 public Config commit() throws IOException { 682 checkWrite(); 683 configMap.commit(); 684 return this; 685 } 686 687 @Override /* Overridden from Context */ 688 public Builder copy() { 689 return new Builder(this); 690 } 691 692 /** 693 * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 694 * 695 * @param key The key. 696 * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value. 697 */ 698 public boolean exists(String key) { 699 return ne(get(key).as(String.class).orElse(null)); 700 } 701 702 /** 703 * Gets the entry with the specified key. 704 * 705 * <p> 706 * The key can be in one of the following formats... 707 * <ul class='spaced-list'> 708 * <li> 709 * <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header). 710 * <li> 711 * <js>"section/key"</js> - A value from the specified section. 712 * </ul> 713 * 714 * <p> 715 * If entry does not exist, returns an empty {@link Entry} object. 716 * 717 * @param key The key. 718 * @return The entry bean, never <jk>null</jk>. 719 */ 720 public Entry get(String key) { 721 return new Entry(this, configMap, sname(key), skey(key)); 722 } 723 724 /** 725 * Returns the keys of the entries in the specified section. 726 * 727 * @param section 728 * The section name to write from. 729 * <br>If empty, refers to the default section. 730 * <br>Must not be <jk>null</jk>. 731 * @return 732 * An unmodifiable set of keys, or an empty set if the section doesn't exist. 733 */ 734 public Set<String> getKeys(String section) { 735 return configMap.getKeys(section(section)); 736 } 737 738 /** 739 * Returns the name associated with this config (usually a file name). 740 * 741 * @return The name associated with this config, or <jk>null</jk> if it has no name. 742 */ 743 public String getName() { return name; } 744 745 /** 746 * Gets the section with the specified name. 747 * 748 * <p> 749 * If section does not exist, returns an empty {@link Section} object. 750 * 751 * @param name The section name. <jk>null</jk> and blank refer to the default section. 752 * @return The section bean, never <jk>null</jk>. 753 */ 754 public Section getSection(String name) { 755 return new Section(this, configMap, emptyIfNull(name)); 756 } 757 758 /** 759 * Returns the section names defined in this config. 760 * 761 * @return The section names defined in this config. 762 */ 763 public Set<String> getSectionNames() { return u(configMap.getSections()); } 764 765 /** 766 * Gets the entry with the specified key. 767 * 768 * <p> 769 * The key can be in one of the following formats... 770 * <ul class='spaced-list'> 771 * <li> 772 * <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header). 773 * <li> 774 * <js>"section/key"</js> - A value from the specified section. 775 * </ul> 776 * 777 * <p> 778 * If entry does not exist, returns <jk>null</jk>. 779 * 780 * <h5 class='section'>Notes:</h5><ul> 781 * <li class='note'>This method is equivalent to calling <c>get(<jv>key</jv>).orElse(<jk>null</jk>);</c>. 782 * </ul> 783 * 784 * @param key The key. 785 * @return The entry value, or <jk>null</jk> if it doesn't exist. 786 */ 787 public String getString(String key) { 788 return new Entry(this, configMap, sname(key), skey(key)).orElse(null); 789 } 790 791 /** 792 * Loads the contents of the specified map of maps into this config. 793 * 794 * @param m The maps to load. 795 * @return This object. 796 * @throws SerializeException Value could not be serialized. 797 */ 798 public Config load(Map<String,Map<String,Object>> m) throws SerializeException { 799 if (nn(m)) 800 for (var e : m.entrySet()) { 801 setSection(e.getKey(), null, e.getValue()); 802 } 803 return this; 804 } 805 806 /** 807 * Overwrites the contents of the config file. 808 * 809 * @param contents The new contents of the config file. 810 * @param synchronous Wait until the change has been persisted before returning this map. 811 * @return This object. 812 * @throws IOException Thrown by underlying stream. 813 * @throws InterruptedException Thread was interrupted. 814 * @throws UnsupportedOperationException If configuration is read only. 815 */ 816 public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException { 817 checkWrite(); 818 configMap.load(read(contents), synchronous); 819 return this; 820 } 821 822 /** 823 * Overwrites the contents of the config file. 824 * 825 * @param contents The new contents of the config file. 826 * @param synchronous Wait until the change has been persisted before returning this map. 827 * @return This object. 828 * @throws IOException Thrown by underlying stream. 829 * @throws InterruptedException Thread was interrupted. 830 * @throws UnsupportedOperationException If configuration is read only. 831 */ 832 public Config load(String contents, boolean synchronous) throws IOException, InterruptedException { 833 checkWrite(); 834 configMap.load(contents, synchronous); 835 return this; 836 } 837 838 @Override /* Overridden from ConfigEventListener */ 839 public synchronized void onConfigChange(ConfigEvents events) { 840 listeners.forEach(x -> x.onConfigChange(events)); 841 } 842 843 /** 844 * Removes an entry with the specified key. 845 * 846 * @param key The key. 847 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 848 * @throws UnsupportedOperationException If configuration is read only. 849 */ 850 public Config remove(String key) { 851 checkWrite(); 852 var sname = sname(key); 853 var skey = skey(key); 854 configMap.removeEntry(sname, skey); 855 return this; 856 } 857 858 /** 859 * Removes the import statement with the specified name from the specified section. 860 * 861 * @param sectionName 862 * The section name where to place the import statement. 863 * <br>Must not be <jk>null</jk>. 864 * <br>Use blank for the default section. 865 * @param importName 866 * The import name. 867 * <br>Must not be <jk>null</jk>. 868 * @return This object. 869 * @throws UnsupportedOperationException If configuration is read only. 870 */ 871 public Config removeImport(String sectionName, String importName) { 872 checkWrite(); 873 configMap.removeImport(sectionName, importName); 874 return this; 875 } 876 877 /** 878 * Removes a listener from this config. 879 * 880 * @param listener The listener to remove. 881 * @return This object. 882 */ 883 public synchronized Config removeListener(ConfigEventListener listener) { 884 listeners.remove(listener); 885 return this; 886 } 887 888 /** 889 * Removes the section with the specified name. 890 * 891 * @param name The name of the section to remove 892 * @return This object. 893 * @throws UnsupportedOperationException If configuration is read only. 894 */ 895 public Config removeSection(String name) { 896 checkWrite(); 897 configMap.removeSection(name); 898 return this; 899 } 900 901 /** 902 * Creates a copy of this config using the specified var session for resolving variables. 903 * 904 * <p> 905 * This creates a shallow copy of the config but replacing the variable resolver. 906 * 907 * @param varSession The var session used for resolving string variables. 908 * @return A new config object. 909 */ 910 public Config resolving(VarResolverSession varSession) { 911 return new Config(this, varSession); 912 } 913 914 /** 915 * Does a rollback of any changes on this config currently in memory. 916 * 917 * @return This object. 918 * @throws UnsupportedOperationException If configuration is read only. 919 */ 920 public Config rollback() { 921 checkWrite(); 922 configMap.rollback(); 923 return this; 924 } 925 926 /** 927 * Adds or replaces an entry with the specified key with a POJO serialized to a string using the registered 928 * serializer. 929 * 930 * <p> 931 * Equivalent to calling <c>put(key, value, isEncoded(key))</c>. 932 * 933 * @param key The key. 934 * @param value The new value POJO. 935 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 936 * @throws SerializeException 937 * If serializer could not serialize the value or if a serializer is not registered with this config file. 938 * @throws UnsupportedOperationException If configuration is read only. 939 */ 940 public Config set(String key, Object value) throws SerializeException { 941 return set(key, value, null); 942 } 943 944 /** 945 * Same as {@link #set(String, Object)} but allows you to specify the serializer to use to serialize the 946 * value. 947 * 948 * @param key The key. 949 * @param value The new value. 950 * @param serializer 951 * The serializer to use for serializing the object. 952 * If <jk>null</jk>, then uses the predefined serializer on the config file. 953 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 954 * @throws SerializeException 955 * If serializer could not serialize the value or if a serializer is not registered with this config file. 956 * @throws UnsupportedOperationException If configuration is read only. 957 */ 958 public Config set(String key, Object value, Serializer serializer) throws SerializeException { 959 return set(key, serialize(value, serializer)); 960 } 961 962 /** 963 * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value. 964 * 965 * @param key The key. 966 * @param value The new value. 967 * @param serializer 968 * The serializer to use for serializing the object. 969 * If <jk>null</jk>, then uses the predefined serializer on the config file. 970 * @param modifiers 971 * Optional modifiers to apply to the value. 972 * <br>If <jk>null</jk>, then previous value will not be replaced. 973 * @param comment 974 * Optional same-line comment to add to this value. 975 * <br>If <jk>null</jk>, then previous value will not be replaced. 976 * @param preLines 977 * Optional comment or blank lines to add before this entry. 978 * <br>If <jk>null</jk>, then previous value will not be replaced. 979 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. 980 * @throws SerializeException 981 * If serializer could not serialize the value or if a serializer is not registered with this config file. 982 * @throws UnsupportedOperationException If configuration is read only. 983 */ 984 public Config set(String key, Object value, Serializer serializer, String modifiers, String comment, List<String> preLines) throws SerializeException { 985 checkWrite(); 986 assertArgNotNull("key", key); 987 var sname = sname(key); 988 var skey = skey(key); 989 modifiers = nullIfEmpty(modifiers); 990 991 var s = applyMods(modifiers, serialize(value, serializer)); 992 993 configMap.setEntry(sname, skey, s, modifiers, comment, preLines); 994 return this; 995 } 996 997 /** 998 * Sets a value in this config. 999 * 1000 * @param key The key. 1001 * @param value The value. 1002 * @return This object. 1003 * @throws UnsupportedOperationException If configuration is read only. 1004 */ 1005 public Config set(String key, String value) { 1006 checkWrite(); 1007 assertArgNotNull("key", key); 1008 var sname = sname(key); 1009 var skey = skey(key); 1010 1011 var ce = configMap.getEntry(sname, skey); 1012 if (ce == null && value == null) 1013 return this; 1014 1015 var s = applyMods(ce == null ? null : ce.getModifiers(), s(value)); 1016 1017 configMap.setEntry(sname, skey, s, null, null, null); 1018 return this; 1019 } 1020 1021 /** 1022 * Creates the specified import statement if it doesn't exist. 1023 * 1024 * @param sectionName 1025 * The section name where to place the import statement. 1026 * <br>Must not be <jk>null</jk>. 1027 * <br>Use blank for the default section. 1028 * @param importName 1029 * The import name. 1030 * <br>Must not be <jk>null</jk>. 1031 * @param preLines 1032 * Optional comment and blank lines to add immediately before the import statement. 1033 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1034 * @return The appended or existing import statement. 1035 * @throws UnsupportedOperationException If configuration is read only. 1036 */ 1037 public Config setImport(String sectionName, String importName, List<String> preLines) { 1038 checkWrite(); 1039 configMap.setImport(section(name), importName, preLines); 1040 return this; 1041 } 1042 1043 /** 1044 * Creates the specified section if it doesn't exist. 1045 * 1046 * <p> 1047 * Returns the existing section if it already exists. 1048 * 1049 * @param name 1050 * The section name. 1051 * <br>Must not be <jk>null</jk>. 1052 * <br>Use blank for the default section. 1053 * @param preLines 1054 * Optional comment and blank lines to add immediately before the section. 1055 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1056 * @return The appended or existing section. 1057 * @throws UnsupportedOperationException If configuration is read only. 1058 */ 1059 public Config setSection(String name, List<String> preLines) { 1060 try { 1061 return setSection(section(name), preLines, null); 1062 } catch (SerializeException e) { 1063 throw toRex(e); // Impossible. 1064 } 1065 } 1066 1067 /** 1068 * Creates the specified section if it doesn't exist. 1069 * 1070 * @param name 1071 * The section name. 1072 * <br>Must not be <jk>null</jk>. 1073 * <br>Use blank for the default section. 1074 * @param preLines 1075 * Optional comment and blank lines to add immediately before the section. 1076 * <br>If <jk>null</jk>, previous pre-lines will not be replaced. 1077 * @param contents 1078 * Values to set in the new section. 1079 * <br>Can be <jk>null</jk>. 1080 * @return The appended or existing section. 1081 * @throws SerializeException Contents could not be serialized. 1082 * @throws UnsupportedOperationException If configuration is read only. 1083 */ 1084 public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException { 1085 checkWrite(); 1086 configMap.setSection(section(name), preLines); 1087 1088 if (nn(contents)) 1089 for (var e : contents.entrySet()) 1090 set(section(name) + '/' + e.getKey(), e.getValue()); 1091 1092 return this; 1093 } 1094 1095 /** 1096 * Takes the settings defined in this configuration and sets them as system properties. 1097 * 1098 * @return This object. 1099 */ 1100 public Config setSystemProperties() { 1101 for (var section : getSectionNames()) { 1102 for (var key : getKeys(section)) { 1103 var k = (section.isEmpty() ? key : section + '/' + key); 1104 System.setProperty(k, getRaw(k)); 1105 } 1106 } 1107 return this; 1108 } 1109 1110 /** 1111 * Returns the contents of this config as a simple map. 1112 * 1113 * @return The contents of this config as a simple map. 1114 */ 1115 public JsonMap toMap() { 1116 return configMap.asMap(); 1117 } 1118 1119 @Override /* Overridden from Context */ 1120 protected FluentMap<String,Object> properties() { 1121 return super.properties() 1122 .a("binaryFormat", binaryFormat) 1123 .a("binaryLineLength", binaryLineLength) 1124 .a("mods", mods) 1125 .a("multiLineValuesOnSeparateLines", multiLineValuesOnSeparateLines) 1126 .a("name", name) 1127 .a("parser", parser) 1128 .a("readOnly", readOnly) 1129 .a("serializer", serializer) 1130 .a("store", store) 1131 .a("varResolver", varResolver); 1132 } 1133 1134 @Override /* Overridden from Object */ 1135 public String toString() { 1136 return configMap.toString(); 1137 } 1138 1139 /** 1140 * Saves this config file to the specified writer as an INI file. 1141 * 1142 * <p> 1143 * The writer will automatically be closed. 1144 * 1145 * @param w The writer to send the output to. 1146 * @return This object. 1147 * @throws IOException If a problem occurred trying to send contents to the writer. 1148 */ 1149 public Writer writeTo(Writer w) throws IOException { 1150 return configMap.writeTo(w); 1151 } 1152 1153 /** 1154 * Returns the specified value as a string from the config file. 1155 * 1156 * <p> 1157 * Unlike {@link #getString(String)}, this method doesn't replace SVL variables. 1158 * 1159 * @param key The key. 1160 * @return The value, or <jk>null</jk> if the section or value doesn't exist. 1161 */ 1162 private String getRaw(String key) { 1163 1164 var sname = sname(key); 1165 var skey = skey(key); 1166 1167 var ce = configMap.getEntry(sname, skey); 1168 1169 if (ce == null) 1170 return null; 1171 1172 return removeMods(ce.getModifiers(), ce.getValue()); 1173 } 1174 1175 private String nlIfMl(CharSequence cs) { 1176 var s = cs.toString(); 1177 if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines) 1178 return "\n" + s; 1179 return s; 1180 } 1181 1182 private String serialize(Object value, Serializer serializer) throws SerializeException { 1183 if (value == null) 1184 return ""; 1185 if (serializer == null) 1186 serializer = this.serializer; 1187 var c = value.getClass(); 1188 if (value instanceof CharSequence cs) 1189 return nlIfMl(cs); 1190 if (isSimpleType(c)) 1191 return value.toString(); 1192 1193 if (value instanceof byte[] b) { 1194 var s = (String)null; 1195 if (binaryFormat == BinaryFormat.HEX) 1196 s = toHex(b); 1197 else if (binaryFormat == BinaryFormat.SPACED_HEX) 1198 s = toSpacedHex(b); 1199 else 1200 s = base64Encode(b); 1201 var l = binaryLineLength; 1202 if (l <= 0 || s.length() <= l) 1203 return s; 1204 var sb = new StringBuilder(); 1205 for (var i = 0; i < s.length(); i += l) 1206 sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l))); 1207 return sb.toString(); 1208 } 1209 1210 var r = (String)null; 1211 if (multiLineValuesOnSeparateLines) 1212 r = "\n" + (String)serializer.serialize(value); 1213 else 1214 r = (String)serializer.serialize(value); 1215 1216 if (r.startsWith("'")) 1217 return r.substring(1, r.length() - 1); 1218 return r; 1219 } 1220 1221 String applyMods(String mods, String x) { 1222 if (nn(mods) && nn(x)) 1223 for (var i = 0; i < mods.length(); i++) 1224 x = getMod(mods.charAt(i)).doApply(x); 1225 return x; 1226 } 1227 1228 void checkWrite() { 1229 if (readOnly) 1230 throw unsupportedOp("Cannot call this method on a read-only configuration."); 1231 } 1232 1233 ConfigMap getConfigMap() { return configMap; } 1234 1235 List<ConfigEventListener> getListeners() { return u(listeners); } 1236 1237 Mod getMod(char id) { 1238 var x = mods.get(id); 1239 return def(x, Mod.NO_OP); 1240 } 1241 1242 String removeMods(String mods, String x) { 1243 if (nn(mods) && nn(x)) 1244 for (var i = mods.length() - 1; i > -1; i--) 1245 x = getMod(mods.charAt(i)).doRemove(x); 1246 return x; 1247 } 1248}