001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.config;
014
015import static org.apache.juneau.config.ConfigMod.*;
016import static org.apache.juneau.internal.StringUtils.*;
017import static org.apache.juneau.internal.ThrowableUtils.*;
018
019import java.beans.*;
020import java.io.*;
021import java.lang.reflect.*;
022import java.util.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.annotation.*;
026import org.apache.juneau.config.encode.*;
027import org.apache.juneau.config.encode.ConfigEncoder;
028import org.apache.juneau.config.event.*;
029import org.apache.juneau.config.internal.*;
030import org.apache.juneau.config.store.*;
031import org.apache.juneau.config.vars.*;
032import org.apache.juneau.http.*;
033import org.apache.juneau.internal.*;
034import org.apache.juneau.json.*;
035import org.apache.juneau.parser.*;
036import org.apache.juneau.serializer.*;
037import org.apache.juneau.svl.*;
038
039/**
040 * Main configuration API class.
041 *
042 * <ul class='seealso'>
043 *    <li class='link'>{@doc juneau-config}
044 *    <li class='extlink'>{@source}
045 * </ul>
046 */
047@ConfigurableContext
048public final class Config extends Context implements ConfigEventListener, Writable {
049
050   private static boolean DISABLE_AUTO_SYSTEM_PROPS = Boolean.getBoolean("juneau.disableAutoSystemProps");
051   private static volatile Config SYSTEM_DEFAULT = findSystemDefault();
052
053   /**
054    * Sets a system default configuration.
055    *
056    * @param systemDefault The new system default configuration.
057    */
058   public synchronized static void setSystemDefault(Config systemDefault) {
059      SYSTEM_DEFAULT = systemDefault;
060   }
061
062   /**
063    * Returns the system default configuration.
064    *
065    * @return The system default configuration, or <jk>null</jk> if it doesn't exist.
066    */
067   public synchronized static Config getSystemDefault() {
068      return SYSTEM_DEFAULT;
069   }
070
071   private synchronized static Config findSystemDefault() {
072
073      for (String n : getCandidateSystemDefaultConfigNames()) {
074         Config config = find(n);
075         if (config != null) {
076            if (! DISABLE_AUTO_SYSTEM_PROPS)
077               config.setSystemProperties();
078            return config;
079         }
080      }
081
082      return null;
083   }
084
085   /**
086    * Returns the list of candidate system default configuration file names.
087    *
088    * <p>
089    * If the <js>"juneau.configFile"</js> system property is set, returns a singleton of that value.
090    * <br>Otherwise, returns a list consisting of the following values:
091    * <ol>
092    *    <li>File with same name as jar file but with <js>".cfg"</js> extension.  (e.g. <js>"myjar.cfg"</js>)
093    *    <li>Any file ending in <js>".cfg"</js> in the home directory (names ordered alphabetically).
094    *    <li><js>"juneau.cfg"</js>
095    *    <li><js>"default.cfg"</js>
096    *    <li><js>"application.cfg"</js>
097    *    <li><js>"app.cfg"</js>
098    *    <li><js>"settings.cfg"</js>
099    *    <li><js>"application.properties"</js>
100    * </ol>
101    * <p>
102    *
103    * @return
104    *    A list of candidate file names.
105    *    <br>The returned list is modifiable.
106    *    <br>Each call constructs a new list.
107    */
108   public synchronized static List<String> getCandidateSystemDefaultConfigNames() {
109      List<String> l = new ArrayList<>();
110
111      String s = System.getProperty("juneau.configFile");
112      if (s != null) {
113         l.add(s);
114         return l;
115      }
116
117      String cmd = System.getProperty("sun.java.command", "not_found").split("\\s+")[0];
118      if (cmd.endsWith(".jar") && ! cmd.contains("surefirebooter")) {
119         cmd = cmd.replaceAll(".*?([^\\\\\\/]+)\\.jar$", "$1");
120         l.add(cmd + ".cfg");
121         cmd = cmd.replaceAll("[\\.\\_].*$", "");  // Try also without version in jar name.
122         l.add(cmd + ".cfg");
123      }
124
125      Set<File> files = new TreeSet<>(Arrays.asList(new File(".").listFiles()));
126      for (File f : files)
127         if (f.getName().endsWith(".cfg"))
128            l.add(f.getName());
129
130      l.add("juneau.cfg");
131      l.add("default.cfg");
132      l.add("application.cfg");
133      l.add("app.cfg");
134      l.add("settings.cfg");
135      l.add("application.properties");
136
137      return l;
138   }
139
140   private synchronized static Config find(String name) {
141      if (name == null)
142         return null;
143      if (ConfigFileStore.DEFAULT.exists(name))
144         return Config.create(name).store(ConfigFileStore.DEFAULT).build();
145      if (ConfigClasspathStore.DEFAULT.exists(name))
146         return Config.create(name).store(ConfigClasspathStore.DEFAULT).build();
147      return null;
148   }
149
150   //-------------------------------------------------------------------------------------------------------------------
151   // Configurable properties
152   //-------------------------------------------------------------------------------------------------------------------
153
154   static final String PREFIX = "Config";
155
156   /**
157    * Configuration property:  Configuration name.
158    *
159    * <h5 class='section'>Property:</h5>
160    * <ul 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    */
1268   public ObjectMap getObjectMap(String key) throws ParseException {
1269      return getObject(key, ObjectMap.class);
1270   }
1271
1272   /**
1273    * Convenience method for returning a config entry as an {@link ObjectMap}.
1274    *
1275    * @param key The key.
1276    * @param def The default value.
1277    * @return The value, or the default value if the section or key does not exist.
1278    * @throws ParseException Malformed input encountered.
1279    */
1280   public ObjectMap getObjectMap(String key, ObjectMap def) throws ParseException {
1281      return getObjectWithDefault(key, def, ObjectMap.class);
1282   }
1283
1284   /**
1285    * Convenience method for returning a config entry as an {@link ObjectList}.
1286    *
1287    * @param key The key.
1288    * @return The value, or <jk>null</jk> if the section or key does not exist.
1289    * @throws ParseException Malformed input encountered.
1290    */
1291   public ObjectList getObjectList(String key) throws ParseException {
1292      return getObject(key, ObjectList.class);
1293   }
1294
1295   /**
1296    * Convenience method for returning a config entry as an {@link ObjectList}.
1297    *
1298    * @param key The key.
1299    * @param def The default value.
1300    * @return The value, or the default value if the section or key does not exist.
1301    * @throws ParseException Malformed input encountered.
1302    */
1303   public ObjectList getObjectList(String key, ObjectList def) throws ParseException {
1304      return getObjectWithDefault(key, def, ObjectList.class);
1305   }
1306
1307   /**
1308    * Returns the keys of the entries in the specified section.
1309    *
1310    * @param section
1311    *    The section name to write from.
1312    *    <br>If empty, refers to the default section.
1313    *    <br>Must not be <jk>null</jk>.
1314    * @return
1315    *    An unmodifiable set of keys, or an empty set if the section doesn't exist.
1316    */
1317   public Set<String> getKeys(String section) {
1318      return configMap.getKeys(section(section));
1319   }
1320
1321   /**
1322    * Copies the entries in a section to the specified bean by calling the public setters on that bean.
1323    *
1324    * @param section
1325    *    The section name to write from.
1326    *    <br>If empty, refers to the default section.
1327    *    <br>Must not be <jk>null</jk>.
1328    * @param bean The bean to set the properties on.
1329    * @param ignoreUnknownProperties
1330    *    If <jk>true</jk>, don't throw an {@link IllegalArgumentException} if this section contains a key that doesn't
1331    *    correspond to a setter method.
1332    * @return An object map of the changes made to the bean.
1333    * @throws ParseException If parser was not set on this config file or invalid properties were found in the section.
1334    * @throws UnsupportedOperationException If configuration is read only.
1335    */
1336   public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties) throws ParseException {
1337      checkWrite();
1338      assertFieldNotNull(bean, "bean");
1339      section = section(section);
1340
1341      Set<String> keys = configMap.getKeys(section);
1342      if (keys == null)
1343         throw new IllegalArgumentException("Section '"+section+"' not found in configuration.");
1344
1345      BeanMap<?> bm = beanSession.toBeanMap(bean);
1346      for (String k : keys) {
1347         BeanPropertyMeta bpm = bm.getPropertyMeta(k);
1348         if (bpm == null) {
1349            if (! ignoreUnknownProperties)
1350               throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section);
1351         } else {
1352            bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass()));
1353         }
1354      }
1355
1356      return this;
1357   }
1358
1359   /**
1360    * Shortcut for calling <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>.
1361    *
1362    * @param section
1363    *    The section name to write from.
1364    *    <br>If empty, refers to the default section.
1365    *    <br>Must not be <jk>null</jk>.
1366    * @param c The bean class to create.
1367    * @return A new bean instance.
1368    * @throws ParseException Malformed input encountered.
1369    */
1370   public <T> T getSectionAsBean(String section, Class<T> c) throws ParseException {
1371      return getSectionAsBean(section, c, false);
1372   }
1373
1374   /**
1375    * Converts this config file section to the specified bean instance.
1376    *
1377    * <p>
1378    * Key/value pairs in the config file section get copied as bean property values to the specified bean class.
1379    *
1380    * <h5 class='figure'>Example config file</h5>
1381    * <p class='bcode w800'>
1382    *    <cs>[MyAddress]</cs>
1383    *    <ck>name</ck> = <cv>John Smith</cv>
1384    *    <ck>street</ck> = <cv>123 Main Street</cv>
1385    *    <ck>city</ck> = <cv>Anywhere</cv>
1386    *    <ck>state</ck> = <cv>NY</cv>
1387    *    <ck>zip</ck> = <cv>12345</cv>
1388    * </p>
1389    *
1390    * <h5 class='figure'>Example bean</h5>
1391    * <p class='bcode w800'>
1392    *    <jk>public class</jk> Address {
1393    *       public String name, street, city;
1394    *       public StateEnum state;
1395    *       public int zip;
1396    *    }
1397    * </p>
1398    *
1399    * <h5 class='figure'>Example usage</h5>
1400    * <p class='bcode w800'>
1401    *    Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
1402    *    Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>);
1403    * </p>
1404    *
1405    * @param section
1406    *    The section name to write from.
1407    *    <br>If empty, refers to the default section.
1408    *    <br>Must not be <jk>null</jk>.
1409    * @param c The bean class to create.
1410    * @param ignoreUnknownProperties
1411    *    If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property
1412    *    name.
1413    * @return A new bean instance, or <jk>null</jk> if the section doesn't exist.
1414    * @throws ParseException Unknown property was encountered in section.
1415    */
1416   public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties) throws ParseException {
1417      assertFieldNotNull(c, "c");
1418      section = section(section);
1419
1420      if (! configMap.hasSection(section))
1421         return null;
1422
1423      Set<String> keys = configMap.getKeys(section);
1424
1425      BeanMap<T> bm = beanSession.newBeanMap(c);
1426      for (String k : keys) {
1427         BeanPropertyMeta bpm = bm.getPropertyMeta(k);
1428         if (bpm == null) {
1429            if (! ignoreUnknownProperties)
1430               throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section);
1431         } else {
1432            bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass()));
1433         }
1434      }
1435
1436      return bm.getBean();
1437   }
1438
1439   /**
1440    * Returns a section of this config copied into an {@link ObjectMap}.
1441    *
1442    * @param section
1443    *    The section name to write from.
1444    *    <br>If empty, refers to the default section.
1445    *    <br>Must not be <jk>null</jk>.
1446    * @return A new {@link ObjectMap}, or <jk>null</jk> if the section doesn't exist.
1447    * @throws ParseException Malformed input encountered.
1448    */
1449   public ObjectMap getSectionAsMap(String section) throws ParseException {
1450      section = section(section);
1451
1452      if (! configMap.hasSection(section))
1453         return null;
1454
1455      Set<String> keys = configMap.getKeys(section);
1456
1457      ObjectMap om = new ObjectMap();
1458      for (String k : keys)
1459         om.put(k, getObject(section + '/' + k, Object.class));
1460      return om;
1461   }
1462
1463   /**
1464    * Returns the section names defined in this config.
1465    *
1466    * @return The section names defined in this config.
1467    */
1468   public Set<String> getSections() {
1469      return Collections.unmodifiableSet(configMap.getSections());
1470   }
1471
1472   /**
1473    * Wraps a config file section inside a Java interface so that values in the section can be read and
1474    * write using getters and setters.
1475    *
1476    * <h5 class='figure'>Example config file</h5>
1477    * <p class='bcode w800'>
1478    *    <cs>[MySection]</cs>
1479    *    <ck>string</ck> = <cv>foo</cv>
1480    *    <ck>int</ck> = <cv>123</cv>
1481    *    <ck>enum</ck> = <cv>ONE</cv>
1482    *    <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv>
1483    *    <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv>
1484    *    <ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv>
1485    * </p>
1486    *
1487    * <h5 class='figure'>Example interface</h5>
1488    * <p class='bcode w800'>
1489    *    <jk>public interface</jk> MyConfigInterface {
1490    *
1491    *       String getString();
1492    *       <jk>void</jk> setString(String x);
1493    *
1494    *       <jk>int</jk> getInt();
1495    *       <jk>void</jk> setInt(<jk>int</jk> x);
1496    *
1497    *       MyEnum getEnum();
1498    *       <jk>void</jk> setEnum(MyEnum x);
1499    *
1500    *       MyBean getBean();
1501    *       <jk>void</jk> setBean(MyBean x);
1502    *
1503    *       <jk>int</jk>[][][] getInt3dArray();
1504    *       <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x);
1505    *
1506    *       Map&lt;String,List&lt;MyBean[][][]&gt;&gt; getBean1d3dListMap();
1507    *       <jk>void</jk> setBean1d3dListMap(Map&lt;String,List&lt;MyBean[][][]&gt;&gt; x);
1508    *    }
1509    * </p>
1510    *
1511    * <h5 class='figure'>Example usage</h5>
1512    * <p class='bcode w800'>
1513    *    Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
1514    *
1515    *    MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>);
1516    *
1517    *    <jk>int</jk> myInt = ci.getInt();
1518    *
1519    *    ci.setBean(<jk>new</jk> MyBean());
1520    *
1521    *    cf.save();
1522    * </p>
1523    *
1524    * <ul class='notes'>
1525    *    <li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown.
1526    * </ul>
1527    *
1528    * @param section
1529    *    The section name to write from.
1530    *    <br>If empty, refers to the default section.
1531    *    <br>Must not be <jk>null</jk>.
1532    * @param c The proxy interface class.
1533    * @return The proxy interface.
1534    */
1535   @SuppressWarnings("unchecked")
1536   public <T> T getSectionAsInterface(String section, final Class<T> c) {
1537      assertFieldNotNull(c, "c");
1538      final String section2 = section(section);
1539
1540      if (! c.isInterface())
1541         throw new IllegalArgumentException("Class '"+c.getName()+"' passed to getSectionAsInterface() is not an interface.");
1542
1543      InvocationHandler h = new InvocationHandler() {
1544
1545         @Override
1546         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1547            BeanInfo bi = Introspector.getBeanInfo(c, null);
1548            for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
1549               Method rm = pd.getReadMethod(), wm = pd.getWriteMethod();
1550               if (method.equals(rm))
1551                  return Config.this.getObject(section2 + '/' + pd.getName(), rm.getGenericReturnType());
1552               if (method.equals(wm))
1553                  return Config.this.set(section2 + '/' + pd.getName(), args[0]);
1554            }
1555            throw new UnsupportedOperationException("Unsupported interface method.  method='" + method + "'");
1556         }
1557      };
1558
1559      return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h);
1560   }
1561
1562   /**
1563    * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value.
1564    *
1565    * @param key The key.
1566    * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value.
1567    */
1568   public boolean exists(String key) {
1569      return isNotEmpty(getString(key, null));
1570   }
1571
1572   /**
1573    * Creates the specified section if it doesn't exist.
1574    *
1575    * <p>
1576    * Returns the existing section if it already exists.
1577    *
1578    * @param name
1579    *    The section name.
1580    *    <br>Must not be <jk>null</jk>.
1581    *    <br>Use blank for the default section.
1582    * @param preLines
1583    *    Optional comment and blank lines to add immediately before the section.
1584    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
1585    * @return The appended or existing section.
1586    * @throws UnsupportedOperationException If configuration is read only.
1587    */
1588   public Config setSection(String name, List<String> preLines) {
1589      try {
1590         return setSection(section(name), preLines, null);
1591      } catch (SerializeException e) {
1592         throw new RuntimeException(e);  // Impossible.
1593      }
1594   }
1595
1596   /**
1597    * Creates the specified section if it doesn't exist.
1598    *
1599    * @param name
1600    *    The section name.
1601    *    <br>Must not be <jk>null</jk>.
1602    *    <br>Use blank for the default section.
1603    * @param preLines
1604    *    Optional comment and blank lines to add immediately before the section.
1605    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
1606    * @param contents
1607    *    Values to set in the new section.
1608    *    <br>Can be <jk>null</jk>.
1609    * @return The appended or existing section.
1610    * @throws SerializeException Contents could not be serialized.
1611    * @throws UnsupportedOperationException If configuration is read only.
1612    */
1613   public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException {
1614      checkWrite();
1615      configMap.setSection(section(name), preLines);
1616
1617      if (contents != null)
1618         for (Map.Entry<String,Object> e : contents.entrySet())
1619            set(section(name) + '/' + e.getKey(), e.getValue());
1620
1621      return this;
1622   }
1623
1624   /**
1625    * Removes the section with the specified name.
1626    *
1627    * @param name The name of the section to remove
1628    * @return This object (for method chaining).
1629    * @throws UnsupportedOperationException If configuration is read only.
1630    */
1631   public Config removeSection(String name) {
1632      checkWrite();
1633      configMap.removeSection(name);
1634      return this;
1635   }
1636
1637   /**
1638    * Creates the specified import statement if it doesn't exist.
1639    *
1640    * @param sectionName
1641    *    The section name where to place the import statement.
1642    *    <br>Must not be <jk>null</jk>.
1643    *    <br>Use blank for the default section.
1644    * @param importName
1645    *    The import name.
1646    *    <br>Must not be <jk>null</jk>.
1647    * @param preLines
1648    *    Optional comment and blank lines to add immediately before the import statement.
1649    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
1650    * @return The appended or existing import statement.
1651    * @throws UnsupportedOperationException If configuration is read only.
1652    */
1653   public Config setImport(String sectionName, String importName, List<String> preLines) {
1654      checkWrite();
1655      configMap.setImport(section(name), importName, preLines);
1656      return this;
1657   }
1658
1659   /**
1660    * Removes the import statement with the specified name from the specified section.
1661    *
1662    * @param sectionName
1663    *    The section name where to place the import statement.
1664    *    <br>Must not be <jk>null</jk>.
1665    *    <br>Use blank for the default section.
1666    * @param importName
1667    *    The import name.
1668    *    <br>Must not be <jk>null</jk>.
1669    * @return This object (for method chaining).
1670    * @throws UnsupportedOperationException If configuration is read only.
1671    */
1672   public Config removeImport(String sectionName, String importName) {
1673      checkWrite();
1674      configMap.removeImport(sectionName, importName);
1675      return this;
1676   }
1677
1678   /**
1679    * Loads the contents of the specified map of maps into this config.
1680    *
1681    * @param m The maps to load.
1682    * @return This object (for method chaining).
1683    * @throws SerializeException Value could not be serialized.
1684    */
1685   public Config load(Map<String,Map<String,Object>> m) throws SerializeException {
1686      if (m != null)
1687         for (Map.Entry<String,Map<String,Object>> e : m.entrySet()) {
1688            setSection(e.getKey(), null, e.getValue());
1689         }
1690      return this;
1691   }
1692
1693   /**
1694    * Commit the changes in this config to the store.
1695    *
1696    * @return This object (for method chaining).
1697    * @throws IOException Thrown by underlying stream.
1698    * @throws UnsupportedOperationException If configuration is read only.
1699    */
1700   public Config commit() throws IOException {
1701      checkWrite();
1702      configMap.commit();
1703      return this;
1704   }
1705
1706   /**
1707    * Saves this config file to the specified writer as an INI file.
1708    *
1709    * <p>
1710    * The writer will automatically be closed.
1711    *
1712    * @param w The writer to send the output to.
1713    * @return This object (for method chaining).
1714    * @throws IOException If a problem occurred trying to send contents to the writer.
1715    */
1716   @Override /* Writable */
1717   public Writer writeTo(Writer w) throws IOException {
1718      return configMap.writeTo(w);
1719   }
1720
1721   /**
1722    * Add a listener to this config to react to modification events.
1723    *
1724    * <p>
1725    * Listeners should be removed using {@link #removeListener(ConfigEventListener)}.
1726    *
1727    * @param listener The new listener to add.
1728    * @return This object (for method chaining).
1729    */
1730   public synchronized Config addListener(ConfigEventListener listener) {
1731      listeners.add(listener);
1732      return this;
1733   }
1734
1735   /**
1736    * Removes a listener from this config.
1737    *
1738    * @param listener The listener to remove.
1739    * @return This object (for method chaining).
1740    */
1741   public synchronized Config removeListener(ConfigEventListener listener) {
1742      listeners.remove(listener);
1743      return this;
1744   }
1745
1746   /**
1747    * Closes this configuration object by unregistering it from the underlying config map.
1748    *
1749    * @throws IOException Thrown by underlying stream.
1750    */
1751   public void close() throws IOException {
1752      configMap.unregister(this);
1753   }
1754
1755   /**
1756    * Overwrites the contents of the config file.
1757    *
1758    * @param contents The new contents of the config file.
1759    * @param synchronous Wait until the change has been persisted before returning this map.
1760    * @return This object (for method chaining).
1761    * @throws IOException Thrown by underlying stream.
1762    * @throws InterruptedException Thread was interrupted.
1763    * @throws UnsupportedOperationException If configuration is read only.
1764    */
1765   public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException {
1766      checkWrite();
1767      configMap.load(IOUtils.read(contents), synchronous);
1768      return this;
1769   }
1770
1771   /**
1772    * Overwrites the contents of the config file.
1773    *
1774    * @param contents The new contents of the config file.
1775    * @param synchronous Wait until the change has been persisted before returning this map.
1776    * @return This object (for method chaining).
1777    * @throws IOException Thrown by underlying stream.
1778    * @throws InterruptedException Thread was interrupted.
1779    * @throws UnsupportedOperationException If configuration is read only.
1780    */
1781   public Config load(String contents, boolean synchronous) throws IOException, InterruptedException {
1782      checkWrite();
1783      configMap.load(contents, synchronous);
1784      return this;
1785   }
1786
1787   /**
1788    * Does a rollback of any changes on this config currently in memory.
1789    *
1790    * @return This object (for method chaining).
1791    * @throws UnsupportedOperationException If configuration is read only.
1792    */
1793   public Config rollback() {
1794      checkWrite();
1795      configMap.rollback();
1796      return this;
1797   }
1798
1799   /**
1800    * Returns the values in this config map as a map of maps.
1801    *
1802    * <p>
1803    * This is considered a snapshot copy of the config map.
1804    *
1805    * <p>
1806    * The returned map is modifiable, but modifications to the returned map are not reflected in the config map.
1807    *
1808    * @return A copy of this config as a map of maps.
1809    */
1810   @Override /* Context */
1811   public ObjectMap toMap() {
1812      return configMap.asMap();
1813   }
1814
1815
1816   //-----------------------------------------------------------------------------------------------------------------
1817   // Test methods
1818   //-----------------------------------------------------------------------------------------------------------------
1819
1820   ConfigMap getConfigMap() {
1821      return configMap;
1822   }
1823
1824   List<ConfigEventListener> getListeners() {
1825      return Collections.unmodifiableList(listeners);
1826   }
1827
1828
1829   //-----------------------------------------------------------------------------------------------------------------
1830   // Interface methods
1831   //-----------------------------------------------------------------------------------------------------------------
1832
1833   /**
1834    * Unused.
1835    */
1836   @Override /* Context */
1837   public Session createSession(SessionArgs args) {
1838      throw new UnsupportedOperationException();
1839   }
1840
1841   /**
1842    * Unused.
1843    */
1844   @Override /* Context */
1845   public SessionArgs createDefaultSessionArgs() {
1846      throw new UnsupportedOperationException();
1847   }
1848
1849   @Override /* ConfigEventListener */
1850   public void onConfigChange(ConfigEvents events) {
1851      for (ConfigEventListener l : listeners)
1852         l.onConfigChange(events);
1853   }
1854
1855   @Override /* Writable */
1856   public MediaType getMediaType() {
1857      return MediaType.PLAIN;
1858   }
1859
1860
1861   //-----------------------------------------------------------------------------------------------------------------
1862   // Private methods
1863   //-----------------------------------------------------------------------------------------------------------------
1864
1865   private String serialize(Object value, Serializer serializer) throws SerializeException {
1866      if (value == null)
1867         return "";
1868      if (serializer == null)
1869         serializer = this.serializer;
1870      Class<?> c = value.getClass();
1871      if (value instanceof CharSequence)
1872         return nlIfMl((CharSequence)value);
1873      if (isSimpleType(c))
1874         return value.toString();
1875
1876      if (value instanceof byte[]) {
1877         String s = null;
1878         byte[] b = (byte[])value;
1879         if (binaryFormat == BinaryFormat.HEX)
1880            s = toHex(b);
1881         else if (binaryFormat == BinaryFormat.SPACED_HEX)
1882            s = toSpacedHex(b);
1883         else
1884            s = base64Encode(b);
1885         int l = binaryLineLength;
1886         if (l <= 0 || s.length() <= l)
1887            return s;
1888         StringBuilder sb = new StringBuilder();
1889         for (int i = 0; i < s.length(); i += l)
1890            sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l)));
1891         return sb.toString();
1892      }
1893
1894      String r = null;
1895      if (multiLineValuesOnSeparateLines)
1896         r = "\n" + (String)serializer.serialize(value);
1897      else
1898         r = (String)serializer.serialize(value);
1899
1900      if (r.startsWith("'"))
1901         return r.substring(1, r.length()-1);
1902      return r;
1903   }
1904
1905   private String nlIfMl(CharSequence cs) {
1906      String s = cs.toString();
1907      if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines)
1908         return "\n" + s;
1909      return s;
1910   }
1911
1912   @SuppressWarnings({ "unchecked" })
1913   private <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException {
1914
1915      if (isEmpty(s))
1916         return null;
1917
1918      if (isSimpleType(type))
1919         return (T)beanSession.convertToType(s, (Class<?>)type);
1920
1921      if (type == byte[].class) {
1922         if (s.indexOf('\n') != -1)
1923            s = s.replaceAll("\n", "");
1924         try {
1925            switch (binaryFormat) {
1926               case HEX: return (T)fromHex(s);
1927               case SPACED_HEX: return (T)fromSpacedHex(s);
1928               default: return (T)base64Decode(s);
1929            }
1930         } catch (Exception e) {
1931            throw new ParseException(e, "Value could not be converted to a byte array.");
1932         }
1933      }
1934
1935      if (parser == null)
1936         parser = this.parser;
1937
1938      if (parser instanceof JsonParser) {
1939         char s1 = firstNonWhitespaceChar(s);
1940         if (isArray(type) && s1 != '[')
1941            s = '[' + s + ']';
1942         else if (s1 != '[' && s1 != '{' && ! "null".equals(s))
1943            s = '\'' + s + '\'';
1944      }
1945
1946      return parser.parse(s, type, args);
1947   }
1948
1949   private boolean isSimpleType(Type t) {
1950      if (! (t instanceof Class))
1951         return false;
1952      Class<?> c = (Class<?>)t;
1953      return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum());
1954   }
1955
1956   private boolean isArray(Type t) {
1957      if (! (t instanceof Class))
1958         return false;
1959      Class<?> c = (Class<?>)t;
1960      return (c.isArray());
1961   }
1962
1963   private String sname(String key) {
1964      assertFieldNotNull(key, "key");
1965      int i = key.indexOf('/');
1966      if (i == -1)
1967         return "";
1968      return key.substring(0, i);
1969   }
1970
1971   private String skey(String key) {
1972      int i = key.indexOf('/');
1973      if (i == -1)
1974         return key;
1975      return key.substring(i+1);
1976   }
1977
1978   private String section(String section) {
1979      assertFieldNotNull(section, "section");
1980      if (isEmpty(section))
1981         return "";
1982      return section;
1983   }
1984
1985   private void checkWrite() {
1986      if (readOnly)
1987         throw new UnsupportedOperationException("Cannot call this method on a read-only configuration.");
1988   }
1989
1990
1991   //-----------------------------------------------------------------------------------------------------------------
1992   // Other methods
1993   //-----------------------------------------------------------------------------------------------------------------
1994
1995   @Override /* Object */
1996   public String toString() {
1997      return configMap.toString();
1998   }
1999
2000   @Override /* Object */
2001   protected void finalize() throws Throwable {
2002      close();
2003   }
2004}