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