001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import java.io.FileNotFoundException;
021import java.io.FilterWriter;
022import java.io.IOException;
023import java.io.LineNumberReader;
024import java.io.Reader;
025import java.io.Writer;
026import java.net.URL;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Deque;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.regex.Matcher;
035import java.util.regex.Pattern;
036
037import org.apache.commons.configuration2.convert.ListDelimiterHandler;
038import org.apache.commons.configuration2.convert.ValueTransformer;
039import org.apache.commons.configuration2.event.ConfigurationEvent;
040import org.apache.commons.configuration2.ex.ConfigurationException;
041import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
042import org.apache.commons.configuration2.io.FileHandler;
043import org.apache.commons.configuration2.io.FileLocator;
044import org.apache.commons.configuration2.io.FileLocatorAware;
045import org.apache.commons.configuration2.io.FileLocatorUtils;
046import org.apache.commons.lang3.ArrayUtils;
047import org.apache.commons.lang3.StringUtils;
048import org.apache.commons.text.StringEscapeUtils;
049import org.apache.commons.text.translate.AggregateTranslator;
050import org.apache.commons.text.translate.CharSequenceTranslator;
051import org.apache.commons.text.translate.EntityArrays;
052import org.apache.commons.text.translate.LookupTranslator;
053import org.apache.commons.text.translate.UnicodeEscaper;
054
055/**
056 * This is the "classic" Properties loader which loads the values from a single or multiple files (which can be chained
057 * with "include =". All given path references are either absolute or relative to the file name supplied in the
058 * constructor.
059 * <p>
060 * In this class, empty PropertyConfigurations can be built, properties added and later saved. include statements are
061 * (obviously) not supported if you don't construct a PropertyConfiguration from a file.
062 *
063 * <p>
064 * The properties file syntax is explained here, basically it follows the syntax of the stream parsed by
065 * {@link java.util.Properties#load} and adds several useful extensions:
066 *
067 * <ul>
068 * <li>Each property has the syntax {@code key &lt;separator&gt; value}. The separators accepted are {@code '='},
069 * {@code ':'} and any white space character. Examples:
070 *
071 * <pre>
072 *  key1 = value1
073 *  key2 : value2
074 *  key3   value3
075 * </pre>
076 *
077 * </li>
078 * <li>The <i>key</i> may use any character, separators must be escaped:
079 *
080 * <pre>
081 *  key\:foo = bar
082 * </pre>
083 *
084 * </li>
085 * <li><i>value</i> may be separated on different lines if a backslash is placed at the end of the line that continues
086 * below.</li>
087 * <li>The list delimiter facilities provided by {@link AbstractConfiguration} are supported, too. If an appropriate
088 * {@link ListDelimiterHandler} is set (for instance a
089 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler D efaultListDelimiterHandler} object
090 * configured with a comma as delimiter character), <i>value</i> can contain <em>value delimiters</em> and will then be
091 * interpreted as a list of tokens. So the following property definition
092 *
093 * <pre>
094 *  key = This property, has multiple, values
095 * </pre>
096 *
097 * will result in a property with three values. You can change the handling of delimiters using the
098 * {@link AbstractConfiguration#setListDelimiterHandler(ListDelimiterHandler)} method. Per default, list splitting is
099 * disabled.</li>
100 * <li>Commas in each token are escaped placing a backslash right before the comma.</li>
101 * <li>If a <i>key</i> is used more than once, the values are appended like if they were on the same line separated with
102 * commas. <em>Note</em>: When the configuration file is written back to disk the associated
103 * {@link PropertiesConfigurationLayout} object (see below) will try to preserve as much of the original format as
104 * possible, i.e. properties with multiple values defined on a single line will also be written back on a single line,
105 * and multiple occurrences of a single key will be written on multiple lines. If the {@code addProperty()} method was
106 * called multiple times for adding multiple values to a property, these properties will per default be written on
107 * multiple lines in the output file, too. Some options of the {@code PropertiesConfigurationLayout} class have
108 * influence on that behavior.</li>
109 * <li>Blank lines and lines starting with character '#' or '!' are skipped.</li>
110 * <li>If a property is named "include" (or whatever is defined by setInclude() and getInclude() and the value of that
111 * property is the full path to a file on disk, that file will be included into the configuration. You can also pull in
112 * files relative to the parent configuration file. So if you have something like the following:
113 *
114 * include = additional.properties
115 *
116 * Then "additional.properties" is expected to be in the same directory as the parent configuration file.
117 *
118 * The properties in the included file are added to the parent configuration, they do not replace existing properties
119 * with the same key.
120 *
121 * </li>
122 * <li>You can define custom error handling for the special key {@code "include"} by using
123 * {@link #setIncludeListener(ConfigurationConsumer)}.</li>
124 * </ul>
125 *
126 * <p>
127 * Here is an example of a valid extended properties file:
128 * </p>
129 *
130 * <pre>
131 *      # lines starting with # are comments
132 *
133 *      # This is the simplest property
134 *      key = value
135 *
136 *      # A long property may be separated on multiple lines
137 *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
138 *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
139 *
140 *      # This is a property with many tokens
141 *      tokens_on_a_line = first token, second token
142 *
143 *      # This sequence generates exactly the same result
144 *      tokens_on_multiple_lines = first token
145 *      tokens_on_multiple_lines = second token
146 *
147 *      # commas may be escaped in tokens
148 *      commas.escaped = Hi\, what'up?
149 *
150 *      # properties can reference other properties
151 *      base.prop = /base
152 *      first.prop = ${base.prop}/first
153 *      second.prop = ${first.prop}/second
154 * </pre>
155 *
156 * <p>
157 * A {@code PropertiesConfiguration} object is associated with an instance of the {@link PropertiesConfigurationLayout}
158 * class, which is responsible for storing the layout of the parsed properties file (i.e. empty lines, comments, and
159 * such things). The {@code getLayout()} method can be used to obtain this layout object. With {@code setLayout()} a new
160 * layout object can be set. This should be done before a properties file was loaded.
161 * <p>
162 * Like other {@code Configuration} implementations, this class uses a {@code Synchronizer} object to control concurrent
163 * access. By choosing a suitable implementation of the {@code Synchronizer} interface, an instance can be made
164 * thread-safe or not. Note that access to most of the properties typically set through a builder is not protected by
165 * the {@code Synchronizer}. The intended usage is that these properties are set once at construction time through the
166 * builder and after that remain constant. If you wish to change such properties during life time of an instance, you
167 * have to use the {@code lock()} and {@code unlock()} methods manually to ensure that other threads see your changes.
168 * <p>
169 * As this class extends {@link AbstractConfiguration}, all basic features like variable interpolation, list handling,
170 * or data type conversions are available as well. This is described in the chapter
171 * <a href="https://commons.apache.org/proper/commons-configuration/userguide/howto_basicfeatures.html"> Basic features
172 * and AbstractConfiguration</a> of the user's guide. There is also a separate chapter dealing with
173 * <a href="commons.apache.org/proper/commons-configuration/userguide/howto_properties.html"> Properties files</a> in
174 * special.
175 *
176 * @see java.util.Properties#load
177 */
178public class PropertiesConfiguration extends BaseConfiguration implements FileBasedConfiguration, FileLocatorAware {
179
180    /**
181     * <p>
182     * A default implementation of the {@code IOFactory} interface.
183     * </p>
184     * <p>
185     * This class implements the {@code createXXXX()} methods defined by the {@code IOFactory} interface in a way that the
186     * default objects (i.e. {@code PropertiesReader} and {@code PropertiesWriter} are returned. Customizing either the
187     * reader or the writer (or both) can be done by extending this class and overriding the corresponding
188     * {@code createXXXX()} method.
189     * </p>
190     *
191     * @since 1.7
192     */
193    public static class DefaultIOFactory implements IOFactory {
194        /**
195         * The singleton instance.
196         */
197        static final DefaultIOFactory INSTANCE = new DefaultIOFactory();
198
199        @Override
200        public PropertiesReader createPropertiesReader(final Reader in) {
201            return new PropertiesReader(in);
202        }
203
204        @Override
205        public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler) {
206            return new PropertiesWriter(out, handler);
207        }
208    }
209
210    /**
211     * <p>
212     * Definition of an interface that allows customization of read and write operations.
213     * </p>
214     * <p>
215     * For reading and writing properties files the inner classes {@code PropertiesReader} and {@code PropertiesWriter} are
216     * used. This interface defines factory methods for creating both a {@code PropertiesReader} and a
217     * {@code PropertiesWriter}. An object implementing this interface can be passed to the {@code setIOFactory()} method of
218     * {@code PropertiesConfiguration}. Every time the configuration is read or written the {@code IOFactory} is asked to
219     * create the appropriate reader or writer object. This provides an opportunity to inject custom reader or writer
220     * implementations.
221     * </p>
222     *
223     * @since 1.7
224     */
225    public interface IOFactory {
226        /**
227         * Creates a {@code PropertiesReader} for reading a properties file. This method is called whenever the
228         * {@code PropertiesConfiguration} is loaded. The reader returned by this method is then used for parsing the properties
229         * file.
230         *
231         * @param in the underlying reader (of the properties file)
232         * @return the {@code PropertiesReader} for loading the configuration
233         */
234        PropertiesReader createPropertiesReader(Reader in);
235
236        /**
237         * Creates a {@code PropertiesWriter} for writing a properties file. This method is called before the
238         * {@code PropertiesConfiguration} is saved. The writer returned by this method is then used for writing the properties
239         * file.
240         *
241         * @param out the underlying writer (to the properties file)
242         * @param handler the list delimiter delimiter for list parsing
243         * @return the {@code PropertiesWriter} for saving the configuration
244         */
245        PropertiesWriter createPropertiesWriter(Writer out, ListDelimiterHandler handler);
246    }
247
248    /**
249     * An alternative {@link IOFactory} that tries to mimic the behavior of {@link java.util.Properties} (Jup) more closely.
250     * The goal is to allow both of them be used interchangeably when reading and writing properties files without losing or
251     * changing information.
252     * <p>
253     * It also has the option to <em>not</em> use Unicode escapes. When using UTF-8 encoding (which is e.g. the new default
254     * for resource bundle properties files since Java 9), Unicode escapes are no longer required and avoiding them makes
255     * properties files more readable with regular text editors.
256     * <p>
257     * Some of the ways this implementation differs from {@link DefaultIOFactory}:
258     * <ul>
259     * <li>Trailing whitespace will not be trimmed from each line.</li>
260     * <li>Unknown escape sequences will have their backslash removed.</li>
261     * <li>{@code \b} is not a recognized escape sequence.</li>
262     * <li>Leading spaces in property values are preserved by escaping them.</li>
263     * <li>All natural lines (i.e. in the file) of a logical property line will have their leading whitespace trimmed.</li>
264     * <li>Natural lines that look like comment lines within a logical line are not treated as such; they're part of the
265     * property value.</li>
266     * </ul>
267     *
268     * @since 2.4
269     */
270    public static class JupIOFactory implements IOFactory {
271
272        /**
273         * Whether characters less than {@code \u0020} and characters greater than {@code \u007E} in property keys or values
274         * should be escaped using Unicode escape sequences. Not necessary when e.g. writing as UTF-8.
275         */
276        private final boolean escapeUnicode;
277
278        /**
279         * Constructs a new {@link JupIOFactory} with Unicode escaping.
280         */
281        public JupIOFactory() {
282            this(true);
283        }
284
285        /**
286         * Constructs a new {@link JupIOFactory} with optional Unicode escaping. Whether Unicode escaping is required depends on
287         * the encoding used to save the properties file. E.g. for ISO-8859-1 this must be turned on, for UTF-8 it's not
288         * necessary. Unfortunately this factory can't determine the encoding on its own.
289         *
290         * @param escapeUnicode whether Unicode characters should be escaped
291         */
292        public JupIOFactory(final boolean escapeUnicode) {
293            this.escapeUnicode = escapeUnicode;
294        }
295
296        @Override
297        public PropertiesReader createPropertiesReader(final Reader in) {
298            return new JupPropertiesReader(in);
299        }
300
301        @Override
302        public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler) {
303            return new JupPropertiesWriter(out, handler, escapeUnicode);
304        }
305
306    }
307
308    /**
309     * A {@link PropertiesReader} that tries to mimic the behavior of {@link java.util.Properties}.
310     *
311     * @since 2.4
312     */
313    public static class JupPropertiesReader extends PropertiesReader {
314
315        /**
316         * Constructor.
317         *
318         * @param reader A Reader.
319         */
320        public JupPropertiesReader(final Reader reader) {
321            super(reader);
322        }
323
324        @Override
325        protected void parseProperty(final String line) {
326            final String[] property = doParseProperty(line, false);
327            initPropertyName(property[0]);
328            initPropertyValue(property[1]);
329            initPropertySeparator(property[2]);
330        }
331
332        @Override
333        public String readProperty() throws IOException {
334            getCommentLines().clear();
335            final StringBuilder buffer = new StringBuilder();
336
337            while (true) {
338                String line = readLine();
339                if (line == null) {
340                    // EOF
341                    if (buffer.length() > 0) {
342                        break;
343                    }
344                    return null;
345                }
346
347                // while a property line continues there are no comments (even if the line from
348                // the file looks like one)
349                if (isCommentLine(line) && buffer.length() == 0) {
350                    getCommentLines().add(line);
351                    continue;
352                }
353
354                // while property line continues left trim all following lines read from the
355                // file
356                if (buffer.length() > 0) {
357                    // index of the first non-whitespace character
358                    int i;
359                    for (i = 0; i < line.length(); i++) {
360                        if (!Character.isWhitespace(line.charAt(i))) {
361                            break;
362                        }
363                    }
364
365                    line = line.substring(i);
366                }
367
368                if (!checkCombineLines(line)) {
369                    buffer.append(line);
370                    break;
371                }
372                line = line.substring(0, line.length() - 1);
373                buffer.append(line);
374            }
375            return buffer.toString();
376        }
377
378        @Override
379        protected String unescapePropertyValue(final String value) {
380            return unescapeJava(value, true);
381        }
382
383    }
384
385    /**
386     * A {@link PropertiesWriter} that tries to mimic the behavior of {@link java.util.Properties}.
387     *
388     * @since 2.4
389     */
390    public static class JupPropertiesWriter extends PropertiesWriter {
391
392        /**
393         * The starting ASCII printable character.
394         */
395        private static final int PRINTABLE_INDEX_END = 0x7e;
396
397        /**
398         * The ending ASCII printable character.
399         */
400        private static final int PRINTABLE_INDEX_START = 0x20;
401
402        /**
403         * A UnicodeEscaper for characters outside the ASCII printable range.
404         */
405        private static final UnicodeEscaper ESCAPER = UnicodeEscaper.outsideOf(PRINTABLE_INDEX_START, PRINTABLE_INDEX_END);
406
407        /**
408         * Characters that need to be escaped when wring a properties file.
409         */
410        private static final Map<CharSequence, CharSequence> JUP_CHARS_ESCAPE;
411        static {
412            final Map<CharSequence, CharSequence> initialMap = new HashMap<>();
413            initialMap.put("\\", "\\\\");
414            initialMap.put("\n", "\\n");
415            initialMap.put("\t", "\\t");
416            initialMap.put("\f", "\\f");
417            initialMap.put("\r", "\\r");
418            JUP_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap);
419        }
420
421        /**
422         * Creates a new instance of {@code JupPropertiesWriter}.
423         *
424         * @param writer a Writer object providing the underlying stream
425         * @param delHandler the delimiter handler for dealing with properties with multiple values
426         * @param escapeUnicode whether Unicode characters should be escaped using Unicode escapes
427         */
428        public JupPropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler, final boolean escapeUnicode) {
429            super(writer, delHandler, value -> {
430                String valueString = String.valueOf(value);
431
432                final CharSequenceTranslator translator;
433                if (escapeUnicode) {
434                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE), ESCAPER);
435                } else {
436                    translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE));
437                }
438
439                valueString = translator.translate(valueString);
440
441                // escape the first leading space to preserve it (and all after it)
442                if (valueString.startsWith(" ")) {
443                    valueString = "\\" + valueString;
444                }
445
446                return valueString;
447            });
448        }
449
450    }
451
452    /**
453     * This class is used to read properties lines. These lines do not terminate with new-line chars but rather when there
454     * is no backslash sign a the end of the line. This is used to concatenate multiple lines for readability.
455     */
456    public static class PropertiesReader extends LineNumberReader {
457
458        /** The regular expression to parse the key and the value of a property. */
459        private static final Pattern PROPERTY_PATTERN = Pattern
460            .compile("(([\\S&&[^\\\\" + new String(SEPARATORS) + "]]|\\\\.)*)(\\s*(\\s+|[" + new String(SEPARATORS) + "])\\s*)?(.*)");
461
462        /** Constant for the index of the group for the key. */
463        private static final int IDX_KEY = 1;
464
465        /** Constant for the index of the group for the value. */
466        private static final int IDX_VALUE = 5;
467
468        /** Constant for the index of the group for the separator. */
469        private static final int IDX_SEPARATOR = 3;
470
471        /** Stores the comment lines for the currently processed property. */
472        private final List<String> commentLines;
473
474        /** Stores the name of the last read property. */
475        private String propertyName;
476
477        /** Stores the value of the last read property. */
478        private String propertyValue;
479
480        /** Stores the property separator of the last read property. */
481        private String propertySeparator = DEFAULT_SEPARATOR;
482
483        /**
484         * Constructor.
485         *
486         * @param reader A Reader.
487         */
488        public PropertiesReader(final Reader reader) {
489            super(reader);
490            commentLines = new ArrayList<>();
491        }
492
493        /**
494         * Checks if the passed in line should be combined with the following. This is true, if the line ends with an odd number
495         * of backslashes.
496         *
497         * @param line the line
498         * @return a flag if the lines should be combined
499         */
500        static boolean checkCombineLines(final String line) {
501            return countTrailingBS(line) % 2 != 0;
502        }
503
504        /**
505         * Parse a property line and return the key, the value, and the separator in an array.
506         *
507         * @param line the line to parse
508         * @param trimValue flag whether the value is to be trimmed
509         * @return an array with the property's key, value, and separator
510         */
511        static String[] doParseProperty(final String line, final boolean trimValue) {
512            final Matcher matcher = PROPERTY_PATTERN.matcher(line);
513
514            final String[] result = {"", "", ""};
515
516            if (matcher.matches()) {
517                result[0] = matcher.group(IDX_KEY).trim();
518
519                String value = matcher.group(IDX_VALUE);
520                if (trimValue) {
521                    value = value.trim();
522                }
523                result[1] = value;
524
525                result[2] = matcher.group(IDX_SEPARATOR);
526            }
527
528            return result;
529        }
530
531        /**
532         * Gets the comment lines that have been read for the last property.
533         *
534         * @return the comment lines for the last property returned by {@code readProperty()}
535         * @since 1.3
536         */
537        public List<String> getCommentLines() {
538            return commentLines;
539        }
540
541        /**
542         * Gets the name of the last read property. This method can be called after {@link #nextProperty()} was invoked and
543         * its return value was <b>true</b>.
544         *
545         * @return the name of the last read property
546         * @since 1.3
547         */
548        public String getPropertyName() {
549            return propertyName;
550        }
551
552        /**
553         * Gets the separator that was used for the last read property. The separator can be stored so that it can later be
554         * restored when saving the configuration.
555         *
556         * @return the separator for the last read property
557         * @since 1.7
558         */
559        public String getPropertySeparator() {
560            return propertySeparator;
561        }
562
563        /**
564         * Gets the value of the last read property. This method can be called after {@link #nextProperty()} was invoked and
565         * its return value was <b>true</b>.
566         *
567         * @return the value of the last read property
568         * @since 1.3
569         */
570        public String getPropertyValue() {
571            return propertyValue;
572        }
573
574        /**
575         * Sets the name of the current property. This method can be called by {@code parseProperty()} for storing the results
576         * of the parse operation. It also ensures that the property key is correctly escaped.
577         *
578         * @param name the name of the current property
579         * @since 1.7
580         */
581        protected void initPropertyName(final String name) {
582            propertyName = unescapePropertyName(name);
583        }
584
585        /**
586         * Sets the separator of the current property. This method can be called by {@code parseProperty()}. It allows the
587         * associated layout object to keep track of the property separators. When saving the configuration the separators can
588         * be restored.
589         *
590         * @param value the separator used for the current property
591         * @since 1.7
592         */
593        protected void initPropertySeparator(final String value) {
594            propertySeparator = value;
595        }
596
597        /**
598         * Sets the value of the current property. This method can be called by {@code parseProperty()} for storing the results
599         * of the parse operation. It also ensures that the property value is correctly escaped.
600         *
601         * @param value the value of the current property
602         * @since 1.7
603         */
604        protected void initPropertyValue(final String value) {
605            propertyValue = unescapePropertyValue(value);
606        }
607
608        /**
609         * Parses the next property from the input stream and stores the found name and value in internal fields. These fields
610         * can be obtained using the provided getter methods. The return value indicates whether EOF was reached (<b>false</b>)
611         * or whether further properties are available (<b>true</b>).
612         *
613         * @return a flag if further properties are available
614         * @throws IOException if an error occurs
615         * @since 1.3
616         */
617        public boolean nextProperty() throws IOException {
618            final String line = readProperty();
619
620            if (line == null) {
621                return false; // EOF
622            }
623
624            // parse the line
625            parseProperty(line);
626            return true;
627        }
628
629        /**
630         * Parses a line read from the properties file. This method is called for each non-comment line read from the source
631         * file. Its task is to split the passed in line into the property key and its value. The results of the parse operation
632         * can be stored by calling the {@code initPropertyXXX()} methods.
633         *
634         * @param line the line read from the properties file
635         * @since 1.7
636         */
637        protected void parseProperty(final String line) {
638            final String[] property = doParseProperty(line, true);
639            initPropertyName(property[0]);
640            initPropertyValue(property[1]);
641            initPropertySeparator(property[2]);
642        }
643
644        /**
645         * Reads a property line. Returns null if Stream is at EOF. Concatenates lines ending with "\". Skips lines beginning
646         * with "#" or "!" and empty lines. The return value is a property definition ({@code &lt;name&gt;} =
647         * {@code &lt;value&gt;})
648         *
649         * @return A string containing a property value or null
650         *
651         * @throws IOException in case of an I/O error
652         */
653        public String readProperty() throws IOException {
654            commentLines.clear();
655            final StringBuilder buffer = new StringBuilder();
656
657            while (true) {
658                String line = readLine();
659                if (line == null) {
660                    // EOF
661                    return null;
662                }
663
664                if (isCommentLine(line)) {
665                    commentLines.add(line);
666                    continue;
667                }
668
669                line = line.trim();
670
671                if (!checkCombineLines(line)) {
672                    buffer.append(line);
673                    break;
674                }
675                line = line.substring(0, line.length() - 1);
676                buffer.append(line);
677            }
678            return buffer.toString();
679        }
680
681        /**
682         * Performs unescaping on the given property name.
683         *
684         * @param name the property name
685         * @return the unescaped property name
686         * @since 2.4
687         */
688        protected String unescapePropertyName(final String name) {
689            return StringEscapeUtils.unescapeJava(name);
690        }
691
692        /**
693         * Performs unescaping on the given property value.
694         *
695         * @param value the property value
696         * @return the unescaped property value
697         * @since 2.4
698         */
699        protected String unescapePropertyValue(final String value) {
700            return unescapeJava(value);
701        }
702    } // class PropertiesReader
703
704    /**
705     * This class is used to write properties lines. The most important method is
706     * {@code writeProperty(String, Object, boolean)}, which is called during a save operation for each property found in
707     * the configuration.
708     */
709    public static class PropertiesWriter extends FilterWriter {
710
711        /**
712         * Properties escape map.
713         */
714        private static final Map<CharSequence, CharSequence> PROPERTIES_CHARS_ESCAPE;
715        static {
716            final Map<CharSequence, CharSequence> initialMap = new HashMap<>();
717            initialMap.put("\\", "\\\\");
718            PROPERTIES_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap);
719        }
720
721        /**
722         * A translator for escaping property values. This translator performs a subset of transformations done by the
723         * ESCAPE_JAVA translator from Commons Lang 3.
724         */
725        private static final CharSequenceTranslator ESCAPE_PROPERTIES = new AggregateTranslator(new LookupTranslator(PROPERTIES_CHARS_ESCAPE),
726            new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE), UnicodeEscaper.outsideOf(32, 0x7f));
727
728        /**
729         * A {@code ValueTransformer} implementation used to escape property values. This implementation applies the
730         * transformation defined by the {@link #ESCAPE_PROPERTIES} translator.
731         */
732        private static final ValueTransformer DEFAULT_TRANSFORMER = value -> {
733            final String strVal = String.valueOf(value);
734            return ESCAPE_PROPERTIES.translate(strVal);
735        };
736
737        /** The value transformer used for escaping property values. */
738        private final ValueTransformer valueTransformer;
739
740        /** The list delimiter handler. */
741        private final ListDelimiterHandler delimiterHandler;
742
743        /** The separator to be used for the current property. */
744        private String currentSeparator;
745
746        /** The global separator. If set, it overrides the current separator. */
747        private String globalSeparator;
748
749        /** The line separator. */
750        private String lineSeparator;
751
752        /**
753         * Creates a new instance of {@code PropertiesWriter}.
754         *
755         * @param writer a Writer object providing the underlying stream
756         * @param delHandler the delimiter handler for dealing with properties with multiple values
757         */
758        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler) {
759            this(writer, delHandler, DEFAULT_TRANSFORMER);
760        }
761
762        /**
763         * Creates a new instance of {@code PropertiesWriter}.
764         *
765         * @param writer a Writer object providing the underlying stream
766         * @param delHandler the delimiter handler for dealing with properties with multiple values
767         * @param valueTransformer the value transformer used to escape property values
768         */
769        public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler, final ValueTransformer valueTransformer) {
770            super(writer);
771            delimiterHandler = delHandler;
772            this.valueTransformer = valueTransformer;
773        }
774
775        /**
776         * Escapes the key of a property before it gets written to file. This method is called on saving a configuration for
777         * each property key. It ensures that separator characters contained in the key are escaped.
778         *
779         * @param key the key
780         * @return the escaped key
781         * @since 2.0
782         */
783        protected String escapeKey(final String key) {
784            final StringBuilder newkey = new StringBuilder();
785
786            for (int i = 0; i < key.length(); i++) {
787                final char c = key.charAt(i);
788
789                if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c) || c == '\\') {
790                    // escape the separator
791                    newkey.append('\\');
792                }
793                newkey.append(c);
794            }
795
796            return newkey.toString();
797        }
798
799        /**
800         * Returns the separator to be used for the given property. This method is called by {@code writeProperty()}. The string
801         * returned here is used as separator between the property key and its value. Per default the method checks whether a
802         * global separator is set. If this is the case, it is returned. Otherwise the separator returned by
803         * {@code getCurrentSeparator()} is used, which was set by the associated layout object. Derived classes may implement a
804         * different strategy for defining the separator.
805         *
806         * @param key the property key
807         * @param value the value
808         * @return the separator to be used
809         * @since 1.7
810         */
811        protected String fetchSeparator(final String key, final Object value) {
812            return getGlobalSeparator() != null ? getGlobalSeparator() : StringUtils.defaultString(getCurrentSeparator());
813        }
814
815        /**
816         * Gets the current property separator.
817         *
818         * @return the current property separator
819         * @since 1.7
820         */
821        public String getCurrentSeparator() {
822            return currentSeparator;
823        }
824
825        /**
826         * Gets the delimiter handler for properties with multiple values. This object is used to escape property values so
827         * that they can be read in correctly the next time they are loaded.
828         *
829         * @return the delimiter handler for properties with multiple values
830         * @since 2.0
831         */
832        public ListDelimiterHandler getDelimiterHandler() {
833            return delimiterHandler;
834        }
835
836        /**
837         * Gets the global property separator.
838         *
839         * @return the global property separator
840         * @since 1.7
841         */
842        public String getGlobalSeparator() {
843            return globalSeparator;
844        }
845
846        /**
847         * Gets the line separator.
848         *
849         * @return the line separator
850         * @since 1.7
851         */
852        public String getLineSeparator() {
853            return lineSeparator != null ? lineSeparator : LINE_SEPARATOR;
854        }
855
856        /**
857         * Sets the current property separator. This separator is used when writing the next property.
858         *
859         * @param currentSeparator the current property separator
860         * @since 1.7
861         */
862        public void setCurrentSeparator(final String currentSeparator) {
863            this.currentSeparator = currentSeparator;
864        }
865
866        /**
867         * Sets the global property separator. This separator corresponds to the {@code globalSeparator} property of
868         * {@link PropertiesConfigurationLayout}. It defines the separator to be used for all properties. If it is undefined,
869         * the current separator is used.
870         *
871         * @param globalSeparator the global property separator
872         * @since 1.7
873         */
874        public void setGlobalSeparator(final String globalSeparator) {
875            this.globalSeparator = globalSeparator;
876        }
877
878        /**
879         * Sets the line separator. Each line written by this writer is terminated with this separator. If not set, the
880         * platform-specific line separator is used.
881         *
882         * @param lineSeparator the line separator to be used
883         * @since 1.7
884         */
885        public void setLineSeparator(final String lineSeparator) {
886            this.lineSeparator = lineSeparator;
887        }
888
889        /**
890         * Write a comment.
891         *
892         * @param comment the comment to write
893         * @throws IOException if an I/O error occurs.
894         */
895        public void writeComment(final String comment) throws IOException {
896            writeln("# " + comment);
897        }
898
899        /**
900         * Helper method for writing a line with the platform specific line ending.
901         *
902         * @param s the content of the line (may be <b>null</b>)
903         * @throws IOException if an error occurs
904         * @since 1.3
905         */
906        public void writeln(final String s) throws IOException {
907            if (s != null) {
908                write(s);
909            }
910            write(getLineSeparator());
911        }
912
913        /**
914         * Write a property.
915         *
916         * @param key The key of the property
917         * @param values The array of values of the property
918         *
919         * @throws IOException if an I/O error occurs.
920         */
921        public void writeProperty(final String key, final List<?> values) throws IOException {
922            for (final Object value : values) {
923                writeProperty(key, value);
924            }
925        }
926
927        /**
928         * Write a property.
929         *
930         * @param key the key of the property
931         * @param value the value of the property
932         *
933         * @throws IOException if an I/O error occurs.
934         */
935        public void writeProperty(final String key, final Object value) throws IOException {
936            writeProperty(key, value, false);
937        }
938
939        /**
940         * Writes the given property and its value. If the value happens to be a list, the {@code forceSingleLine} flag is
941         * evaluated. If it is set, all values are written on a single line using the list delimiter as separator.
942         *
943         * @param key the property key
944         * @param value the property value
945         * @param forceSingleLine the &quot;force single line&quot; flag
946         * @throws IOException if an error occurs
947         * @since 1.3
948         */
949        public void writeProperty(final String key, final Object value, final boolean forceSingleLine) throws IOException {
950            String v;
951
952            if (value instanceof List) {
953                v = null;
954                final List<?> values = (List<?>) value;
955                if (forceSingleLine) {
956                    try {
957                        v = String.valueOf(getDelimiterHandler().escapeList(values, valueTransformer));
958                    } catch (final UnsupportedOperationException ignored) {
959                        // the handler may not support escaping lists,
960                        // then the list is written in multiple lines
961                    }
962                }
963                if (v == null) {
964                    writeProperty(key, values);
965                    return;
966                }
967            } else {
968                v = String.valueOf(getDelimiterHandler().escape(value, valueTransformer));
969            }
970
971            write(escapeKey(key));
972            write(fetchSeparator(key, value));
973            write(v);
974
975            writeln(null);
976        }
977    } // class PropertiesWriter
978
979    /**
980     * Defines default error handling for the special {@code "include"} key by throwing the given exception.
981     *
982     * @since 2.6
983     */
984    public static final ConfigurationConsumer<ConfigurationException> DEFAULT_INCLUDE_LISTENER = e -> {
985        throw e;
986    };
987
988    /**
989     * Defines error handling as a noop for the special {@code "include"} key.
990     *
991     * @since 2.6
992     */
993    public static final ConfigurationConsumer<ConfigurationException> NOOP_INCLUDE_LISTENER = e -> { /* noop */ };
994
995    /**
996     * The default encoding (ISO-8859-1 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
997     */
998    public static final String DEFAULT_ENCODING = "ISO-8859-1";
999
1000    /** Constant for the supported comment characters. */
1001    static final String COMMENT_CHARS = "#!";
1002
1003    /** Constant for the default properties separator. */
1004    static final String DEFAULT_SEPARATOR = " = ";
1005
1006    /**
1007     * A string with special characters that need to be unescaped when reading a properties file.
1008     * {@code java.util.Properties} escapes these characters when writing out a properties file.
1009     */
1010    private static final String UNESCAPE_CHARACTERS = ":#=!\\\'\"";
1011
1012    /**
1013     * This is the name of the property that can point to other properties file for including other properties files.
1014     */
1015    private static String include = "include";
1016
1017    /**
1018     * This is the name of the property that can point to other properties file for including other properties files.
1019     * <p>
1020     * If the file is absent, processing continues normally.
1021     * </p>
1022     */
1023    private static String includeOptional = "includeoptional";
1024
1025    /** The list of possible key/value separators */
1026    private static final char[] SEPARATORS = {'=', ':'};
1027
1028    /** The white space characters used as key/value separators. */
1029    private static final char[] WHITE_SPACE = {' ', '\t', '\f'};
1030
1031    /** Constant for the platform specific line separator. */
1032    private static final String LINE_SEPARATOR = System.lineSeparator();
1033
1034    /** Constant for the radix of hex numbers. */
1035    private static final int HEX_RADIX = 16;
1036
1037    /** Constant for the length of a unicode literal. */
1038    private static final int UNICODE_LEN = 4;
1039
1040    /** Stores the layout object. */
1041    private PropertiesConfigurationLayout layout;
1042
1043    /** The include listener for the special {@code "include"} key. */
1044    private ConfigurationConsumer<ConfigurationException> includeListener;
1045
1046    /** The IOFactory for creating readers and writers. */
1047    private IOFactory ioFactory;
1048
1049    /** The current {@code FileLocator}. */
1050    private FileLocator locator;
1051
1052    /** Allow file inclusion or not */
1053    private boolean includesAllowed = true;
1054
1055    /**
1056     * Creates an empty PropertyConfiguration object which can be used to synthesize a new Properties file by adding values
1057     * and then saving().
1058     */
1059    public PropertiesConfiguration() {
1060        installLayout(createLayout());
1061    }
1062
1063    /**
1064     * Returns the number of trailing backslashes. This is sometimes needed for the correct handling of escape characters.
1065     *
1066     * @param line the string to investigate
1067     * @return the number of trailing backslashes
1068     */
1069    private static int countTrailingBS(final String line) {
1070        int bsCount = 0;
1071        for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) {
1072            bsCount++;
1073        }
1074
1075        return bsCount;
1076    }
1077
1078    /**
1079     * Gets the property value for including other properties files. By default it is "include".
1080     *
1081     * @return A String.
1082     */
1083    public static String getInclude() {
1084        return PropertiesConfiguration.include;
1085    }
1086
1087    /**
1088     * Gets the property value for including other properties files. By default it is "includeoptional".
1089     * <p>
1090     * If the file is absent, processing continues normally.
1091     * </p>
1092     *
1093     * @return A String.
1094     * @since 2.5
1095     */
1096    public static String getIncludeOptional() {
1097        return PropertiesConfiguration.includeOptional;
1098    }
1099
1100    /**
1101     * Tests whether a line is a comment, i.e. whether it starts with a comment character.
1102     *
1103     * @param line the line
1104     * @return a flag if this is a comment line
1105     * @since 1.3
1106     */
1107    static boolean isCommentLine(final String line) {
1108        final String s = line.trim();
1109        // blank lines are also treated as comment lines
1110        return s.isEmpty() || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
1111    }
1112
1113    /**
1114     * Checks whether the specified character needs to be unescaped. This method is called when during reading a property
1115     * file an escape character ('\') is detected. If the character following the escape character is recognized as a
1116     * special character which is escaped per default in a Java properties file, it has to be unescaped.
1117     *
1118     * @param ch the character in question
1119     * @return a flag whether this character has to be unescaped
1120     */
1121    private static boolean needsUnescape(final char ch) {
1122        return UNESCAPE_CHARACTERS.indexOf(ch) >= 0;
1123    }
1124
1125    /**
1126     * Sets the property value for including other properties files. By default it is "include".
1127     *
1128     * @param inc A String.
1129     */
1130    public static void setInclude(final String inc) {
1131        PropertiesConfiguration.include = inc;
1132    }
1133
1134    /**
1135     * Sets the property value for including other properties files. By default it is "include".
1136     * <p>
1137     * If the file is absent, processing continues normally.
1138     * </p>
1139     *
1140     * @param inc A String.
1141     * @since 2.5
1142     */
1143    public static void setIncludeOptional(final String inc) {
1144        PropertiesConfiguration.includeOptional = inc;
1145    }
1146
1147    /**
1148     * <p>
1149     * Unescapes any Java literals found in the {@code String} to a {@code Writer}.
1150     * </p>
1151     * This is a slightly modified version of the StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
1152     * drop escaped separators (i.e '\,').
1153     *
1154     * @param str the {@code String} to unescape, may be null
1155     * @return the processed string
1156     * @throws IllegalArgumentException if the Writer is {@code null}
1157     */
1158    protected static String unescapeJava(final String str) {
1159        return unescapeJava(str, false);
1160    }
1161
1162    /**
1163     * Unescapes Java literals found in the {@code String} to a {@code Writer}.
1164     * <p>
1165     * When the parameter {@code jupCompatible} is {@code false}, the classic behavior is used (see
1166     * {@link #unescapeJava(String)}). When it's {@code true} a slightly different behavior that's compatible with
1167     * {@link java.util.Properties} is used (see {@link JupIOFactory}).
1168     * </p>
1169     *
1170     * @param str the {@code String} to unescape, may be null
1171     * @param jupCompatible whether unescaping is compatible with {@link java.util.Properties}; otherwise the classic
1172     *        behavior is used
1173     * @return the processed string
1174     * @throws IllegalArgumentException if the Writer is {@code null}
1175     */
1176    protected static String unescapeJava(final String str, final boolean jupCompatible) {
1177        if (str == null) {
1178            return null;
1179        }
1180        final int sz = str.length();
1181        final StringBuilder out = new StringBuilder(sz);
1182        final StringBuilder unicode = new StringBuilder(UNICODE_LEN);
1183        boolean hadSlash = false;
1184        boolean inUnicode = false;
1185        for (int i = 0; i < sz; i++) {
1186            final char ch = str.charAt(i);
1187            if (inUnicode) {
1188                // if in unicode, then we're reading unicode
1189                // values in somehow
1190                unicode.append(ch);
1191                if (unicode.length() == UNICODE_LEN) {
1192                    // unicode now contains the four hex digits
1193                    // which represents our unicode character
1194                    try {
1195                        final int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
1196                        out.append((char) value);
1197                        unicode.setLength(0);
1198                        inUnicode = false;
1199                        hadSlash = false;
1200                    } catch (final NumberFormatException nfe) {
1201                        throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);
1202                    }
1203                }
1204                continue;
1205            }
1206
1207            if (hadSlash) {
1208                // handle an escaped value
1209                hadSlash = false;
1210
1211                switch (ch) {
1212                case 'r':
1213                    out.append('\r');
1214                    break;
1215                case 'f':
1216                    out.append('\f');
1217                    break;
1218                case 't':
1219                    out.append('\t');
1220                    break;
1221                case 'n':
1222                    out.append('\n');
1223                    break;
1224                default:
1225                    if (!jupCompatible && ch == 'b') {
1226                        out.append('\b');
1227                    } else if (ch == 'u') {
1228                        // uh-oh, we're in unicode country....
1229                        inUnicode = true;
1230                    } else {
1231                        // JUP simply throws away the \ of unknown escape sequences
1232                        if (!needsUnescape(ch) && !jupCompatible) {
1233                            out.append('\\');
1234                        }
1235                        out.append(ch);
1236                    }
1237                    break;
1238                }
1239
1240                continue;
1241            }
1242            if (ch == '\\') {
1243                hadSlash = true;
1244                continue;
1245            }
1246            out.append(ch);
1247        }
1248
1249        if (hadSlash) {
1250            // then we're in the weird case of a \ at the end of the
1251            // string, let's output it anyway.
1252            out.append('\\');
1253        }
1254
1255        return out.toString();
1256    }
1257
1258    /**
1259     * Creates a copy of this object.
1260     *
1261     * @return the copy
1262     */
1263    @Override
1264    public Object clone() {
1265        final PropertiesConfiguration copy = (PropertiesConfiguration) super.clone();
1266        if (layout != null) {
1267            copy.setLayout(new PropertiesConfigurationLayout(layout));
1268        }
1269        return copy;
1270    }
1271
1272    /**
1273     * Creates a standard layout object. This configuration is initialized with such a standard layout.
1274     *
1275     * @return the newly created layout object
1276     */
1277    private PropertiesConfigurationLayout createLayout() {
1278        return new PropertiesConfigurationLayout();
1279    }
1280
1281    /**
1282     * Gets the footer comment. This is a comment at the very end of the file.
1283     *
1284     * @return the footer comment
1285     * @since 2.0
1286     */
1287    public String getFooter() {
1288        beginRead(false);
1289        try {
1290            return getLayout().getFooterComment();
1291        } finally {
1292            endRead();
1293        }
1294    }
1295
1296    /**
1297     * Return the comment header.
1298     *
1299     * @return the comment header
1300     * @since 1.1
1301     */
1302    public String getHeader() {
1303        beginRead(false);
1304        try {
1305            return getLayout().getHeaderComment();
1306        } finally {
1307            endRead();
1308        }
1309    }
1310
1311    /**
1312     * Gets the current include listener, never null.
1313     *
1314     * @return the current include listener, never null.
1315     * @since 2.6
1316     */
1317    public ConfigurationConsumer<ConfigurationException> getIncludeListener() {
1318        return includeListener != null ? includeListener : PropertiesConfiguration.DEFAULT_INCLUDE_LISTENER;
1319    }
1320
1321    /**
1322     * Gets the {@code IOFactory} to be used for creating readers and writers when loading or saving this configuration.
1323     *
1324     * @return the {@code IOFactory}
1325     * @since 1.7
1326     */
1327    public IOFactory getIOFactory() {
1328        return ioFactory != null ? ioFactory : DefaultIOFactory.INSTANCE;
1329    }
1330
1331    /**
1332     * Gets the associated layout object.
1333     *
1334     * @return the associated layout object
1335     * @since 1.3
1336     */
1337    public PropertiesConfigurationLayout getLayout() {
1338        return layout;
1339    }
1340
1341    /**
1342     * Stores the current {@code FileLocator} for a following IO operation. The {@code FileLocator} is needed to resolve
1343     * include files with relative file names.
1344     *
1345     * @param locator the current {@code FileLocator}
1346     * @since 2.0
1347     */
1348    @Override
1349    public void initFileLocator(final FileLocator locator) {
1350        this.locator = locator;
1351    }
1352
1353    /**
1354     * Installs a layout object. It has to be ensured that the layout is registered as change listener at this
1355     * configuration. If there is already a layout object installed, it has to be removed properly.
1356     *
1357     * @param layout the layout object to be installed
1358     */
1359    private void installLayout(final PropertiesConfigurationLayout layout) {
1360        // only one layout must exist
1361        if (this.layout != null) {
1362            removeEventListener(ConfigurationEvent.ANY, this.layout);
1363        }
1364
1365        if (layout == null) {
1366            this.layout = createLayout();
1367        } else {
1368            this.layout = layout;
1369        }
1370        addEventListener(ConfigurationEvent.ANY, this.layout);
1371    }
1372
1373    /**
1374     * Reports the status of file inclusion.
1375     *
1376     * @return True if include files are loaded.
1377     */
1378    public boolean isIncludesAllowed() {
1379        return this.includesAllowed;
1380    }
1381
1382    /**
1383     * Helper method for loading an included properties file. This method is called by {@code load()} when an
1384     * {@code include} property is encountered. It tries to resolve relative file names based on the current base path. If
1385     * this fails, a resolution based on the location of this properties file is tried.
1386     *
1387     * @param fileName the name of the file to load
1388     * @param optional whether or not the {@code fileName} is optional
1389     * @param seenStack Stack of seen include URLs
1390     * @throws ConfigurationException if loading fails
1391     */
1392    private void loadIncludeFile(final String fileName, final boolean optional, final Deque<URL> seenStack) throws ConfigurationException {
1393        if (locator == null) {
1394            throw new ConfigurationException(
1395                "Load operation not properly " + "initialized! Do not call read(InputStream) directly," + " but use a FileHandler to load a configuration.");
1396        }
1397
1398        URL url = locateIncludeFile(locator.getBasePath(), fileName);
1399        if (url == null) {
1400            final URL baseURL = locator.getSourceURL();
1401            if (baseURL != null) {
1402                url = locateIncludeFile(baseURL.toString(), fileName);
1403            }
1404        }
1405
1406        if (optional && url == null) {
1407            return;
1408        }
1409
1410        if (url == null) {
1411            getIncludeListener().accept(new ConfigurationException("Cannot resolve include file " + fileName, new FileNotFoundException(fileName)));
1412        } else {
1413            final FileHandler fh = new FileHandler(this);
1414            fh.setFileLocator(locator);
1415            final FileLocator orgLocator = locator;
1416            try {
1417                try {
1418                    // Check for cycles
1419                    if (seenStack.contains(url)) {
1420                        throw new ConfigurationException(String.format("Cycle detected loading %s, seen stack: %s", url, seenStack));
1421                    }
1422                    seenStack.add(url);
1423                    try {
1424                        fh.load(url);
1425                    } finally {
1426                        seenStack.pop();
1427                    }
1428                } catch (final ConfigurationException e) {
1429                    getIncludeListener().accept(e);
1430                }
1431            } finally {
1432                locator = orgLocator; // reset locator which is changed by load
1433            }
1434        }
1435    }
1436
1437    /**
1438     * Tries to obtain the URL of an include file using the specified (optional) base path and file name.
1439     *
1440     * @param basePath the base path
1441     * @param fileName the file name
1442     * @return the URL of the include file or <b>null</b> if it cannot be resolved
1443     */
1444    private URL locateIncludeFile(final String basePath, final String fileName) {
1445        final FileLocator includeLocator = FileLocatorUtils.fileLocator(locator).sourceURL(null).basePath(basePath).fileName(fileName).create();
1446        return FileLocatorUtils.locate(includeLocator);
1447    }
1448
1449    /**
1450     * This method is invoked by the associated {@link PropertiesConfigurationLayout} object for each property definition
1451     * detected in the parsed properties file. Its task is to check whether this is a special property definition (e.g. the
1452     * {@code include} property). If not, the property must be added to this configuration. The return value indicates
1453     * whether the property should be treated as a normal property. If it is <b>false</b>, the layout object will ignore
1454     * this property.
1455     *
1456     * @param key the property key
1457     * @param value the property value
1458     * @param seenStack the stack of seen include URLs
1459     * @return a flag whether this is a normal property
1460     * @throws ConfigurationException if an error occurs
1461     * @since 1.3
1462     */
1463    boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack) throws ConfigurationException {
1464        final boolean result;
1465
1466        if (StringUtils.isNotEmpty(getInclude()) && key.equalsIgnoreCase(getInclude())) {
1467            if (isIncludesAllowed()) {
1468                final Collection<String> files = getListDelimiterHandler().split(value, true);
1469                for (final String f : files) {
1470                    loadIncludeFile(interpolate(f), false, seenStack);
1471                }
1472            }
1473            result = false;
1474        } else if (StringUtils.isNotEmpty(getIncludeOptional()) && key.equalsIgnoreCase(getIncludeOptional())) {
1475            if (isIncludesAllowed()) {
1476                final Collection<String> files = getListDelimiterHandler().split(value, true);
1477                for (final String f : files) {
1478                    loadIncludeFile(interpolate(f), true, seenStack);
1479                }
1480            }
1481            result = false;
1482        } else {
1483            addPropertyInternal(key, value);
1484            result = true;
1485        }
1486
1487        return result;
1488    }
1489
1490    /**
1491     * {@inheritDoc} This implementation delegates to the associated layout object which does the actual loading. Note that
1492     * this method does not do any synchronization. This lies in the responsibility of the caller. (Typically, the caller is
1493     * a {@code FileHandler} object which takes care for proper synchronization.)
1494     *
1495     * @since 2.0
1496     */
1497    @Override
1498    public void read(final Reader in) throws ConfigurationException, IOException {
1499        getLayout().load(this, in);
1500    }
1501
1502    /**
1503     * Sets the footer comment. If set, this comment is written after all properties at the end of the file.
1504     *
1505     * @param footer the footer comment
1506     * @since 2.0
1507     */
1508    public void setFooter(final String footer) {
1509        beginWrite(false);
1510        try {
1511            getLayout().setFooterComment(footer);
1512        } finally {
1513            endWrite();
1514        }
1515    }
1516
1517    /**
1518     * Set the comment header.
1519     *
1520     * @param header the header to use
1521     * @since 1.1
1522     */
1523    public void setHeader(final String header) {
1524        beginWrite(false);
1525        try {
1526            getLayout().setHeaderComment(header);
1527        } finally {
1528            endWrite();
1529        }
1530    }
1531
1532    /**
1533     * Sets the current include listener, may not be null.
1534     *
1535     * @param includeListener the current include listener, may not be null.
1536     * @throws IllegalArgumentException if the {@code includeListener} is null.
1537     * @since 2.6
1538     */
1539    public void setIncludeListener(final ConfigurationConsumer<ConfigurationException> includeListener) {
1540        if (includeListener == null) {
1541            throw new IllegalArgumentException("includeListener must not be null.");
1542        }
1543        this.includeListener = includeListener;
1544    }
1545
1546    /**
1547     * Controls whether additional files can be loaded by the {@code include = <xxx>} statement or not. This is <b>true</b>
1548     * per default.
1549     *
1550     * @param includesAllowed True if Includes are allowed.
1551     */
1552    public void setIncludesAllowed(final boolean includesAllowed) {
1553        this.includesAllowed = includesAllowed;
1554    }
1555
1556    /**
1557     * Sets the {@code IOFactory} to be used for creating readers and writers when loading or saving this configuration.
1558     * Using this method a client can customize the reader and writer classes used by the load and save operations. Note
1559     * that this method must be called before invoking one of the {@code load()} and {@code save()} methods. Especially, if
1560     * you want to use a custom {@code IOFactory} for changing the {@code PropertiesReader}, you cannot load the
1561     * configuration data in the constructor.
1562     *
1563     * @param ioFactory the new {@code IOFactory} (must not be <b>null</b>)
1564     * @throws IllegalArgumentException if the {@code IOFactory} is <b>null</b>
1565     * @since 1.7
1566     */
1567    public void setIOFactory(final IOFactory ioFactory) {
1568        if (ioFactory == null) {
1569            throw new IllegalArgumentException("IOFactory must not be null.");
1570        }
1571
1572        this.ioFactory = ioFactory;
1573    }
1574
1575    /**
1576     * Sets the associated layout object.
1577     *
1578     * @param layout the new layout object; can be <b>null</b>, then a new layout object will be created
1579     * @since 1.3
1580     */
1581    public void setLayout(final PropertiesConfigurationLayout layout) {
1582        installLayout(layout);
1583    }
1584
1585    /**
1586     * {@inheritDoc} This implementation delegates to the associated layout object which does the actual saving. Note that,
1587     * analogous to {@link #read(Reader)}, this method does not do any synchronization.
1588     *
1589     * @since 2.0
1590     */
1591    @Override
1592    public void write(final Writer out) throws ConfigurationException, IOException {
1593        getLayout().save(this, out);
1594    }
1595
1596}