You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/09/18 21:23:56 UTC
[commons-configuration] 05/06: Sort members.
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git
commit bc15cb565eeaf2825b3ae9f7235a5680dc3f0f47
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Sep 18 16:58:51 2020 -0400
Sort members.
---
.../configuration2/PropertiesConfiguration.java | 2200 ++++++++++----------
1 file changed, 1100 insertions(+), 1100 deletions(-)
diff --git a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
index e3d6967..c022ab8 100644
--- a/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/PropertiesConfiguration.java
@@ -206,518 +206,333 @@ public class PropertiesConfiguration extends BaseConfiguration
{
/**
- * 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
- * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
- */
- public static final String DEFAULT_ENCODING = "ISO-8859-1";
-
- /** 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. {@code 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 = new char[] {'=', ':'};
-
- /** The white space characters used as key/value separators. */
- private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
-
- /** Constant for the platform specific line separator.*/
- private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
- /** 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());
- }
-
- /**
- * 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.
+ * A default implementation of the {@code IOFactory} interface.
* </p>
- *
- * @return A String.
- * @since 2.5
- */
- public static String getIncludeOptional()
- {
- return PropertiesConfiguration.includeOptional;
- }
-
- /**
- * 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.
+ * 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>
*
- * @param inc A String.
- * @since 2.5
- */
- public static void setIncludeOptional(final String inc)
- {
- PropertiesConfiguration.includeOptional = inc;
- }
-
- /**
- * 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;
- }
-
- /**
- * Reports the status of file inclusion.
- *
- * @return True if include files are loaded.
+ * @since 1.7
*/
- public boolean isIncludesAllowed()
+ public static class DefaultIOFactory implements IOFactory
{
- return this.includesAllowed;
- }
+ /**
+ * The singleton instance.
+ */
+ static final DefaultIOFactory INSTANCE = new DefaultIOFactory();
- /**
- * Return the comment header.
- *
- * @return the comment header
- * @since 1.1
- */
- public String getHeader()
- {
- beginRead(false);
- try
- {
- return getLayout().getHeaderComment();
- }
- finally
+ @Override
+ public PropertiesReader createPropertiesReader(final Reader in)
{
- endRead();
+ return new PropertiesReader(in);
}
- }
- /**
- * Set 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
+ @Override
+ public PropertiesWriter createPropertiesWriter(final Writer out,
+ final ListDelimiterHandler handler)
{
- endWrite();
+ return new PropertiesWriter(out, handler);
}
}
/**
- * Returns the footer comment. This is a comment at the very end of the
- * file.
+ * <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>
*
- * @return the footer comment
- * @since 2.0
+ * @since 1.7
*/
- public String getFooter()
+ public interface IOFactory
{
- beginRead(false);
- try
- {
- return getLayout().getFooterComment();
- }
- finally
- {
- endRead();
- }
- }
+ /**
+ * 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);
- /**
- * 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();
- }
+ /**
+ * 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);
}
/**
- * Returns the associated layout object.
+ * 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>
*
- * @return the associated layout object
- * @since 1.3
+ * @since 2.4
*/
- public PropertiesConfigurationLayout getLayout()
+ public static class JupIOFactory implements IOFactory
{
- return layout;
- }
- /**
- * 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);
- }
+ /**
+ * 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;
- /**
- * 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)
+ /**
+ * Constructs a new {@link JupIOFactory} with Unicode escaping.
+ */
+ public JupIOFactory()
{
- removeEventListener(ConfigurationEvent.ANY, this.layout);
+ this(true);
}
- if (layout == null)
- {
- this.layout = createLayout();
- }
- else
+ /**
+ * 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.layout = layout;
+ this.escapeUnicode = escapeUnicode;
}
- addEventListener(ConfigurationEvent.ANY, this.layout);
- }
-
- /**
- * 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 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;
- }
-
- /**
- * Returns 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;
- }
-
- /**
- * 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)
+ @Override
+ public PropertiesReader createPropertiesReader(final Reader in)
{
- throw new IllegalArgumentException("includeListener must not be null.");
+ return new JupPropertiesReader(in);
}
- this.includeListener = includeListener;
- }
- /**
- * 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)
+ @Override
+ public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler)
{
- throw new IllegalArgumentException("IOFactory must not be null.");
+ return new JupPropertiesWriter(out, handler, escapeUnicode);
}
- this.ioFactory = ioFactory;
}
/**
- * Stores the current {@code FileLocator} for a following IO operation. The
- * {@code FileLocator} is needed to resolve include files with relative file
- * names.
+ * A {@link PropertiesReader} that tries to mimic the behavior of
+ * {@link java.util.Properties}.
*
- * @param locator the current {@code FileLocator}
- * @since 2.0
+ * @since 2.4
*/
- @Override
- public void initFileLocator(final FileLocator locator)
+ public static class JupPropertiesReader extends PropertiesReader
{
- this.locator = locator;
- }
- /**
- * {@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);
- }
+ /**
+ * Constructor.
+ *
+ * @param reader A Reader.
+ */
+ public JupPropertiesReader(final Reader reader)
+ {
+ super(reader);
+ }
- /**
- * {@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);
- }
- /**
- * Creates a copy of this object.
- *
- * @return the copy
- */
- @Override
- public Object clone()
- {
- final PropertiesConfiguration copy = (PropertiesConfiguration) super.clone();
- if (layout != null)
+ @Override
+ protected void parseProperty(final String line)
{
- copy.setLayout(new PropertiesConfigurationLayout(layout));
+ final String[] property = doParseProperty(line, false);
+ initPropertyName(property[0]);
+ initPropertyValue(property[1]);
+ initPropertySeparator(property[2]);
}
- return copy;
- }
-
- /**
- * 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
- {
- boolean result;
- if (StringUtils.isNotEmpty(getInclude())
- && key.equalsIgnoreCase(getInclude()))
+ @Override
+ public String readProperty() throws IOException
{
- if (isIncludesAllowed())
+ getCommentLines().clear();
+ final StringBuilder buffer = new StringBuilder();
+
+ while (true)
{
- final Collection<String> files =
- getListDelimiterHandler().split(value, true);
- for (final String f : files)
+ String line = readLine();
+ if (line == null)
{
- loadIncludeFile(interpolate(f), false, seenStack);
+ // EOF
+ if (buffer.length() > 0)
+ {
+ break;
+ }
+ return null;
}
- }
- 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)
+ // 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))
{
- loadIncludeFile(interpolate(f), true, seenStack);
+ 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))
+ {
+ line = line.substring(0, line.length() - 1);
+ buffer.append(line);
+ }
+ else
+ {
+ buffer.append(line);
+ break;
}
}
- result = false;
+ return buffer.toString();
}
- else
+ @Override
+ protected String unescapePropertyValue(final String value)
{
- addPropertyInternal(key, value);
- result = true;
+ return unescapeJava(value, true);
}
- return result;
}
- /**
- * 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();
- // blanc lines are also treated as comment lines
- return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
- }
+ /**
+ * 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);
+
+ 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;
+ }
- /**
- * 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 valueString;
+ });
}
- return bsCount;
}
/**
@@ -743,99 +558,70 @@ public class PropertiesConfiguration extends BaseConfiguration
/** 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;
-
/**
- * Constructor.
+ * 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 reader A Reader.
+ * @param line the line
+ * @return a flag if the lines should be combined
*/
- public PropertiesReader(final Reader reader)
+ static boolean checkCombineLines(final String line)
{
- super(reader);
- commentLines = new ArrayList<>();
+ return countTrailingBS(line) % 2 != 0;
}
/**
- * 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
+ * Parse a property line and return the key, the value, and the separator in an
+ * array.
*
- * @throws IOException in case of an I/O error
+ * @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
*/
- public String readProperty() throws IOException
+ static String[] doParseProperty(final String line, final boolean trimValue)
{
- commentLines.clear();
- final StringBuilder buffer = new StringBuilder();
+ final Matcher matcher = PROPERTY_PATTERN.matcher(line);
- while (true)
+ final String[] result = {"", "", ""};
+
+ if (matcher.matches())
{
- String line = readLine();
- if (line == null)
- {
- // EOF
- return null;
- }
+ result[0] = matcher.group(IDX_KEY).trim();
- if (isCommentLine(line))
+ String value = matcher.group(IDX_VALUE);
+ if (trimValue)
{
- commentLines.add(line);
- continue;
+ value = value.trim();
}
+ result[1] = value;
- line = line.trim();
-
- if (checkCombineLines(line))
- {
- line = line.substring(0, line.length() - 1);
- buffer.append(line);
- }
- else
- {
- buffer.append(line);
- break;
- }
+ result[2] = matcher.group(IDX_SEPARATOR);
}
- return buffer.toString();
+
+ return result;
}
+ /** 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;
+
/**
- * 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>).
+ * Constructor.
*
- * @return a flag if further properties are available
- * @throws IOException if an error occurs
- * @since 1.3
+ * @param reader A Reader.
*/
- public boolean nextProperty() throws IOException
+ public PropertiesReader(final Reader reader)
{
- final String line = readProperty();
-
- if (line == null)
- {
- return false; // EOF
- }
-
- // parse the line
- parseProperty(line);
- return true;
+ super(reader);
+ commentLines = new ArrayList<>();
}
/**
@@ -864,6 +650,19 @@ public class PropertiesConfiguration extends BaseConfiguration
}
/**
+ * Returns 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;
+ }
+
+ /**
* Returns 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>.
@@ -877,16 +676,70 @@ public class PropertiesConfiguration extends BaseConfiguration
}
/**
- * Returns 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.
+ * 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.
*
- * @return the separator for the last read property
+ * @param name the name of the current property
* @since 1.7
*/
- public String getPropertySeparator()
+ protected void initPropertyName(final String name)
{
- return propertySeparator;
+ 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;
}
/**
@@ -908,17 +761,50 @@ public class PropertiesConfiguration extends BaseConfiguration
}
/**
- * 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.
+ * 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>})
*
- * @param name the name of the current property
- * @since 1.7
+ * @return A string containing a property value or null
+ *
+ * @throws IOException in case of an I/O error
*/
- protected void initPropertyName(final String name)
+ public String readProperty() throws IOException
{
- propertyName = unescapePropertyName(name);
+ 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))
+ {
+ line = line.substring(0, line.length() - 1);
+ buffer.append(line);
+ }
+ else
+ {
+ buffer.append(line);
+ break;
+ }
+ }
+ return buffer.toString();
}
/**
@@ -934,20 +820,6 @@ public class PropertiesConfiguration extends BaseConfiguration
}
/**
- * 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);
- }
-
- /**
* Performs unescaping on the given property value.
*
* @param value the property value
@@ -958,63 +830,6 @@ public class PropertiesConfiguration extends BaseConfiguration
{
return unescapeJava(value);
}
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
} // class PropertiesReader
/**
@@ -1065,218 +880,40 @@ public class PropertiesConfiguration extends BaseConfiguration
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;
- }
-
- /**
- * Returns 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;
- }
-
- /**
- * Returns the current property separator.
- *
- * @return the current property separator
- * @since 1.7
- */
- public String getCurrentSeparator()
- {
- return currentSeparator;
- }
-
- /**
- * 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;
- }
-
- /**
- * Returns the global property separator.
- *
- * @return the global property separator
- * @since 1.7
- */
- public String getGlobalSeparator()
- {
- return globalSeparator;
- }
-
- /**
- * 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;
- }
-
- /**
- * Returns the line separator.
- *
- * @return the line separator
- * @since 1.7
- */
- public String getLineSeparator()
- {
- return lineSeparator != null ? lineSeparator : LINE_SEPARATOR;
- }
-
- /**
- * 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 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);
- }
-
- /**
- * 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 (int i = 0; i < values.size(); i++)
- {
- writeProperty(key, values.get(i));
- }
- }
-
- /**
- * 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;
+ private String currentSeparator;
- if (value instanceof List)
- {
- v = null;
- final List<?> values = (List<?>) value;
- if (forceSingleLine)
- {
- try
- {
- v = String.valueOf(getDelimiterHandler()
- .escapeList(values, valueTransformer));
- }
- catch (final UnsupportedOperationException uoex)
- {
- // 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));
- }
+ /** The global separator. If set, it overrides the current separator.*/
+ private String globalSeparator;
- write(escapeKey(key));
- write(fetchSeparator(key, value));
- write(v);
+ /** The line separator.*/
+ private String lineSeparator;
- writeln(null);
+ /**
+ * 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);
}
/**
- * Write a comment.
+ * Creates a new instance of {@code PropertiesWriter}.
*
- * @param comment the comment to write
- * @throws IOException if an I/O error occurs
+ * @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 void writeComment(final String comment) throws IOException
+ public PropertiesWriter(final Writer writer, final ListDelimiterHandler delHandler,
+ final ValueTransformer valueTransformer)
{
- writeln("# " + comment);
+ super(writer);
+ delimiterHandler = delHandler;
+ this.valueTransformer = valueTransformer;
}
/**
@@ -1313,23 +950,6 @@ public class PropertiesConfiguration extends BaseConfiguration
}
/**
- * 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());
- }
-
- /**
* 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
@@ -1349,336 +969,364 @@ public class PropertiesConfiguration extends BaseConfiguration
return getGlobalSeparator() != null ? getGlobalSeparator()
: StringUtils.defaultString(getCurrentSeparator());
}
- } // class PropertiesWriter
- /**
- * <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.
+ * Returns the current property separator.
*
- * @param in the underlying reader (of the properties file)
- * @return the {@code PropertiesReader} for loading the
- * configuration
+ * @return the current property separator
+ * @since 1.7
*/
- PropertiesReader createPropertiesReader(Reader in);
+ public String getCurrentSeparator()
+ {
+ return currentSeparator;
+ }
/**
- * 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.
+ * Returns 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.
*
- * @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
+ * @return the delimiter handler for properties with multiple values
+ * @since 2.0
*/
- PropertiesWriter createPropertiesWriter(Writer out,
- ListDelimiterHandler handler);
- }
+ public ListDelimiterHandler getDelimiterHandler()
+ {
+ return delimiterHandler;
+ }
- /**
- * <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.
+ * Returns the global property separator.
+ *
+ * @return the global property separator
+ * @since 1.7
*/
- static final DefaultIOFactory INSTANCE = new DefaultIOFactory();
-
- @Override
- public PropertiesReader createPropertiesReader(final Reader in)
+ public String getGlobalSeparator()
{
- return new PropertiesReader(in);
+ return globalSeparator;
}
- @Override
- public PropertiesWriter createPropertiesWriter(final Writer out,
- final ListDelimiterHandler handler)
+ /**
+ * Returns the line separator.
+ *
+ * @return the line separator
+ * @since 1.7
+ */
+ public String getLineSeparator()
{
- return new PropertiesWriter(out, handler);
+ return lineSeparator != null ? lineSeparator : LINE_SEPARATOR;
}
- }
- /**
- * 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
- {
+ /**
+ * 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;
+ }
/**
- * 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.
+ * 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
*/
- private final boolean escapeUnicode;
+ public void setGlobalSeparator(final String globalSeparator)
+ {
+ this.globalSeparator = globalSeparator;
+ }
/**
- * Constructs a new {@link JupIOFactory} with Unicode escaping.
+ * 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 JupIOFactory()
+ public void setLineSeparator(final String lineSeparator)
{
- this(true);
+ this.lineSeparator = lineSeparator;
}
/**
- * 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.
+ * Write a comment.
*
- * @param escapeUnicode whether Unicode characters should be escaped
+ * @param comment the comment to write
+ * @throws IOException if an I/O error occurs
*/
- public JupIOFactory(final boolean escapeUnicode)
+ public void writeComment(final String comment) throws IOException
{
- this.escapeUnicode = escapeUnicode;
+ writeln("# " + comment);
}
- @Override
- public PropertiesReader createPropertiesReader(final Reader in)
+ /**
+ * 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
{
- return new JupPropertiesReader(in);
+ if (s != null)
+ {
+ write(s);
+ }
+ write(getLineSeparator());
}
- @Override
- public PropertiesWriter createPropertiesWriter(final Writer out, final ListDelimiterHandler handler)
+ /**
+ * 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
{
- return new JupPropertiesWriter(out, handler, escapeUnicode);
+ for (int i = 0; i < values.size(); i++)
+ {
+ writeProperty(key, values.get(i));
+ }
}
- }
-
- /**
- * A {@link PropertiesReader} that tries to mimic the behavior of
- * {@link java.util.Properties}.
- *
- * @since 2.4
- */
- public static class JupPropertiesReader extends PropertiesReader
- {
-
/**
- * Constructor.
+ * Write a property.
*
- * @param reader A Reader.
+ * @param key the key of the property
+ * @param value the value of the property
+ *
+ * @throws IOException if an I/O error occurs
*/
- public JupPropertiesReader(final Reader reader)
+ public void writeProperty(final String key, final Object value) throws IOException
{
- super(reader);
+ writeProperty(key, value, false);
}
-
- @Override
- public String readProperty() throws IOException
+ /**
+ * 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
{
- getCommentLines().clear();
- final StringBuilder buffer = new StringBuilder();
+ String v;
- while (true)
+ if (value instanceof List)
{
- String line = readLine();
- if (line == null)
+ v = null;
+ final List<?> values = (List<?>) value;
+ if (forceSingleLine)
{
- // EOF
- if (buffer.length() > 0)
+ try
{
- break;
+ v = String.valueOf(getDelimiterHandler()
+ .escapeList(values, valueTransformer));
}
- 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++)
+ catch (final UnsupportedOperationException uoex)
{
- if (!Character.isWhitespace(line.charAt(i)))
- {
- break;
- }
+ // the handler may not support escaping lists,
+ // then the list is written in multiple lines
}
-
- line = line.substring(i);
- }
-
- if (checkCombineLines(line))
- {
- line = line.substring(0, line.length() - 1);
- buffer.append(line);
}
- else
+ if (v == null)
{
- buffer.append(line);
- break;
+ writeProperty(key, values);
+ return;
}
}
- return buffer.toString();
- }
+ else
+ {
+ v = String.valueOf(getDelimiterHandler().escape(value, valueTransformer));
+ }
- @Override
- protected void parseProperty(final String line)
- {
- final String[] property = doParseProperty(line, false);
- initPropertyName(property[0]);
- initPropertyValue(property[1]);
- initPropertySeparator(property[2]);
+ write(escapeKey(key));
+ write(fetchSeparator(key, value));
+ write(v);
+
+ writeln(null);
}
+ } // class PropertiesWriter
- @Override
- protected String unescapePropertyValue(final String value)
+ /**
+ * 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
+ * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
+ */
+ public static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+ /** 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. {@code 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 = new char[] {'=', ':'};
+
+ /** The white space characters used as key/value separators. */
+ private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
+
+ /** Constant for the platform specific line separator.*/
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ /** 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;
+
+ /**
+ * 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--)
{
- return unescapeJava(value, true);
+ 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();
+ // blanc lines are also treated as comment lines
+ return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
}
/**
- * A {@link PropertiesWriter} that tries to mimic the behavior of
- * {@link java.util.Properties}.
+ * 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.
*
- * @since 2.4
+ * @param ch the character in question
+ * @return a flag whether this character has to be unescaped
*/
- public static class JupPropertiesWriter extends PropertiesWriter
+ private static boolean needsUnescape(final char ch)
{
+ return UNESCAPE_CHARACTERS.indexOf(ch) >= 0;
+ }
- /**
- * 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);
-
- 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;
- });
- }
+ /**
+ * 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;
}
/**
@@ -1795,39 +1443,199 @@ public class PropertiesConfiguration extends BaseConfiguration
out.append(ch);
}
- continue;
- }
- else if (ch == '\\')
- {
- hadSlash = true;
- continue;
- }
- out.append(ch);
- }
+ continue;
+ }
+ else 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();
+ }
+
+ /** 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());
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Returns 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();
+ }
+ }
+
+ /**
+ * Return 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;
+ }
+
+ /**
+ * Returns 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;
+ }
+
+ /**
+ * Returns 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;
+ }
- if (hadSlash)
+ /**
+ * 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)
{
- // then we're in the weird case of a \ at the end of the
- // string, let's output it anyway.
- out.append('\\');
+ removeEventListener(ConfigurationEvent.ANY, this.layout);
}
- return out.toString();
+ if (layout == null)
+ {
+ this.layout = createLayout();
+ }
+ else
+ {
+ this.layout = layout;
+ }
+ addEventListener(ConfigurationEvent.ANY, this.layout);
}
/**
- * 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.
+ * Reports the status of file inclusion.
*
- * @param ch the character in question
- * @return a flag whether this character has to be unescaped
+ * @return True if include files are loaded.
*/
- private static boolean needsUnescape(final char ch)
+ public boolean isIncludesAllowed()
{
- return UNESCAPE_CHARACTERS.indexOf(ch) >= 0;
+ return this.includesAllowed;
}
/**
@@ -1926,4 +1734,196 @@ public class PropertiesConfiguration extends BaseConfiguration
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
+ {
+ 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();
+ }
+ }
+
+ /**
+ * Set 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);
+ }
+
}