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