| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.configuration2; |
| |
| import java.io.FileNotFoundException; |
| import java.io.FilterWriter; |
| import java.io.IOException; |
| import java.io.LineNumberReader; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.configuration2.convert.ListDelimiterHandler; |
| import org.apache.commons.configuration2.convert.ValueTransformer; |
| import org.apache.commons.configuration2.event.ConfigurationEvent; |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; |
| import org.apache.commons.configuration2.io.FileHandler; |
| import org.apache.commons.configuration2.io.FileLocator; |
| import org.apache.commons.configuration2.io.FileLocatorAware; |
| import org.apache.commons.configuration2.io.FileLocatorUtils; |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.text.StringEscapeUtils; |
| import org.apache.commons.text.translate.AggregateTranslator; |
| import org.apache.commons.text.translate.CharSequenceTranslator; |
| import org.apache.commons.text.translate.EntityArrays; |
| import org.apache.commons.text.translate.LookupTranslator; |
| import org.apache.commons.text.translate.UnicodeEscaper; |
| |
| /** |
| * This is the "classic" Properties loader which loads the values from a single or multiple files (which can be chained |
| * with "include =". All given path references are either absolute or relative to the file name supplied in the |
| * constructor. |
| * <p> |
| * In this class, empty PropertyConfigurations can be built, properties added and later saved. include statements are |
| * (obviously) not supported if you don't construct a PropertyConfiguration from a file. |
| * |
| * <p> |
| * The properties file syntax is explained here, basically it follows the syntax of the stream parsed by |
| * {@link java.util.Properties#load} and adds several useful extensions: |
| * |
| * <ul> |
| * <li>Each property has the syntax {@code key <separator> value}. The separators accepted are {@code '='}, |
| * {@code ':'} and any white space character. Examples: |
| * |
| * <pre> |
| * key1 = value1 |
| * key2 : value2 |
| * key3 value3 |
| * </pre> |
| * |
| * </li> |
| * <li>The <i>key</i> may use any character, separators must be escaped: |
| * |
| * <pre> |
| * key\:foo = bar |
| * </pre> |
| * |
| * </li> |
| * <li><i>value</i> may be separated on different lines if a backslash is placed at the end of the line that continues |
| * below.</li> |
| * <li>The list delimiter facilities provided by {@link AbstractConfiguration} are supported, too. If an appropriate |
| * {@link ListDelimiterHandler} is set (for instance a |
| * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler D efaultListDelimiterHandler} object |
| * configured with a comma as delimiter character), <i>value</i> can contain <em>value delimiters</em> and will then be |
| * interpreted as a list of tokens. So the following property definition |
| * |
| * <pre> |
| * key = This property, has multiple, values |
| * </pre> |
| * |
| * will result in a property with three values. You can change the handling of delimiters using the |
| * {@link AbstractConfiguration#setListDelimiterHandler(ListDelimiterHandler)} method. Per default, list splitting is |
| * disabled.</li> |
| * <li>Commas in each token are escaped placing a backslash right before the comma.</li> |
| * <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 |
| * commas. <em>Note</em>: When the configuration file is written back to disk the associated |
| * {@link PropertiesConfigurationLayout} object (see below) will try to preserve as much of the original format as |
| * possible, i.e. properties with multiple values defined on a single line will also be written back on a single line, |
| * and multiple occurrences of a single key will be written on multiple lines. If the {@code addProperty()} method was |
| * called multiple times for adding multiple values to a property, these properties will per default be written on |
| * multiple lines in the output file, too. Some options of the {@code PropertiesConfigurationLayout} class have |
| * influence on that behavior.</li> |
| * <li>Blank lines and lines starting with character '#' or '!' are skipped.</li> |
| * <li>If a property is named "include" (or whatever is defined by setInclude() and getInclude() and the value of that |
| * property is the full path to a file on disk, that file will be included into the configuration. You can also pull in |
| * files relative to the parent configuration file. So if you have something like the following: |
| * |
| * include = additional.properties |
| * |
| * Then "additional.properties" is expected to be in the same directory as the parent configuration file. |
| * |
| * The properties in the included file are added to the parent configuration, they do not replace existing properties |
| * with the same key. |
| * |
| * </li> |
| * <li>You can define custom error handling for the special key {@code "include"} by using |
| * {@link #setIncludeListener(ConfigurationConsumer)}.</li> |
| * </ul> |
| * |
| * <p> |
| * Here is an example of a valid extended properties file: |
| * </p> |
| * |
| * <pre> |
| * # lines starting with # are comments |
| * |
| * # This is the simplest property |
| * key = value |
| * |
| * # A long property may be separated on multiple lines |
| * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ |
| * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
| * |
| * # This is a property with many tokens |
| * tokens_on_a_line = first token, second token |
| * |
| * # This sequence generates exactly the same result |
| * tokens_on_multiple_lines = first token |
| * tokens_on_multiple_lines = second token |
| * |
| * # commas may be escaped in tokens |
| * commas.escaped = Hi\, what'up? |
| * |
| * # properties can reference other properties |
| * base.prop = /base |
| * first.prop = ${base.prop}/first |
| * second.prop = ${first.prop}/second |
| * </pre> |
| * |
| * <p> |
| * A {@code PropertiesConfiguration} object is associated with an instance of the {@link PropertiesConfigurationLayout} |
| * class, which is responsible for storing the layout of the parsed properties file (i.e. empty lines, comments, and |
| * such things). The {@code getLayout()} method can be used to obtain this layout object. With {@code setLayout()} a new |
| * layout object can be set. This should be done before a properties file was loaded. |
| * <p> |
| * Like other {@code Configuration} implementations, this class uses a {@code Synchronizer} object to control concurrent |
| * access. By choosing a suitable implementation of the {@code Synchronizer} interface, an instance can be made |
| * thread-safe or not. Note that access to most of the properties typically set through a builder is not protected by |
| * the {@code Synchronizer}. The intended usage is that these properties are set once at construction time through the |
| * builder and after that remain constant. If you wish to change such properties during life time of an instance, you |
| * have to use the {@code lock()} and {@code unlock()} methods manually to ensure that other threads see your changes. |
| * <p> |
| * As this class extends {@link AbstractConfiguration}, all basic features like variable interpolation, list handling, |
| * or data type conversions are available as well. This is described in the chapter |
| * <a href="https://commons.apache.org/proper/commons-configuration/userguide/howto_basicfeatures.html"> Basic features |
| * and AbstractConfiguration</a> of the user's guide. There is also a separate chapter dealing with |
| * <a href="commons.apache.org/proper/commons-configuration/userguide/howto_properties.html"> Properties files</a> in |
| * special. |
| * |
| * @see java.util.Properties#load |
| */ |
| public class PropertiesConfiguration extends BaseConfiguration implements FileBasedConfiguration, FileLocatorAware { |
| |
| /** |
| * <p> |
| * A default implementation of the {@code IOFactory} interface. |
| * </p> |
| * <p> |
| * This class implements the {@code createXXXX()} methods defined by the {@code IOFactory} interface in a way that the |
| * default objects (i.e. {@code PropertiesReader} and {@code PropertiesWriter} are returned. Customizing either the |
| * reader or the writer (or both) can be done by extending this class and overriding the corresponding |
| * {@code createXXXX()} method. |
| * </p> |
| * |
| * @since 1.7 |
| */ |
| public static class DefaultIOFactory implements IOFactory { |
| /** |
| * The singleton instance. |
| */ |
| static final DefaultIOFactory INSTANCE = new DefaultIOFactory(); |
| |
| @Override |
| public PropertiesReader createPropertiesReader(final Reader in) { |
| return new PropertiesReader(in); |
| } |
| |
| @Override |
| public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler) { |
| return new PropertiesWriter(out, handler); |
| } |
| } |
| |
| /** |
| * <p> |
| * Definition of an interface that allows customization of read and write operations. |
| * </p> |
| * <p> |
| * For reading and writing properties files the inner classes {@code PropertiesReader} and {@code PropertiesWriter} are |
| * used. This interface defines factory methods for creating both a {@code PropertiesReader} and a |
| * {@code PropertiesWriter}. An object implementing this interface can be passed to the {@code setIOFactory()} method of |
| * {@code PropertiesConfiguration}. Every time the configuration is read or written the {@code IOFactory} is asked to |
| * create the appropriate reader or writer object. This provides an opportunity to inject custom reader or writer |
| * implementations. |
| * </p> |
| * |
| * @since 1.7 |
| */ |
| public interface IOFactory { |
| /** |
| * Creates a {@code PropertiesReader} for reading a properties file. This method is called whenever the |
| * {@code PropertiesConfiguration} is loaded. The reader returned by this method is then used for parsing the properties |
| * file. |
| * |
| * @param in the underlying reader (of the properties file) |
| * @return the {@code PropertiesReader} for loading the configuration |
| */ |
| PropertiesReader createPropertiesReader(Reader in); |
| |
| /** |
| * Creates a {@code PropertiesWriter} for writing a properties file. This method is called before the |
| * {@code PropertiesConfiguration} is saved. The writer returned by this method is then used for writing the properties |
| * file. |
| * |
| * @param out the underlying writer (to the properties file) |
| * @param handler the list delimiter delimiter for list parsing |
| * @return the {@code PropertiesWriter} for saving the configuration |
| */ |
| PropertiesWriter createPropertiesWriter(Writer out, ListDelimiterHandler handler); |
| } |
| |
| /** |
| * An alternative {@link IOFactory} that tries to mimic the behavior of {@link java.util.Properties} (Jup) more closely. |
| * The goal is to allow both of them be used interchangeably when reading and writing properties files without losing or |
| * changing information. |
| * <p> |
| * It also has the option to <em>not</em> use Unicode escapes. When using UTF-8 encoding (which is e.g. the new default |
| * for resource bundle properties files since Java 9), Unicode escapes are no longer required and avoiding them makes |
| * properties files more readable with regular text editors. |
| * <p> |
| * Some of the ways this implementation differs from {@link DefaultIOFactory}: |
| * <ul> |
| * <li>Trailing whitespace will not be trimmed from each line.</li> |
| * <li>Unknown escape sequences will have their backslash removed.</li> |
| * <li>{@code \b} is not a recognized escape sequence.</li> |
| * <li>Leading spaces in property values are preserved by escaping them.</li> |
| * <li>All natural lines (i.e. in the file) of a logical property line will have their leading whitespace trimmed.</li> |
| * <li>Natural lines that look like comment lines within a logical line are not treated as such; they're part of the |
| * property value.</li> |
| * </ul> |
| * |
| * @since 2.4 |
| */ |
| public static class JupIOFactory implements IOFactory { |
| |
| /** |
| * Whether characters less than {@code \u0020} and characters greater than {@code \u007E} in property keys or values |
| * should be escaped using Unicode escape sequences. Not necessary when e.g. writing as UTF-8. |
| */ |
| private final boolean escapeUnicode; |
| |
| /** |
| * Constructs a new {@link JupIOFactory} with Unicode escaping. |
| */ |
| public JupIOFactory() { |
| this(true); |
| } |
| |
| /** |
| * Constructs a new {@link JupIOFactory} with optional Unicode escaping. Whether Unicode escaping is required depends on |
| * 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 |
| * necessary. Unfortunately this factory can't determine the encoding on its own. |
| * |
| * @param escapeUnicode whether Unicode characters should be escaped |
| */ |
| public JupIOFactory(final boolean escapeUnicode) { |
| this.escapeUnicode = escapeUnicode; |
| } |
| |
| @Override |
| public PropertiesReader createPropertiesReader(final Reader in) { |
| return new JupPropertiesReader(in); |
| } |
| |
| @Override |
| public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler) { |
| return new JupPropertiesWriter(out, handler, escapeUnicode); |
| } |
| |
| } |
| |
| /** |
| * A {@link PropertiesReader} that tries to mimic the behavior of {@link java.util.Properties}. |
| * |
| * @since 2.4 |
| */ |
| public static class JupPropertiesReader extends PropertiesReader { |
| |
| /** |
| * Constructs a new instance. |
| * |
| * @param reader A Reader. |
| */ |
| public JupPropertiesReader(final Reader reader) { |
| super(reader); |
| } |
| |
| @Override |
| protected void parseProperty(final String line) { |
| final String[] property = doParseProperty(line, false); |
| initPropertyName(property[0]); |
| initPropertyValue(property[1]); |
| initPropertySeparator(property[2]); |
| } |
| |
| @Override |
| public String readProperty() throws IOException { |
| getCommentLines().clear(); |
| final StringBuilder buffer = new StringBuilder(); |
| |
| while (true) { |
| String line = readLine(); |
| if (line == null) { |
| // EOF |
| if (buffer.length() > 0) { |
| break; |
| } |
| return null; |
| } |
| |
| // while a property line continues there are no comments (even if the line from |
| // the file looks like one) |
| if (isCommentLine(line) && buffer.length() == 0) { |
| getCommentLines().add(line); |
| continue; |
| } |
| |
| // while property line continues left trim all following lines read from the |
| // file |
| if (buffer.length() > 0) { |
| // index of the first non-whitespace character |
| int i; |
| for (i = 0; i < line.length(); i++) { |
| if (!Character.isWhitespace(line.charAt(i))) { |
| break; |
| } |
| } |
| |
| line = line.substring(i); |
| } |
| |
| if (!checkCombineLines(line)) { |
| buffer.append(line); |
| break; |
| } |
| line = line.substring(0, line.length() - 1); |
| buffer.append(line); |
| } |
| return buffer.toString(); |
| } |
| |
| @Override |
| protected String unescapePropertyValue(final String value) { |
| return unescapeJava(value, true); |
| } |
| |
| } |
| |
| /** |
| * A {@link PropertiesWriter} that tries to mimic the behavior of {@link java.util.Properties}. |
| * |
| * @since 2.4 |
| */ |
| public static class JupPropertiesWriter extends PropertiesWriter { |
| |
| /** |
| * The starting ASCII printable character. |
| */ |
| private static final int PRINTABLE_INDEX_END = 0x7e; |
| |
| /** |
| * The ending ASCII printable character. |
| */ |
| private static final int PRINTABLE_INDEX_START = 0x20; |
| |
| /** |
| * A UnicodeEscaper for characters outside the ASCII printable range. |
| */ |
| private static final UnicodeEscaper ESCAPER = UnicodeEscaper.outsideOf(PRINTABLE_INDEX_START, PRINTABLE_INDEX_END); |
| |
| /** |
| * Characters that need to be escaped when wring a properties file. |
| */ |
| private static final Map<CharSequence, CharSequence> JUP_CHARS_ESCAPE; |
| static { |
| final Map<CharSequence, CharSequence> initialMap = new HashMap<>(); |
| initialMap.put("\\", "\\\\"); |
| initialMap.put("\n", "\\n"); |
| initialMap.put("\t", "\\t"); |
| initialMap.put("\f", "\\f"); |
| initialMap.put("\r", "\\r"); |
| JUP_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap); |
| } |
| |
| /** |
| * Creates a new instance of {@code JupPropertiesWriter}. |
| * |
| * @param writer a Writer object providing the underlying stream |
| * @param delHandler the delimiter handler for dealing with properties with multiple values |
| * @param escapeUnicode whether Unicode characters should be escaped using Unicode escapes |
| */ |
| public JupPropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler, final boolean escapeUnicode) { |
| super(writer, delHandler, value -> { |
| String valueString = String.valueOf(value); |
| |
| final CharSequenceTranslator translator; |
| if (escapeUnicode) { |
| translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE), ESCAPER); |
| } else { |
| translator = new AggregateTranslator(new LookupTranslator(JUP_CHARS_ESCAPE)); |
| } |
| |
| valueString = translator.translate(valueString); |
| |
| // escape the first leading space to preserve it (and all after it) |
| if (valueString.startsWith(" ")) { |
| valueString = "\\" + valueString; |
| } |
| |
| return valueString; |
| }); |
| } |
| |
| } |
| |
| /** |
| * This class is used to read properties lines. These lines do not terminate with new-line chars but rather when there |
| * is no backslash sign a the end of the line. This is used to concatenate multiple lines for readability. |
| */ |
| public static class PropertiesReader extends LineNumberReader { |
| |
| /** The regular expression to parse the key and the value of a property. */ |
| private static final Pattern PROPERTY_PATTERN = Pattern |
| .compile("(([\\S&&[^\\\\" + new String(SEPARATORS) + "]]|\\\\.)*+)(\\s*(\\s+|[" + new String(SEPARATORS) + "])\\s*)?(.*)"); |
| |
| /** Constant for the index of the group for the key. */ |
| private static final int IDX_KEY = 1; |
| |
| /** Constant for the index of the group for the value. */ |
| private static final int IDX_VALUE = 5; |
| |
| /** Constant for the index of the group for the separator. */ |
| private static final int IDX_SEPARATOR = 3; |
| |
| /** Stores the comment lines for the currently processed property. */ |
| private final List<String> commentLines; |
| |
| /** Stores the name of the last read property. */ |
| private String propertyName; |
| |
| /** Stores the value of the last read property. */ |
| private String propertyValue; |
| |
| /** Stores the property separator of the last read property. */ |
| private String propertySeparator = DEFAULT_SEPARATOR; |
| |
| /** |
| * Constructs a new instance. |
| * |
| * @param reader A Reader. |
| */ |
| public PropertiesReader(final Reader reader) { |
| super(reader); |
| commentLines = new ArrayList<>(); |
| } |
| |
| /** |
| * Checks if the passed in line should be combined with the following. This is true, if the line ends with an odd number |
| * of backslashes. |
| * |
| * @param line the line |
| * @return a flag if the lines should be combined |
| */ |
| static boolean checkCombineLines(final String line) { |
| return countTrailingBS(line) % 2 != 0; |
| } |
| |
| /** |
| * Parse a property line and return the key, the value, and the separator in an array. |
| * |
| * @param line the line to parse |
| * @param trimValue flag whether the value is to be trimmed |
| * @return an array with the property's key, value, and separator |
| */ |
| static String[] doParseProperty(final String line, final boolean trimValue) { |
| final Matcher matcher = PROPERTY_PATTERN.matcher(line); |
| |
| final String[] result = {"", "", ""}; |
| |
| if (matcher.matches()) { |
| result[0] = matcher.group(IDX_KEY).trim(); |
| |
| String value = matcher.group(IDX_VALUE); |
| if (trimValue) { |
| value = value.trim(); |
| } |
| result[1] = value; |
| |
| result[2] = matcher.group(IDX_SEPARATOR); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Gets the comment lines that have been read for the last property. |
| * |
| * @return the comment lines for the last property returned by {@code readProperty()} |
| * @since 1.3 |
| */ |
| public List<String> getCommentLines() { |
| return commentLines; |
| } |
| |
| /** |
| * Gets the name of the last read property. This method can be called after {@link #nextProperty()} was invoked and |
| * its return value was <b>true</b>. |
| * |
| * @return the name of the last read property |
| * @since 1.3 |
| */ |
| public String getPropertyName() { |
| return propertyName; |
| } |
| |
| /** |
| * Gets the separator that was used for the last read property. The separator can be stored so that it can later be |
| * restored when saving the configuration. |
| * |
| * @return the separator for the last read property |
| * @since 1.7 |
| */ |
| public String getPropertySeparator() { |
| return propertySeparator; |
| } |
| |
| /** |
| * Gets the value of the last read property. This method can be called after {@link #nextProperty()} was invoked and |
| * its return value was <b>true</b>. |
| * |
| * @return the value of the last read property |
| * @since 1.3 |
| */ |
| public String getPropertyValue() { |
| return propertyValue; |
| } |
| |
| /** |
| * Sets the name of the current property. This method can be called by {@code parseProperty()} for storing the results |
| * of the parse operation. It also ensures that the property key is correctly escaped. |
| * |
| * @param name the name of the current property |
| * @since 1.7 |
| */ |
| protected void initPropertyName(final String name) { |
| propertyName = unescapePropertyName(name); |
| } |
| |
| /** |
| * Sets the separator of the current property. This method can be called by {@code parseProperty()}. It allows the |
| * associated layout object to keep track of the property separators. When saving the configuration the separators can |
| * be restored. |
| * |
| * @param value the separator used for the current property |
| * @since 1.7 |
| */ |
| protected void initPropertySeparator(final String value) { |
| propertySeparator = value; |
| } |
| |
| /** |
| * Sets the value of the current property. This method can be called by {@code parseProperty()} for storing the results |
| * of the parse operation. It also ensures that the property value is correctly escaped. |
| * |
| * @param value the value of the current property |
| * @since 1.7 |
| */ |
| protected void initPropertyValue(final String value) { |
| propertyValue = unescapePropertyValue(value); |
| } |
| |
| /** |
| * Parses the next property from the input stream and stores the found name and value in internal fields. These fields |
| * can be obtained using the provided getter methods. The return value indicates whether EOF was reached (<b>false</b>) |
| * or whether further properties are available (<b>true</b>). |
| * |
| * @return a flag if further properties are available |
| * @throws IOException if an error occurs |
| * @since 1.3 |
| */ |
| public boolean nextProperty() throws IOException { |
| final String line = readProperty(); |
| |
| if (line == null) { |
| return false; // EOF |
| } |
| |
| // parse the line |
| parseProperty(line); |
| return true; |
| } |
| |
| /** |
| * Parses a line read from the properties file. This method is called for each non-comment line read from the source |
| * file. Its task is to split the passed in line into the property key and its value. The results of the parse operation |
| * can be stored by calling the {@code initPropertyXXX()} methods. |
| * |
| * @param line the line read from the properties file |
| * @since 1.7 |
| */ |
| protected void parseProperty(final String line) { |
| final String[] property = doParseProperty(line, true); |
| initPropertyName(property[0]); |
| initPropertyValue(property[1]); |
| initPropertySeparator(property[2]); |
| } |
| |
| /** |
| * Reads a property line. Returns null if Stream is at EOF. Concatenates lines ending with "\". Skips lines beginning |
| * with "#" or "!" and empty lines. The return value is a property definition ({@code <name>} = |
| * {@code <value>}) |
| * |
| * @return A string containing a property value or null |
| * |
| * @throws IOException in case of an I/O error |
| */ |
| public String readProperty() throws IOException { |
| commentLines.clear(); |
| final StringBuilder buffer = new StringBuilder(); |
| |
| while (true) { |
| String line = readLine(); |
| if (line == null) { |
| // EOF |
| return null; |
| } |
| |
| if (isCommentLine(line)) { |
| commentLines.add(line); |
| continue; |
| } |
| |
| line = line.trim(); |
| |
| if (!checkCombineLines(line)) { |
| buffer.append(line); |
| break; |
| } |
| line = line.substring(0, line.length() - 1); |
| buffer.append(line); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Performs unescaping on the given property name. |
| * |
| * @param name the property name |
| * @return the unescaped property name |
| * @since 2.4 |
| */ |
| protected String unescapePropertyName(final String name) { |
| return StringEscapeUtils.unescapeJava(name); |
| } |
| |
| /** |
| * Performs unescaping on the given property value. |
| * |
| * @param value the property value |
| * @return the unescaped property value |
| * @since 2.4 |
| */ |
| protected String unescapePropertyValue(final String value) { |
| return unescapeJava(value); |
| } |
| } // class PropertiesReader |
| |
| /** |
| * This class is used to write properties lines. The most important method is |
| * {@code writeProperty(String, Object, boolean)}, which is called during a save operation for each property found in |
| * the configuration. |
| */ |
| public static class PropertiesWriter extends FilterWriter { |
| |
| /** |
| * Properties escape map. |
| */ |
| private static final Map<CharSequence, CharSequence> PROPERTIES_CHARS_ESCAPE; |
| static { |
| final Map<CharSequence, CharSequence> initialMap = new HashMap<>(); |
| initialMap.put("\\", "\\\\"); |
| PROPERTIES_CHARS_ESCAPE = Collections.unmodifiableMap(initialMap); |
| } |
| |
| /** |
| * A translator for escaping property values. This translator performs a subset of transformations done by the |
| * ESCAPE_JAVA translator from Commons Lang 3. |
| */ |
| private static final CharSequenceTranslator ESCAPE_PROPERTIES = new AggregateTranslator(new LookupTranslator(PROPERTIES_CHARS_ESCAPE), |
| new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE), UnicodeEscaper.outsideOf(32, 0x7f)); |
| |
| /** |
| * A {@code ValueTransformer} implementation used to escape property values. This implementation applies the |
| * transformation defined by the {@link #ESCAPE_PROPERTIES} translator. |
| */ |
| private static final ValueTransformer DEFAULT_TRANSFORMER = value -> { |
| final String strVal = String.valueOf(value); |
| return ESCAPE_PROPERTIES.translate(strVal); |
| }; |
| |
| /** The value transformer used for escaping property values. */ |
| private final ValueTransformer valueTransformer; |
| |
| /** The list delimiter handler. */ |
| private final ListDelimiterHandler delimiterHandler; |
| |
| /** The separator to be used for the current property. */ |
| private String currentSeparator; |
| |
| /** The global separator. If set, it overrides the current separator. */ |
| private String globalSeparator; |
| |
| /** The line separator. */ |
| private String lineSeparator; |
| |
| /** |
| * Creates a new instance of {@code PropertiesWriter}. |
| * |
| * @param writer a Writer object providing the underlying stream |
| * @param delHandler the delimiter handler for dealing with properties with multiple values |
| */ |
| public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler) { |
| this(writer, delHandler, DEFAULT_TRANSFORMER); |
| } |
| |
| /** |
| * Creates a new instance of {@code PropertiesWriter}. |
| * |
| * @param writer a Writer object providing the underlying stream |
| * @param delHandler the delimiter handler for dealing with properties with multiple values |
| * @param valueTransformer the value transformer used to escape property values |
| */ |
| public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler, final ValueTransformer valueTransformer) { |
| super(writer); |
| delimiterHandler = delHandler; |
| this.valueTransformer = valueTransformer; |
| } |
| |
| /** |
| * Escapes the key of a property before it gets written to file. This method is called on saving a configuration for |
| * each property key. It ensures that separator characters contained in the key are escaped. |
| * |
| * @param key the key |
| * @return the escaped key |
| * @since 2.0 |
| */ |
| protected String escapeKey(final String key) { |
| final StringBuilder newkey = new StringBuilder(); |
| |
| for (int i = 0; i < key.length(); i++) { |
| final char c = key.charAt(i); |
| |
| if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c) || c == '\\') { |
| // escape the separator |
| newkey.append('\\'); |
| } |
| newkey.append(c); |
| } |
| |
| return newkey.toString(); |
| } |
| |
| /** |
| * Returns the separator to be used for the given property. This method is called by {@code writeProperty()}. The string |
| * returned here is used as separator between the property key and its value. Per default the method checks whether a |
| * global separator is set. If this is the case, it is returned. Otherwise the separator returned by |
| * {@code getCurrentSeparator()} is used, which was set by the associated layout object. Derived classes may implement a |
| * different strategy for defining the separator. |
| * |
| * @param key the property key |
| * @param value the value |
| * @return the separator to be used |
| * @since 1.7 |
| */ |
| protected String fetchSeparator(final String key, final Object value) { |
| return getGlobalSeparator() != null ? getGlobalSeparator() : StringUtils.defaultString(getCurrentSeparator()); |
| } |
| |
| /** |
| * Gets the current property separator. |
| * |
| * @return the current property separator |
| * @since 1.7 |
| */ |
| public String getCurrentSeparator() { |
| return currentSeparator; |
| } |
| |
| /** |
| * Gets the delimiter handler for properties with multiple values. This object is used to escape property values so |
| * that they can be read in correctly the next time they are loaded. |
| * |
| * @return the delimiter handler for properties with multiple values |
| * @since 2.0 |
| */ |
| public ListDelimiterHandler getDelimiterHandler() { |
| return delimiterHandler; |
| } |
| |
| /** |
| * Gets the global property separator. |
| * |
| * @return the global property separator |
| * @since 1.7 |
| */ |
| public String getGlobalSeparator() { |
| return globalSeparator; |
| } |
| |
| /** |
| * Gets the line separator. |
| * |
| * @return the line separator |
| * @since 1.7 |
| */ |
| public String getLineSeparator() { |
| return lineSeparator != null ? lineSeparator : LINE_SEPARATOR; |
| } |
| |
| /** |
| * Sets the current property separator. This separator is used when writing the next property. |
| * |
| * @param currentSeparator the current property separator |
| * @since 1.7 |
| */ |
| public void setCurrentSeparator(final String currentSeparator) { |
| this.currentSeparator = currentSeparator; |
| } |
| |
| /** |
| * Sets the global property separator. This separator corresponds to the {@code globalSeparator} property of |
| * {@link PropertiesConfigurationLayout}. It defines the separator to be used for all properties. If it is undefined, |
| * the current separator is used. |
| * |
| * @param globalSeparator the global property separator |
| * @since 1.7 |
| */ |
| public void setGlobalSeparator(final String globalSeparator) { |
| this.globalSeparator = globalSeparator; |
| } |
| |
| /** |
| * Sets the line separator. Each line written by this writer is terminated with this separator. If not set, the |
| * platform-specific line separator is used. |
| * |
| * @param lineSeparator the line separator to be used |
| * @since 1.7 |
| */ |
| public void setLineSeparator(final String lineSeparator) { |
| this.lineSeparator = lineSeparator; |
| } |
| |
| /** |
| * Write a comment. |
| * |
| * @param comment the comment to write |
| * @throws IOException if an I/O error occurs. |
| */ |
| public void writeComment(final String comment) throws IOException { |
| writeln("# " + comment); |
| } |
| |
| /** |
| * Helper method for writing a line with the platform specific line ending. |
| * |
| * @param s the content of the line (may be <b>null</b>) |
| * @throws IOException if an error occurs |
| * @since 1.3 |
| */ |
| public void writeln(final String s) throws IOException { |
| if (s != null) { |
| write(s); |
| } |
| write(getLineSeparator()); |
| } |
| |
| /** |
| * Write a property. |
| * |
| * @param key The key of the property |
| * @param values The array of values of the property |
| * |
| * @throws IOException if an I/O error occurs. |
| */ |
| public void writeProperty(final String key, final List<?> values) throws IOException { |
| for (final Object value : values) { |
| writeProperty(key, value); |
| } |
| } |
| |
| /** |
| * Write a property. |
| * |
| * @param key the key of the property |
| * @param value the value of the property |
| * |
| * @throws IOException if an I/O error occurs. |
| */ |
| public void writeProperty(final String key, final Object value) throws IOException { |
| writeProperty(key, value, false); |
| } |
| |
| /** |
| * Writes the given property and its value. If the value happens to be a list, the {@code forceSingleLine} flag is |
| * evaluated. If it is set, all values are written on a single line using the list delimiter as separator. |
| * |
| * @param key the property key |
| * @param value the property value |
| * @param forceSingleLine the "force single line" flag |
| * @throws IOException if an error occurs |
| * @since 1.3 |
| */ |
| public void writeProperty(final String key, final Object value, final boolean forceSingleLine) throws IOException { |
| String v; |
| |
| if (value instanceof List) { |
| v = null; |
| final List<?> values = (List<?>) value; |
| if (forceSingleLine) { |
| try { |
| v = String.valueOf(getDelimiterHandler().escapeList(values, valueTransformer)); |
| } catch (final UnsupportedOperationException ignored) { |
| // the handler may not support escaping lists, |
| // then the list is written in multiple lines |
| } |
| } |
| if (v == null) { |
| writeProperty(key, values); |
| return; |
| } |
| } else { |
| v = String.valueOf(getDelimiterHandler().escape(value, valueTransformer)); |
| } |
| |
| write(escapeKey(key)); |
| write(fetchSeparator(key, value)); |
| write(v); |
| |
| writeln(null); |
| } |
| } // class PropertiesWriter |
| |
| /** |
| * Defines default error handling for the special {@code "include"} key by throwing the given exception. |
| * |
| * @since 2.6 |
| */ |
| public static final ConfigurationConsumer<ConfigurationException> DEFAULT_INCLUDE_LISTENER = e -> { |
| throw e; |
| }; |
| |
| /** |
| * Defines error handling as a noop for the special {@code "include"} key. |
| * |
| * @since 2.6 |
| */ |
| public static final ConfigurationConsumer<ConfigurationException> NOOP_INCLUDE_LISTENER = e -> { /* noop */ }; |
| |
| /** |
| * The default encoding (ISO-8859-1 as specified by https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html) |
| */ |
| public static final String DEFAULT_ENCODING = StandardCharsets.ISO_8859_1.name(); |
| |
| /** Constant for the supported comment characters. */ |
| static final String COMMENT_CHARS = "#!"; |
| |
| /** Constant for the default properties separator. */ |
| static final String DEFAULT_SEPARATOR = " = "; |
| |
| /** |
| * A string with special characters that need to be unescaped when reading a properties file. |
| * {@link java.util.Properties} escapes these characters when writing out a properties file. |
| */ |
| private static final String UNESCAPE_CHARACTERS = ":#=!\\\'\""; |
| |
| /** |
| * This is the name of the property that can point to other properties file for including other properties files. |
| */ |
| private static String include = "include"; |
| |
| /** |
| * This is the name of the property that can point to other properties file for including other properties files. |
| * <p> |
| * If the file is absent, processing continues normally. |
| * </p> |
| */ |
| private static String includeOptional = "includeoptional"; |
| |
| /** The list of possible key/value separators */ |
| private static final char[] SEPARATORS = {'=', ':'}; |
| |
| /** The white space characters used as key/value separators. */ |
| private static final char[] WHITE_SPACE = {' ', '\t', '\f'}; |
| |
| /** Constant for the platform specific line separator. */ |
| private static final String LINE_SEPARATOR = System.lineSeparator(); |
| |
| /** Constant for the radix of hex numbers. */ |
| private static final int HEX_RADIX = 16; |
| |
| /** Constant for the length of a unicode literal. */ |
| private static final int UNICODE_LEN = 4; |
| |
| /** Stores the layout object. */ |
| private PropertiesConfigurationLayout layout; |
| |
| /** The include listener for the special {@code "include"} key. */ |
| private ConfigurationConsumer<ConfigurationException> includeListener; |
| |
| /** The IOFactory for creating readers and writers. */ |
| private IOFactory ioFactory; |
| |
| /** The current {@code FileLocator}. */ |
| private FileLocator locator; |
| |
| /** Allow file inclusion or not */ |
| private boolean includesAllowed = true; |
| |
| /** |
| * Creates an empty PropertyConfiguration object which can be used to synthesize a new Properties file by adding values |
| * and then saving(). |
| */ |
| public PropertiesConfiguration() { |
| installLayout(createLayout()); |
| } |
| |
| /** |
| * Returns the number of trailing backslashes. This is sometimes needed for the correct handling of escape characters. |
| * |
| * @param line the string to investigate |
| * @return the number of trailing backslashes |
| */ |
| private static int countTrailingBS(final String line) { |
| int bsCount = 0; |
| for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) { |
| bsCount++; |
| } |
| |
| return bsCount; |
| } |
| |
| /** |
| * Gets the property value for including other properties files. By default it is "include". |
| * |
| * @return A String. |
| */ |
| public static String getInclude() { |
| return PropertiesConfiguration.include; |
| } |
| |
| /** |
| * Gets the property value for including other properties files. By default it is "includeoptional". |
| * <p> |
| * If the file is absent, processing continues normally. |
| * </p> |
| * |
| * @return A String. |
| * @since 2.5 |
| */ |
| public static String getIncludeOptional() { |
| return PropertiesConfiguration.includeOptional; |
| } |
| |
| /** |
| * Tests whether a line is a comment, i.e. whether it starts with a comment character. |
| * |
| * @param line the line |
| * @return a flag if this is a comment line |
| * @since 1.3 |
| */ |
| static boolean isCommentLine(final String line) { |
| final String s = line.trim(); |
| // blank lines are also treated as comment lines |
| return s.isEmpty() || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0; |
| } |
| |
| /** |
| * Checks whether the specified character needs to be unescaped. This method is called when during reading a property |
| * file an escape character ('\') is detected. If the character following the escape character is recognized as a |
| * special character which is escaped per default in a Java properties file, it has to be unescaped. |
| * |
| * @param ch the character in question |
| * @return a flag whether this character has to be unescaped |
| */ |
| private static boolean needsUnescape(final char ch) { |
| return UNESCAPE_CHARACTERS.indexOf(ch) >= 0; |
| } |
| |
| /** |
| * Sets the property value for including other properties files. By default it is "include". |
| * |
| * @param inc A String. |
| */ |
| public static void setInclude(final String inc) { |
| PropertiesConfiguration.include = inc; |
| } |
| |
| /** |
| * Sets the property value for including other properties files. By default it is "include". |
| * <p> |
| * If the file is absent, processing continues normally. |
| * </p> |
| * |
| * @param inc A String. |
| * @since 2.5 |
| */ |
| public static void setIncludeOptional(final String inc) { |
| PropertiesConfiguration.includeOptional = inc; |
| } |
| |
| /** |
| * <p> |
| * Unescapes any Java literals found in the {@code String} to a {@code Writer}. |
| * </p> |
| * This is a slightly modified version of the StringEscapeUtils.unescapeJava() function in commons-lang that doesn't |
| * drop escaped separators (i.e '\,'). |
| * |
| * @param str the {@code String} to unescape, may be null |
| * @return the processed string |
| * @throws IllegalArgumentException if the Writer is {@code null} |
| */ |
| protected static String unescapeJava(final String str) { |
| return unescapeJava(str, false); |
| } |
| |
| /** |
| * Unescapes Java literals found in the {@code String} to a {@code Writer}. |
| * <p> |
| * When the parameter {@code jupCompatible} is {@code false}, the classic behavior is used (see |
| * {@link #unescapeJava(String)}). When it's {@code true} a slightly different behavior that's compatible with |
| * {@link java.util.Properties} is used (see {@link JupIOFactory}). |
| * </p> |
| * |
| * @param str the {@code String} to unescape, may be null |
| * @param jupCompatible whether unescaping is compatible with {@link java.util.Properties}; otherwise the classic |
| * behavior is used |
| * @return the processed string |
| * @throws IllegalArgumentException if the Writer is {@code null} |
| */ |
| protected static String unescapeJava(final String str, final boolean jupCompatible) { |
| if (str == null) { |
| return null; |
| } |
| final int sz = str.length(); |
| final StringBuilder out = new StringBuilder(sz); |
| final StringBuilder unicode = new StringBuilder(UNICODE_LEN); |
| boolean hadSlash = false; |
| boolean inUnicode = false; |
| for (int i = 0; i < sz; i++) { |
| final char ch = str.charAt(i); |
| if (inUnicode) { |
| // if in unicode, then we're reading unicode |
| // values in somehow |
| unicode.append(ch); |
| if (unicode.length() == UNICODE_LEN) { |
| // unicode now contains the four hex digits |
| // which represents our unicode character |
| try { |
| final int value = Integer.parseInt(unicode.toString(), HEX_RADIX); |
| out.append((char) value); |
| unicode.setLength(0); |
| inUnicode = false; |
| hadSlash = false; |
| } catch (final NumberFormatException nfe) { |
| throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe); |
| } |
| } |
| continue; |
| } |
| |
| if (hadSlash) { |
| // handle an escaped value |
| hadSlash = false; |
| |
| switch (ch) { |
| case 'r': |
| out.append('\r'); |
| break; |
| case 'f': |
| out.append('\f'); |
| break; |
| case 't': |
| out.append('\t'); |
| break; |
| case 'n': |
| out.append('\n'); |
| break; |
| default: |
| if (!jupCompatible && ch == 'b') { |
| out.append('\b'); |
| } else if (ch == 'u') { |
| // uh-oh, we're in unicode country.... |
| inUnicode = true; |
| } else { |
| // JUP simply throws away the \ of unknown escape sequences |
| if (!needsUnescape(ch) && !jupCompatible) { |
| out.append('\\'); |
| } |
| out.append(ch); |
| } |
| break; |
| } |
| |
| continue; |
| } |
| if (ch == '\\') { |
| hadSlash = true; |
| continue; |
| } |
| out.append(ch); |
| } |
| |
| if (hadSlash) { |
| // then we're in the weird case of a \ at the end of the |
| // string, let's output it anyway. |
| out.append('\\'); |
| } |
| |
| return out.toString(); |
| } |
| |
| /** |
| * Creates a copy of this object. |
| * |
| * @return the copy |
| */ |
| @Override |
| public Object clone() { |
| final PropertiesConfiguration copy = (PropertiesConfiguration) super.clone(); |
| if (layout != null) { |
| copy.setLayout(new PropertiesConfigurationLayout(layout)); |
| } |
| return copy; |
| } |
| |
| /** |
| * Creates a standard layout object. This configuration is initialized with such a standard layout. |
| * |
| * @return the newly created layout object |
| */ |
| private PropertiesConfigurationLayout createLayout() { |
| return new PropertiesConfigurationLayout(); |
| } |
| |
| /** |
| * Gets the footer comment. This is a comment at the very end of the file. |
| * |
| * @return the footer comment |
| * @since 2.0 |
| */ |
| public String getFooter() { |
| beginRead(false); |
| try { |
| return getLayout().getFooterComment(); |
| } finally { |
| endRead(); |
| } |
| } |
| |
| /** |
| * Gets the comment header. |
| * |
| * @return the comment header |
| * @since 1.1 |
| */ |
| public String getHeader() { |
| beginRead(false); |
| try { |
| return getLayout().getHeaderComment(); |
| } finally { |
| endRead(); |
| } |
| } |
| |
| /** |
| * Gets the current include listener, never null. |
| * |
| * @return the current include listener, never null. |
| * @since 2.6 |
| */ |
| public ConfigurationConsumer<ConfigurationException> getIncludeListener() { |
| return includeListener != null ? includeListener : PropertiesConfiguration.DEFAULT_INCLUDE_LISTENER; |
| } |
| |
| /** |
| * Gets the {@code IOFactory} to be used for creating readers and writers when loading or saving this configuration. |
| * |
| * @return the {@code IOFactory} |
| * @since 1.7 |
| */ |
| public IOFactory getIOFactory() { |
| return ioFactory != null ? ioFactory : DefaultIOFactory.INSTANCE; |
| } |
| |
| /** |
| * Gets the associated layout object. |
| * |
| * @return the associated layout object |
| * @since 1.3 |
| */ |
| public PropertiesConfigurationLayout getLayout() { |
| return layout; |
| } |
| |
| /** |
| * Stores the current {@code FileLocator} for a following IO operation. The {@code FileLocator} is needed to resolve |
| * include files with relative file names. |
| * |
| * @param locator the current {@code FileLocator} |
| * @since 2.0 |
| */ |
| @Override |
| public void initFileLocator(final FileLocator locator) { |
| this.locator = locator; |
| } |
| |
| /** |
| * Installs a layout object. It has to be ensured that the layout is registered as change listener at this |
| * configuration. If there is already a layout object installed, it has to be removed properly. |
| * |
| * @param layout the layout object to be installed |
| */ |
| private void installLayout(final PropertiesConfigurationLayout layout) { |
| // only one layout must exist |
| if (this.layout != null) { |
| removeEventListener(ConfigurationEvent.ANY, this.layout); |
| } |
| |
| if (layout == null) { |
| this.layout = createLayout(); |
| } else { |
| this.layout = layout; |
| } |
| addEventListener(ConfigurationEvent.ANY, this.layout); |
| } |
| |
| /** |
| * Reports the status of file inclusion. |
| * |
| * @return True if include files are loaded. |
| */ |
| public boolean isIncludesAllowed() { |
| return this.includesAllowed; |
| } |
| |
| /** |
| * Helper method for loading an included properties file. This method is called by {@code load()} when an |
| * {@code include} property is encountered. It tries to resolve relative file names based on the current base path. If |
| * this fails, a resolution based on the location of this properties file is tried. |
| * |
| * @param fileName the name of the file to load |
| * @param optional whether or not the {@code fileName} is optional |
| * @param seenStack Stack of seen include URLs |
| * @throws ConfigurationException if loading fails |
| */ |
| private void loadIncludeFile(final String fileName, final boolean optional, final Deque<URL> seenStack) throws ConfigurationException { |
| if (locator == null) { |
| throw new ConfigurationException( |
| "Load operation not properly " + "initialized! Do not call read(InputStream) directly," + " but use a FileHandler to load a configuration."); |
| } |
| |
| URL url = locateIncludeFile(locator.getBasePath(), fileName); |
| if (url == null) { |
| final URL baseURL = locator.getSourceURL(); |
| if (baseURL != null) { |
| url = locateIncludeFile(baseURL.toString(), fileName); |
| } |
| } |
| |
| if (optional && url == null) { |
| return; |
| } |
| |
| if (url == null) { |
| getIncludeListener().accept(new ConfigurationException("Cannot resolve include file " + fileName, new FileNotFoundException(fileName))); |
| } else { |
| final FileHandler fh = new FileHandler(this); |
| fh.setFileLocator(locator); |
| final FileLocator orgLocator = locator; |
| try { |
| try { |
| // Check for cycles |
| if (seenStack.contains(url)) { |
| throw new ConfigurationException(String.format("Cycle detected loading %s, seen stack: %s", url, seenStack)); |
| } |
| seenStack.add(url); |
| try { |
| fh.load(url); |
| } finally { |
| seenStack.pop(); |
| } |
| } catch (final ConfigurationException e) { |
| getIncludeListener().accept(e); |
| } |
| } finally { |
| locator = orgLocator; // reset locator which is changed by load |
| } |
| } |
| } |
| |
| /** |
| * Tries to obtain the URL of an include file using the specified (optional) base path and file name. |
| * |
| * @param basePath the base path |
| * @param fileName the file name |
| * @return the URL of the include file or <b>null</b> if it cannot be resolved |
| */ |
| private URL locateIncludeFile(final String basePath, final String fileName) { |
| final FileLocator includeLocator = FileLocatorUtils.fileLocator(locator).sourceURL(null).basePath(basePath).fileName(fileName).create(); |
| return FileLocatorUtils.locate(includeLocator); |
| } |
| |
| /** |
| * This method is invoked by the associated {@link PropertiesConfigurationLayout} object for each property definition |
| * detected in the parsed properties file. Its task is to check whether this is a special property definition (e.g. the |
| * {@code include} property). If not, the property must be added to this configuration. The return value indicates |
| * whether the property should be treated as a normal property. If it is <b>false</b>, the layout object will ignore |
| * this property. |
| * |
| * @param key the property key |
| * @param value the property value |
| * @param seenStack the stack of seen include URLs |
| * @return a flag whether this is a normal property |
| * @throws ConfigurationException if an error occurs |
| * @since 1.3 |
| */ |
| boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack) throws ConfigurationException { |
| final boolean result; |
| |
| if (StringUtils.isNotEmpty(getInclude()) && key.equalsIgnoreCase(getInclude())) { |
| if (isIncludesAllowed()) { |
| final Collection<String> files = getListDelimiterHandler().split(value, true); |
| for (final String f : files) { |
| loadIncludeFile(interpolate(f), false, seenStack); |
| } |
| } |
| result = false; |
| } else if (StringUtils.isNotEmpty(getIncludeOptional()) && key.equalsIgnoreCase(getIncludeOptional())) { |
| if (isIncludesAllowed()) { |
| final Collection<String> files = getListDelimiterHandler().split(value, true); |
| for (final String f : files) { |
| loadIncludeFile(interpolate(f), true, seenStack); |
| } |
| } |
| result = false; |
| } else { |
| addPropertyInternal(key, value); |
| result = true; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation delegates to the associated layout object which does the actual loading. Note that |
| * this method does not do any synchronization. This lies in the responsibility of the caller. (Typically, the caller is |
| * a {@code FileHandler} object which takes care for proper synchronization.) |
| * |
| * @since 2.0 |
| */ |
| @Override |
| public void read(final Reader in) throws ConfigurationException, IOException { |
| getLayout().load(this, in); |
| } |
| |
| /** |
| * Sets the footer comment. If set, this comment is written after all properties at the end of the file. |
| * |
| * @param footer the footer comment |
| * @since 2.0 |
| */ |
| public void setFooter(final String footer) { |
| beginWrite(false); |
| try { |
| getLayout().setFooterComment(footer); |
| } finally { |
| endWrite(); |
| } |
| } |
| |
| /** |
| * Sets the comment header. |
| * |
| * @param header the header to use |
| * @since 1.1 |
| */ |
| public void setHeader(final String header) { |
| beginWrite(false); |
| try { |
| getLayout().setHeaderComment(header); |
| } finally { |
| endWrite(); |
| } |
| } |
| |
| /** |
| * Sets the current include listener, may not be null. |
| * |
| * @param includeListener the current include listener, may not be null. |
| * @throws IllegalArgumentException if the {@code includeListener} is null. |
| * @since 2.6 |
| */ |
| public void setIncludeListener(final ConfigurationConsumer<ConfigurationException> includeListener) { |
| if (includeListener == null) { |
| throw new IllegalArgumentException("includeListener must not be null."); |
| } |
| this.includeListener = includeListener; |
| } |
| |
| /** |
| * Controls whether additional files can be loaded by the {@code include = <xxx>} statement or not. This is <b>true</b> |
| * per default. |
| * |
| * @param includesAllowed True if Includes are allowed. |
| */ |
| public void setIncludesAllowed(final boolean includesAllowed) { |
| this.includesAllowed = includesAllowed; |
| } |
| |
| /** |
| * Sets the {@code IOFactory} to be used for creating readers and writers when loading or saving this configuration. |
| * Using this method a client can customize the reader and writer classes used by the load and save operations. Note |
| * that this method must be called before invoking one of the {@code load()} and {@code save()} methods. Especially, if |
| * you want to use a custom {@code IOFactory} for changing the {@code PropertiesReader}, you cannot load the |
| * configuration data in the constructor. |
| * |
| * @param ioFactory the new {@code IOFactory} (must not be <b>null</b>) |
| * @throws IllegalArgumentException if the {@code IOFactory} is <b>null</b> |
| * @since 1.7 |
| */ |
| public void setIOFactory(final IOFactory ioFactory) { |
| if (ioFactory == null) { |
| throw new IllegalArgumentException("IOFactory must not be null."); |
| } |
| |
| this.ioFactory = ioFactory; |
| } |
| |
| /** |
| * Sets the associated layout object. |
| * |
| * @param layout the new layout object; can be <b>null</b>, then a new layout object will be created |
| * @since 1.3 |
| */ |
| public void setLayout(final PropertiesConfigurationLayout layout) { |
| installLayout(layout); |
| } |
| |
| /** |
| * {@inheritDoc} This implementation delegates to the associated layout object which does the actual saving. Note that, |
| * analogous to {@link #read(Reader)}, this method does not do any synchronization. |
| * |
| * @since 2.0 |
| */ |
| @Override |
| public void write(final Writer out) throws ConfigurationException, IOException { |
| getLayout().save(this, out); |
| } |
| |
| } |