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