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