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.common.internal.ArgUtils.*;
016import static org.apache.juneau.common.internal.IOUtils.*;
017import static org.apache.juneau.common.internal.StringUtils.*;
018import static org.apache.juneau.common.internal.ThrowableUtils.*;
019import static org.apache.juneau.internal.CollectionUtils.*;
020
021import java.io.*;
022import java.lang.annotation.*;
023import java.lang.reflect.*;
024import java.util.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.collections.*;
028import org.apache.juneau.config.event.*;
029import org.apache.juneau.config.internal.*;
030import org.apache.juneau.config.mod.*;
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.*;
038import org.apache.juneau.utils.*;
039
040/**
041 * Main configuration API class.
042 *
043 * <h5 class='section'>Notes:</h5><ul>
044 *    <li class='note'>This class is thread safe and reusable.
045 * </ul>
046 *
047 * <h5 class='section'>See Also:</h5><ul>
048 *    <li class='link'><a class="doclink" href="../../../../index.html#juneau-config">Overview &gt; juneau-config</a>
049 * </ul>
050 */
051public final class Config extends Context implements ConfigEventListener {
052
053   //-----------------------------------------------------------------------------------------------------------------
054   // Static
055   //-----------------------------------------------------------------------------------------------------------------
056
057   private static boolean DISABLE_AUTO_SYSTEM_PROPS = Boolean.getBoolean("juneau.disableAutoSystemProps");
058   private static volatile Config SYSTEM_DEFAULT = findSystemDefault();
059
060   /**
061    * Sets a system default configuration.
062    *
063    * @param systemDefault The new system default configuration.
064    */
065   public synchronized static void setSystemDefault(Config systemDefault) {
066      SYSTEM_DEFAULT = systemDefault;
067   }
068
069   /**
070    * Returns the system default configuration.
071    *
072    * @return The system default configuration, or <jk>null</jk> if it doesn't exist.
073    */
074   public synchronized static Config getSystemDefault() {
075      return SYSTEM_DEFAULT;
076   }
077
078   private synchronized static Config findSystemDefault() {
079
080      for (String n : getCandidateSystemDefaultConfigNames()) {
081         Config config = find(n);
082         if (config != null) {
083            if (! DISABLE_AUTO_SYSTEM_PROPS)
084               config.setSystemProperties();
085            return config;
086         }
087      }
088
089      return null;
090   }
091
092   /**
093    * Returns the list of candidate system default configuration file names.
094    *
095    * <p>
096    * If the <js>"juneau.configFile"</js> system property is set, returns a singleton of that value.
097    * <br>Otherwise, returns a list consisting of the following values:
098    * <ol>
099    *    <li>File with same name as jar file but with <js>".cfg"</js> extension.  (e.g. <js>"myjar.cfg"</js>)
100    *    <li>Any file ending in <js>".cfg"</js> in the home directory (names ordered alphabetically).
101    *    <li><js>"juneau.cfg"</js>
102    *    <li><js>"default.cfg"</js>
103    *    <li><js>"application.cfg"</js>
104    *    <li><js>"app.cfg"</js>
105    *    <li><js>"settings.cfg"</js>
106    *    <li><js>"application.properties"</js>
107    * </ol>
108    * <p>
109    *
110    * @return
111    *    A list of candidate file names.
112    *    <br>The returned list is modifiable.
113    *    <br>Each call constructs a new list.
114    */
115   public synchronized static List<String> getCandidateSystemDefaultConfigNames() {
116      List<String> l = list();
117
118      String s = System.getProperty("juneau.configFile");
119      if (s != null) {
120         l.add(s);
121         return l;
122      }
123
124      String cmd = System.getProperty("sun.java.command", "not_found").split("\\s+")[0];
125      if (cmd.endsWith(".jar") && ! cmd.contains("surefirebooter")) {
126         cmd = cmd.replaceAll(".*?([^\\\\\\/]+)\\.jar$", "$1");
127         l.add(cmd + ".cfg");
128         cmd = cmd.replaceAll("[\\.\\_].*$", "");  // Try also without version in jar name.
129         l.add(cmd + ".cfg");
130      }
131
132      Set<File> files = sortedSet(new File(".").listFiles());
133      for (File f : files)
134         if (f.getName().endsWith(".cfg"))
135            l.add(f.getName());
136
137      l.add("juneau.cfg");
138      l.add("default.cfg");
139      l.add("application.cfg");
140      l.add("app.cfg");
141      l.add("settings.cfg");
142      l.add("application.properties");
143
144      return l;
145   }
146
147   private synchronized static Config find(String name) {
148      if (name == null)
149         return null;
150      if (FileStore.DEFAULT.exists(name))
151         return Config.create(name).store(FileStore.DEFAULT).build();
152      if (ClasspathStore.DEFAULT.exists(name))
153         return Config.create(name).store(ClasspathStore.DEFAULT).build();
154      return null;
155   }
156
157   /**
158    * Creates a new builder for this object.
159    *
160    * @return A new builder.
161    */
162   public static Builder create() {
163      return new Builder();
164   }
165
166   /**
167    * Same as {@link #create()} but initializes the builder with the specified config name.
168    *
169    * @param name The configuration name.
170    * @return A new builder.
171    */
172   public static Builder create(String name) {
173      return new Builder().name(name);
174   }
175
176   //-------------------------------------------------------------------------------------------------------------------
177   // Builder
178   //-------------------------------------------------------------------------------------------------------------------
179
180   /**
181    * Builder class.
182    */
183   @FluentSetters
184   public static class Builder extends Context.Builder {
185
186      String name;
187      ConfigStore store;
188      WriterSerializer serializer;
189      ReaderParser parser;
190      Map<Character,Mod> mods;
191      VarResolver varResolver;
192      int binaryLineLength;
193      BinaryFormat binaryFormat;
194      boolean multiLineValuesOnSeparateLines, readOnly;
195
196      /**
197       * Constructor, default settings.
198       */
199      protected Builder() {
200         super();
201         name = env("Config.name", "Configuration.cfg");
202         store = FileStore.DEFAULT;
203         serializer = Json5Serializer.DEFAULT;
204         parser = JsonParser.DEFAULT;
205         mods = map();
206         mods(XorEncodeMod.INSTANCE);
207         varResolver = VarResolver.DEFAULT;
208         binaryLineLength = env("Config.binaryLineLength", -1);
209         binaryFormat = env("Config.binaryFormat", BinaryFormat.BASE64);
210         multiLineValuesOnSeparateLines = env("Config.multiLineValuesOnSeparateLines", false);
211         readOnly = env("Config.readOnly", false);
212      }
213
214      /**
215       * Copy constructor.
216       *
217       * @param copyFrom The bean to copy from.
218       */
219      protected Builder(Config copyFrom) {
220         super(copyFrom);
221         name = copyFrom.name;
222         store = copyFrom.store;
223         serializer = copyFrom.serializer;
224         parser = copyFrom.parser;
225         mods = copyOf(copyFrom.mods);
226         varResolver = copyFrom.varResolver;
227         binaryLineLength = copyFrom.binaryLineLength;
228         binaryFormat = copyFrom.binaryFormat;
229         multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines;
230         readOnly = copyFrom.readOnly;
231      }
232
233      /**
234       * Copy constructor.
235       *
236       * @param copyFrom The builder to copy from.
237       */
238      protected Builder(Builder copyFrom) {
239         super(copyFrom);
240         name = copyFrom.name;
241         store = copyFrom.store;
242         serializer = copyFrom.serializer;
243         parser = copyFrom.parser;
244         mods = copyOf(copyFrom.mods);
245         varResolver = copyFrom.varResolver;
246         binaryLineLength = copyFrom.binaryLineLength;
247         binaryFormat = copyFrom.binaryFormat;
248         multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines;
249         readOnly = copyFrom.readOnly;
250      }
251
252      @Override /* Context.Builder */
253      public Builder copy() {
254         return new Builder(this);
255      }
256
257      @Override /* Context.Builder */
258      public Config build() {
259         return build(Config.class);
260      }
261
262      //-----------------------------------------------------------------------------------------------------------------
263      // Properties
264      //-----------------------------------------------------------------------------------------------------------------
265
266      /**
267       * Configuration name.
268       *
269       * <p>
270       * Specifies the configuration name.
271       * <br>This is typically the configuration file name, although
272       * the name can be anything identifiable by the {@link ConfigStore} used for retrieving and storing the configuration.
273       *
274       * @param value
275       *    The new value for this property.
276       *    <br>The default is the first value found:
277       *    <ul>
278       *       <li>System property <js>"Config.name"
279       *       <li>Environment variable <js>"CONFIG_NAME"
280       *       <li><js>"Configuration.cfg"</js>
281       *    </ul>
282       * @return This object.
283       */
284      public Builder name(String value) {
285         name = value;
286         return this;
287      }
288
289      /**
290       * Configuration store.
291       *
292       * <p>
293       * The configuration store used for retrieving and storing configurations.
294       *
295       * @param value
296       *    The new value for this property.
297       *    <br>The default is {@link FileStore#DEFAULT}.
298       * @return This object.
299       */
300      public Builder store(ConfigStore value) {
301         store = value;
302         return this;
303      }
304
305      /**
306       * Configuration store.
307       *
308       * <p>
309       * Convenience method for calling <code>store(ConfigMemoryStore.<jsf>DEFAULT</jsf>)</code>.
310       *
311       * @return This object.
312       */
313      public Builder memStore() {
314         store = MemoryStore.DEFAULT;
315         return this;
316      }
317
318      /**
319       * POJO serializer.
320       *
321       * <p>
322       * The serializer to use for serializing POJO values.
323       *
324       * @param value
325       *    The new value for this property.
326       *    <br>The default is {@link Json5Serializer#DEFAULT}
327       * @return This object.
328       */
329      public Builder serializer(WriterSerializer value) {
330         serializer = value;
331         return this;
332      }
333
334      /**
335       * POJO parser.
336       *
337       * <p>
338       * The parser to use for parsing values to POJOs.
339       *
340       * @param value
341       *    The new value for this property.
342       *    <br>The default is {@link JsonParser#DEFAULT}.
343       * @return This object.
344       */
345      public Builder parser(ReaderParser value) {
346         parser = value;
347         return this;
348      }
349
350      /**
351       * Adds a value modifier.
352       *
353       * <p>
354       * Modifiers are used to modify entry value before being persisted.
355       *
356       * @param values
357       *    The mods to apply to this config.
358       * @return This object.
359       */
360      public Builder mods(Mod...values) {
361         for (Mod value : values)
362            mods.put(value.getId(), value);
363         return this;
364      }
365
366      /**
367       * SVL variable resolver.
368       *
369       * <p>
370       * The resolver to use for resolving SVL variables.
371       *
372       * @param value
373       *    The new value for this property.
374       *    <br>The default is {@link VarResolver#DEFAULT}.
375       * @return This object.
376       */
377      public Builder varResolver(VarResolver value) {
378         varResolver = value;
379         return this;
380      }
381
382      /**
383       * Binary value line length.
384       *
385       * <p>
386       * When serializing binary values, lines will be split after this many characters.
387       * <br>Use <c>-1</c> to represent no line splitting.
388       *
389       * @param value
390       *    The new value for this property.
391       *    <br>The default is the first value found:
392       *    <ul>
393       *       <li>System property <js>"Config.binaryLineLength"
394       *       <li>Environment variable <js>"CONFIG_BINARYLINELENGTH"
395       *       <li><c>-1</c>
396       *    </ul>
397       * @return This object.
398       */
399      public Builder binaryLineLength(int value) {
400         binaryLineLength = value;
401         return this;
402      }
403
404      /**
405       * Binary value format.
406       *
407       * <p>
408       * The format to use when persisting byte arrays.
409       *
410       * <ul class='values'>
411       *    <li>{@link BinaryFormat#BASE64} - BASE64-encoded string.
412       *    <li>{@link BinaryFormat#HEX} - Hexadecimal.
413       *    <li>{@link BinaryFormat#SPACED_HEX} - Hexadecimal with spaces between bytes.
414       * </ul>
415       *
416       * @param value
417       *    The new value for this property.
418       *    <br>The default is the first value found:
419       *    <ul>
420       *       <li>System property <js>"Config.binaryFormat"
421       *       <li>Environment variable <js>"CONFIG_BINARYFORMAT"
422       *       <li>{@link BinaryFormat#BASE64}
423       *    </ul>
424       * @return This object.
425       */
426      public Builder binaryFormat(BinaryFormat value) {
427         binaryFormat = value;
428         return this;
429      }
430
431      /**
432       * Multi-line values on separate lines.
433       *
434       * <p>
435       * When enabled, multi-line values will always be placed on a separate line from the key.
436       *
437       * <p>
438       * The default is the first value found:
439       *    <ul>
440       *       <li>System property <js>"Config.multiLineValuesOnSeparateLine"
441       *       <li>Environment variable <js>"CONFIG_MULTILINEVALUESONSEPARATELINE"
442       *       <li><jk>false</jk>
443       *    </ul>
444       *
445       * @return This object.
446       */
447      public Builder multiLineValuesOnSeparateLines() {
448         multiLineValuesOnSeparateLines = true;
449         return this;
450      }
451
452      /**
453       * Read-only mode.
454       *
455       * <p>
456       * When enabled, attempts to call any setters on this object will throw an {@link UnsupportedOperationException}.
457       *
458       * <p>
459       *    The default is the first value found:
460       *    <ul>
461       *       <li>System property <js>"Config.readOnly"
462       *       <li>Environment variable <js>"CONFIG_READONLY"
463       *       <li><jk>false</jk>
464       *    </ul>
465       *
466       * @return This object.
467       */
468      public Builder readOnly() {
469         readOnly = true;
470         return this;
471      }
472
473      // <FluentSetters>
474
475      @Override /* GENERATED - org.apache.juneau.Context.Builder */
476      public Builder annotations(Annotation...values) {
477         super.annotations(values);
478         return this;
479      }
480
481      @Override /* GENERATED - org.apache.juneau.Context.Builder */
482      public Builder apply(AnnotationWorkList work) {
483         super.apply(work);
484         return this;
485      }
486
487      @Override /* GENERATED - org.apache.juneau.Context.Builder */
488      public Builder applyAnnotations(java.lang.Class<?>...fromClasses) {
489         super.applyAnnotations(fromClasses);
490         return this;
491      }
492
493      @Override /* GENERATED - org.apache.juneau.Context.Builder */
494      public Builder applyAnnotations(Method...fromMethods) {
495         super.applyAnnotations(fromMethods);
496         return this;
497      }
498
499      @Override /* GENERATED - org.apache.juneau.Context.Builder */
500      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
501         super.cache(value);
502         return this;
503      }
504
505      @Override /* GENERATED - org.apache.juneau.Context.Builder */
506      public Builder debug() {
507         super.debug();
508         return this;
509      }
510
511      @Override /* GENERATED - org.apache.juneau.Context.Builder */
512      public Builder debug(boolean value) {
513         super.debug(value);
514         return this;
515      }
516
517      @Override /* GENERATED - org.apache.juneau.Context.Builder */
518      public Builder impl(Context value) {
519         super.impl(value);
520         return this;
521      }
522
523      @Override /* GENERATED - org.apache.juneau.Context.Builder */
524      public Builder type(Class<? extends org.apache.juneau.Context> value) {
525         super.type(value);
526         return this;
527      }
528
529      // </FluentSetters>
530   }
531
532   //-------------------------------------------------------------------------------------------------------------------
533   // Instance
534   //-------------------------------------------------------------------------------------------------------------------
535
536   final String name;
537   final ConfigStore store;
538   final WriterSerializer serializer;
539   final ReaderParser parser;
540   final Map<Character,Mod> mods;
541   final VarResolver varResolver;
542   final int binaryLineLength;
543   final BinaryFormat binaryFormat;
544   final boolean multiLineValuesOnSeparateLines, readOnly;
545   final BeanSession beanSession;
546   final VarResolverSession varSession;
547
548   private final ConfigMap configMap;
549   private final List<ConfigEventListener> listeners = synced(linkedList());
550
551
552   @Override /* Context */
553   public Builder copy() {
554      return new Builder(this);
555   }
556
557   /**
558    * Constructor.
559    *
560    * @param builder The builder for this object.
561    * @throws IOException Thrown by underlying stream.
562    */
563   public Config(Builder builder) throws IOException {
564      super(builder);
565
566      name = builder.name;
567      store = builder.store;
568      configMap = store.getMap(name);
569      configMap.register(this);
570      serializer = builder.serializer;
571      parser = builder.parser;
572      beanSession = parser.getBeanContext().getSession();
573      mods = unmodifiable(copyOf(builder.mods));
574      varResolver = builder.varResolver;
575      varSession = varResolver
576         .copy()
577         .vars(ConfigVar.class)
578         .bean(Config.class, this)
579         .build()
580         .createSession();
581      binaryLineLength = builder.binaryLineLength;
582      binaryFormat = builder.binaryFormat;
583      multiLineValuesOnSeparateLines = builder.multiLineValuesOnSeparateLines;
584      readOnly = builder.readOnly;
585   }
586
587   Config(Config copyFrom, VarResolverSession varSession) {
588      super(copyFrom);
589      name = copyFrom.name;
590      store = copyFrom.store;
591      configMap = copyFrom.configMap;
592      configMap.register(this);
593      serializer = copyFrom.serializer;
594      parser = copyFrom.parser;
595      mods = copyFrom.mods;
596      varResolver = copyFrom.varResolver;
597      this.varSession = varSession;
598      binaryLineLength = copyFrom.binaryLineLength;
599      binaryFormat = copyFrom.binaryFormat;
600      multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines;
601      readOnly = copyFrom.readOnly;
602      beanSession = copyFrom.beanSession;
603   }
604
605   /**
606    * Creates a copy of this config using the specified var session for resolving variables.
607    *
608    * <p>
609    * This creates a shallow copy of the config but replacing the variable resolver.
610    *
611    * @param varSession The var session used for resolving string variables.
612    * @return A new config object.
613    */
614   public Config resolving(VarResolverSession varSession) {
615      return new Config(this, varSession);
616   }
617
618   /**
619    * Returns the name associated with this config (usually a file name).
620    *
621    * @return The name associated with this config, or <jk>null</jk> if it has no name.
622    */
623   public String getName() {
624      return name;
625   }
626
627   //-----------------------------------------------------------------------------------------------------------------
628   // Workhorse getters
629   //-----------------------------------------------------------------------------------------------------------------
630
631   /**
632    * Returns the specified value as a string from the config file.
633    *
634    * <p>
635    * Unlike {@link #getString(String)}, this method doesn't replace SVL variables.
636    *
637    * @param key The key.
638    * @return The value, or <jk>null</jk> if the section or value doesn't exist.
639    */
640   private String getRaw(String key) {
641
642      String sname = sname(key);
643      String skey = skey(key);
644
645      ConfigMapEntry ce = configMap.getEntry(sname, skey);
646
647      if (ce == null)
648         return null;
649
650      return removeMods(ce.getModifiers(), ce.getValue());
651   }
652
653   String applyMods(String mods, String x) {
654      if (mods != null && x != null)
655         for (int i = 0; i < mods.length(); i++)
656            x = getMod(mods.charAt(i)).doApply(x);
657      return x;
658   }
659
660   String removeMods(String mods, String x) {
661      if (mods != null && x != null)
662         for (int i = mods.length()-1; i > -1; i--)
663            x = getMod(mods.charAt(i)).doRemove(x);
664      return x;
665   }
666
667   Mod getMod(char id) {
668      Mod x = mods.get(id);
669      return x == null ? Mod.NO_OP : x;
670   }
671
672   //-----------------------------------------------------------------------------------------------------------------
673   // Utility methods
674   //-----------------------------------------------------------------------------------------------------------------
675
676   /**
677    * Takes the settings defined in this configuration and sets them as system properties.
678    *
679    * @return This object.
680    */
681   public Config setSystemProperties() {
682      for (String section : getSectionNames()) {
683         for (String key : getKeys(section)) {
684            String k = (section.isEmpty() ? key : section + '/' + key);
685            System.setProperty(k, getRaw(k));
686         }
687      }
688      return this;
689   }
690
691   //-----------------------------------------------------------------------------------------------------------------
692   // Workhorse setters
693   //-----------------------------------------------------------------------------------------------------------------
694
695   /**
696    * Sets a value in this config.
697    *
698    * @param key The key.
699    * @param value The value.
700    * @return This object.
701    * @throws UnsupportedOperationException If configuration is read only.
702    */
703   public Config set(String key, String value) {
704      checkWrite();
705      assertArgNotNull("key", key);
706      String sname = sname(key);
707      String skey = skey(key);
708
709      ConfigMapEntry ce = configMap.getEntry(sname, skey);
710      if (ce == null && value == null)
711         return this;
712
713      String s = applyMods(ce == null ? null : ce.getModifiers(), stringify(value));
714
715      configMap.setEntry(sname, skey, s, null, null, null);
716      return this;
717   }
718
719   /**
720    * Adds or replaces an entry with the specified key with a POJO serialized to a string using the registered
721    * serializer.
722    *
723    * <p>
724    * Equivalent to calling <c>put(key, value, isEncoded(key))</c>.
725    *
726    * @param key The key.
727    * @param value The new value POJO.
728    * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
729    * @throws SerializeException
730    *    If serializer could not serialize the value or if a serializer is not registered with this config file.
731    * @throws UnsupportedOperationException If configuration is read only.
732    */
733   public Config set(String key, Object value) throws SerializeException {
734      return set(key, value, null);
735   }
736
737   /**
738    * Same as {@link #set(String, Object)} but allows you to specify the serializer to use to serialize the
739    * value.
740    *
741    * @param key The key.
742    * @param value The new value.
743    * @param serializer
744    *    The serializer to use for serializing the object.
745    *    If <jk>null</jk>, then uses the predefined serializer on the config file.
746    * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
747    * @throws SerializeException
748    *    If serializer could not serialize the value or if a serializer is not registered with this config file.
749    * @throws UnsupportedOperationException If configuration is read only.
750    */
751   public Config set(String key, Object value, Serializer serializer) throws SerializeException {
752      return set(key, serialize(value, serializer));
753   }
754
755   /**
756    * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value.
757    *
758    * @param key The key.
759    * @param value The new value.
760    * @param serializer
761    *    The serializer to use for serializing the object.
762    *    If <jk>null</jk>, then uses the predefined serializer on the config file.
763    * @param modifiers
764    *    Optional modifiers to apply to the value.
765    *    <br>If <jk>null</jk>, then previous value will not be replaced.
766    * @param comment
767    *    Optional same-line comment to add to this value.
768    *    <br>If <jk>null</jk>, then previous value will not be replaced.
769    * @param preLines
770    *    Optional comment or blank lines to add before this entry.
771    *    <br>If <jk>null</jk>, then previous value will not be replaced.
772    * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
773    * @throws SerializeException
774    *    If serializer could not serialize the value or if a serializer is not registered with this config file.
775    * @throws UnsupportedOperationException If configuration is read only.
776    */
777   public Config set(String key, Object value, Serializer serializer, String modifiers, String comment, List<String> preLines) throws SerializeException {
778      checkWrite();
779      assertArgNotNull("key", key);
780      String sname = sname(key);
781      String skey = skey(key);
782      modifiers = nullIfEmpty(modifiers);
783
784      String s = applyMods(modifiers, serialize(value, serializer));
785
786      configMap.setEntry(sname, skey, s, modifiers, comment, preLines);
787      return this;
788   }
789
790   /**
791    * Removes an entry with the specified key.
792    *
793    * @param key The key.
794    * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
795    * @throws UnsupportedOperationException If configuration is read only.
796    */
797   public Config remove(String key) {
798      checkWrite();
799      String sname = sname(key);
800      String skey = skey(key);
801      configMap.removeEntry(sname, skey);
802      return this;
803   }
804
805   /**
806    * Encodes and unencoded entries in this config.
807    *
808    * <p>
809    * If any entries in the config are marked as encoded but not actually encoded,
810    * this will encode them.
811    *
812    * @return This object.
813    * @throws UnsupportedOperationException If configuration is read only.
814    */
815   public Config applyMods() {
816      checkWrite();
817      for (String section : configMap.getSections()) {
818         for (String key : configMap.getKeys(section)) {
819            ConfigMapEntry ce = configMap.getEntry(section, key);
820            if (ce.getModifiers() != null) {
821               String mods = ce.getModifiers();
822               String value = ce.getValue();
823               for (int i = 0; i < mods.length(); i++) {
824                  Mod mod = getMod(mods.charAt(i));
825                  if (! mod.isApplied(value)) {
826                     configMap.setEntry(section, key, mod.apply(value), null, null, null);
827                  }
828               }
829            }
830         }
831      }
832
833      return this;
834   }
835
836   //-----------------------------------------------------------------------------------------------------------------
837   // API methods
838   //-----------------------------------------------------------------------------------------------------------------
839
840   /**
841    * Gets the entry with the specified key.
842    *
843    * <p>
844    * The key can be in one of the following formats...
845    * <ul class='spaced-list'>
846    *    <li>
847    *       <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header).
848    *    <li>
849    *       <js>"section/key"</js> - A value from the specified section.
850    * </ul>
851    *
852    * <p>
853    * If entry does not exist, returns an empty {@link Entry} object.
854    *
855    * @param key The key.
856    * @return The entry bean, never <jk>null</jk>.
857    */
858   public Entry get(String key) {
859      return new Entry(this, configMap, sname(key), skey(key));
860   }
861
862   /**
863    * Gets the entry with the specified key.
864    *
865    * <p>
866    * The key can be in one of the following formats...
867    * <ul class='spaced-list'>
868    *    <li>
869    *       <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header).
870    *    <li>
871    *       <js>"section/key"</js> - A value from the specified section.
872    * </ul>
873    *
874    * <p>
875    * If entry does not exist, returns <jk>null</jk>.
876    *
877    * <h5 class='section'>Notes:</h5><ul>
878    *    <li class='note'>This method is equivalent to calling <c>get(<jv>key</jv>).orElse(<jk>null</jk>);</c>.
879    * </ul>
880    *
881    * @param key The key.
882    * @return The entry value, or <jk>null</jk> if it doesn't exist.
883    */
884   public String getString(String key) {
885      return new Entry(this, configMap, sname(key), skey(key)).orElse(null);
886   }
887
888   /**
889    * Gets the section with the specified name.
890    *
891    * <p>
892    * If section does not exist, returns an empty {@link Section} object.
893    *
894    * @param name The section name.  <jk>null</jk> and blank refer to the default section.
895    * @return The section bean, never <jk>null</jk>.
896    */
897   public Section getSection(String name) {
898      return new Section(this, configMap, emptyIfNull(name));
899   }
900
901   /**
902    * Returns the keys of the entries in the specified section.
903    *
904    * @param section
905    *    The section name to write from.
906    *    <br>If empty, refers to the default section.
907    *    <br>Must not be <jk>null</jk>.
908    * @return
909    *    An unmodifiable set of keys, or an empty set if the section doesn't exist.
910    */
911   public Set<String> getKeys(String section) {
912      return configMap.getKeys(section(section));
913   }
914
915   /**
916    * Returns the section names defined in this config.
917    *
918    * @return The section names defined in this config.
919    */
920   public Set<String> getSectionNames() {
921      return unmodifiable(configMap.getSections());
922   }
923
924   /**
925    * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value.
926    *
927    * @param key The key.
928    * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value.
929    */
930   public boolean exists(String key) {
931      return isNotEmpty(get(key).as(String.class).orElse(null));
932   }
933
934   /**
935    * Creates the specified section if it doesn't exist.
936    *
937    * <p>
938    * Returns the existing section if it already exists.
939    *
940    * @param name
941    *    The section name.
942    *    <br>Must not be <jk>null</jk>.
943    *    <br>Use blank for the default section.
944    * @param preLines
945    *    Optional comment and blank lines to add immediately before the section.
946    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
947    * @return The appended or existing section.
948    * @throws UnsupportedOperationException If configuration is read only.
949    */
950   public Config setSection(String name, List<String> preLines) {
951      try {
952         return setSection(section(name), preLines, null);
953      } catch (SerializeException e) {
954         throw asRuntimeException(e);  // Impossible.
955      }
956   }
957
958   /**
959    * Creates the specified section if it doesn't exist.
960    *
961    * @param name
962    *    The section name.
963    *    <br>Must not be <jk>null</jk>.
964    *    <br>Use blank for the default section.
965    * @param preLines
966    *    Optional comment and blank lines to add immediately before the section.
967    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
968    * @param contents
969    *    Values to set in the new section.
970    *    <br>Can be <jk>null</jk>.
971    * @return The appended or existing section.
972    * @throws SerializeException Contents could not be serialized.
973    * @throws UnsupportedOperationException If configuration is read only.
974    */
975   public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException {
976      checkWrite();
977      configMap.setSection(section(name), preLines);
978
979      if (contents != null)
980         for (Map.Entry<String,Object> e : contents.entrySet())
981            set(section(name) + '/' + e.getKey(), e.getValue());
982
983      return this;
984   }
985
986   /**
987    * Removes the section with the specified name.
988    *
989    * @param name The name of the section to remove
990    * @return This object.
991    * @throws UnsupportedOperationException If configuration is read only.
992    */
993   public Config removeSection(String name) {
994      checkWrite();
995      configMap.removeSection(name);
996      return this;
997   }
998
999   /**
1000    * Creates the specified import statement if it doesn't exist.
1001    *
1002    * @param sectionName
1003    *    The section name where to place the import statement.
1004    *    <br>Must not be <jk>null</jk>.
1005    *    <br>Use blank for the default section.
1006    * @param importName
1007    *    The import name.
1008    *    <br>Must not be <jk>null</jk>.
1009    * @param preLines
1010    *    Optional comment and blank lines to add immediately before the import statement.
1011    *    <br>If <jk>null</jk>, previous pre-lines will not be replaced.
1012    * @return The appended or existing import statement.
1013    * @throws UnsupportedOperationException If configuration is read only.
1014    */
1015   public Config setImport(String sectionName, String importName, List<String> preLines) {
1016      checkWrite();
1017      configMap.setImport(section(name), importName, preLines);
1018      return this;
1019   }
1020
1021   /**
1022    * Removes the import statement with the specified name from the specified section.
1023    *
1024    * @param sectionName
1025    *    The section name where to place the import statement.
1026    *    <br>Must not be <jk>null</jk>.
1027    *    <br>Use blank for the default section.
1028    * @param importName
1029    *    The import name.
1030    *    <br>Must not be <jk>null</jk>.
1031    * @return This object.
1032    * @throws UnsupportedOperationException If configuration is read only.
1033    */
1034   public Config removeImport(String sectionName, String importName) {
1035      checkWrite();
1036      configMap.removeImport(sectionName, importName);
1037      return this;
1038   }
1039
1040   /**
1041    * Loads the contents of the specified map of maps into this config.
1042    *
1043    * @param m The maps to load.
1044    * @return This object.
1045    * @throws SerializeException Value could not be serialized.
1046    */
1047   public Config load(Map<String,Map<String,Object>> m) throws SerializeException {
1048      if (m != null)
1049         for (Map.Entry<String,Map<String,Object>> e : m.entrySet()) {
1050            setSection(e.getKey(), null, e.getValue());
1051         }
1052      return this;
1053   }
1054
1055   /**
1056    * Commit the changes in this config to the store.
1057    *
1058    * @return This object.
1059    * @throws IOException Thrown by underlying stream.
1060    * @throws UnsupportedOperationException If configuration is read only.
1061    */
1062   public Config commit() throws IOException {
1063      checkWrite();
1064      configMap.commit();
1065      return this;
1066   }
1067
1068   /**
1069    * Saves this config file to the specified writer as an INI file.
1070    *
1071    * <p>
1072    * The writer will automatically be closed.
1073    *
1074    * @param w The writer to send the output to.
1075    * @return This object.
1076    * @throws IOException If a problem occurred trying to send contents to the writer.
1077    */
1078   public Writer writeTo(Writer w) throws IOException {
1079      return configMap.writeTo(w);
1080   }
1081
1082   /**
1083    * Add a listener to this config to react to modification events.
1084    *
1085    * <p>
1086    * Listeners should be removed using {@link #removeListener(ConfigEventListener)}.
1087    *
1088    * @param listener The new listener to add.
1089    * @return This object.
1090    */
1091   public synchronized Config addListener(ConfigEventListener listener) {
1092      listeners.add(listener);
1093      return this;
1094   }
1095
1096   /**
1097    * Removes a listener from this config.
1098    *
1099    * @param listener The listener to remove.
1100    * @return This object.
1101    */
1102   public synchronized Config removeListener(ConfigEventListener listener) {
1103      listeners.remove(listener);
1104      return this;
1105   }
1106
1107   /**
1108    * Closes this configuration object by unregistering it from the underlying config map.
1109    *
1110    * @throws IOException Thrown by underlying stream.
1111    */
1112   public void close() throws IOException {
1113      configMap.unregister(this);
1114   }
1115
1116   /**
1117    * Overwrites the contents of the config file.
1118    *
1119    * @param contents The new contents of the config file.
1120    * @param synchronous Wait until the change has been persisted before returning this map.
1121    * @return This object.
1122    * @throws IOException Thrown by underlying stream.
1123    * @throws InterruptedException Thread was interrupted.
1124    * @throws UnsupportedOperationException If configuration is read only.
1125    */
1126   public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException {
1127      checkWrite();
1128      configMap.load(read(contents), synchronous);
1129      return this;
1130   }
1131
1132   /**
1133    * Overwrites the contents of the config file.
1134    *
1135    * @param contents The new contents of the config file.
1136    * @param synchronous Wait until the change has been persisted before returning this map.
1137    * @return This object.
1138    * @throws IOException Thrown by underlying stream.
1139    * @throws InterruptedException Thread was interrupted.
1140    * @throws UnsupportedOperationException If configuration is read only.
1141    */
1142   public Config load(String contents, boolean synchronous) throws IOException, InterruptedException {
1143      checkWrite();
1144      configMap.load(contents, synchronous);
1145      return this;
1146   }
1147
1148   /**
1149    * Does a rollback of any changes on this config currently in memory.
1150    *
1151    * @return This object.
1152    * @throws UnsupportedOperationException If configuration is read only.
1153    */
1154   public Config rollback() {
1155      checkWrite();
1156      configMap.rollback();
1157      return this;
1158   }
1159
1160   /**
1161    * Returns the contents of this config as a simple map.
1162    *
1163    * @return The contents of this config as a simple map.
1164    */
1165   public JsonMap toMap() {
1166      return configMap.asMap();
1167   }
1168
1169   //-----------------------------------------------------------------------------------------------------------------
1170   // Test methods
1171   //-----------------------------------------------------------------------------------------------------------------
1172
1173   ConfigMap getConfigMap() {
1174      return configMap;
1175   }
1176
1177   List<ConfigEventListener> getListeners() {
1178      return unmodifiable(listeners);
1179   }
1180
1181
1182   //-----------------------------------------------------------------------------------------------------------------
1183   // Interface methods
1184   //-----------------------------------------------------------------------------------------------------------------
1185
1186   @Override /* ConfigEventListener */
1187   public synchronized void onConfigChange(ConfigEvents events) {
1188      for (ConfigEventListener l : listeners)
1189         l.onConfigChange(events);
1190   }
1191
1192   //-----------------------------------------------------------------------------------------------------------------
1193   // Private methods
1194   //-----------------------------------------------------------------------------------------------------------------
1195
1196   private String serialize(Object value, Serializer serializer) throws SerializeException {
1197      if (value == null)
1198         return "";
1199      if (serializer == null)
1200         serializer = this.serializer;
1201      Class<?> c = value.getClass();
1202      if (value instanceof CharSequence)
1203         return nlIfMl((CharSequence)value);
1204      if (isSimpleType(c))
1205         return value.toString();
1206
1207      if (value instanceof byte[]) {
1208         String s = null;
1209         byte[] b = (byte[])value;
1210         if (binaryFormat == BinaryFormat.HEX)
1211            s = toHex(b);
1212         else if (binaryFormat == BinaryFormat.SPACED_HEX)
1213            s = toSpacedHex(b);
1214         else
1215            s = base64Encode(b);
1216         int l = binaryLineLength;
1217         if (l <= 0 || s.length() <= l)
1218            return s;
1219         StringBuilder sb = new StringBuilder();
1220         for (int i = 0; i < s.length(); i += l)
1221            sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l)));
1222         return sb.toString();
1223      }
1224
1225      String r = null;
1226      if (multiLineValuesOnSeparateLines)
1227         r = "\n" + (String)serializer.serialize(value);
1228      else
1229         r = (String)serializer.serialize(value);
1230
1231      if (r.startsWith("'"))
1232         return r.substring(1, r.length()-1);
1233      return r;
1234   }
1235
1236   private String nlIfMl(CharSequence cs) {
1237      String s = cs.toString();
1238      if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines)
1239         return "\n" + s;
1240      return s;
1241   }
1242
1243   private boolean isSimpleType(Type t) {
1244      if (! (t instanceof Class))
1245         return false;
1246      Class<?> c = (Class<?>)t;
1247      return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum());
1248   }
1249
1250   private String sname(String key) {
1251      assertArgNotNull("key", key);
1252      int i = key.indexOf('/');
1253      if (i == -1)
1254         return "";
1255      return key.substring(0, i);
1256   }
1257
1258   private String skey(String key) {
1259      int i = key.indexOf('/');
1260      if (i == -1)
1261         return key;
1262      return key.substring(i+1);
1263   }
1264
1265   private String section(String section) {
1266      assertArgNotNull("section", section);
1267      if (isEmpty(section))
1268         return "";
1269      return section;
1270   }
1271
1272   void checkWrite() {
1273      if (readOnly)
1274         throw new UnsupportedOperationException("Cannot call this method on a read-only configuration.");
1275   }
1276
1277
1278   //-----------------------------------------------------------------------------------------------------------------
1279   // Other methods
1280   //-----------------------------------------------------------------------------------------------------------------
1281
1282   @Override /* Object */
1283   public String toString() {
1284      return configMap.toString();
1285   }
1286
1287   @Override /* Object */
1288   protected void finalize() throws Throwable {
1289      close();
1290   }
1291}