Properties files
Properties files are a popular means of configuring applications. Of course, Commons Configuration
supports this format and enhances significantly the basic java.util.Properties
class.
This section introduces the features of the
PropertiesConfiguration
class.
Note that PropertiesConfiguration
is a very typical example
for an implementation of the Configuration
interface; it
extends
AbstractConfiguration
,
thus all the features provided by this base class are available here as
well. More information about functionality common to all standard
Configuration
implementations can be found in the section
Basic features and AbstractConfiguration.
Using PropertiesConfiguration
Let's start with a simple properties file named
usergui.properties
with the following content:
# Properties definining the GUI
colors.background = #FFFFFF
colors.foreground = #000080
window.width = 500
window.height = 300
To load this file, you'll write something like:
Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("usergui.properties"));
try
{
Configuration config = builder.getConfiguration();
...
}
catch(ConfigurationException cex)
{
// loading of the configuration file failed
}
As is demonstrated by this example, a configuration object for a
properties file is obtained via a
FileBasedConfigurationBuilder
as described in the section
File-based Configurations.
After the properties file was loaded you can access its content through
the methods of the Configuration
interface, e.g.
String backColor = config.getString("colors.background");
Dimension size = new Dimension(config.getInt("window.width"),
config.getInt("window.height"));
Includes
If a property is named "include
", and the value of that property is the
name of a file on the disk, that file will be included into the configuration.
The difference between "include
" and "includeOptional
" (below) is that if the file value
is absent, processing continues with "includeOptional
" but stops with "include
".
For example:
# usergui.properties
include = colors.properties
include = sizes.properties
# colors.properties
colors.background = #FFFFFF
Oprional Includes
If a property is named "includeOptional
", and the value of that property is the
name of a file on the disk, that file will be included into the configuration.
The difference between "include
" (above) and "includeOptional
" is that if the file value
is absent, processing continues with "includeOptional
" but stops with "include
".
For example:
# usergui.properties
includeOptional = colors.properties
includeOptional = sizes.properties
# colors.properties
colors.background = #FFFFFF
Lists and arrays
As was already pointed out in the section
List handling
of Basic features, Commons Configuration has the ability to
return easily a list of values. For example, a properties file can
contain a list of comma separated values:
# chart colors
colors.pie = #FF0000, #00FF00, #0000FF
Provided that an appropriate
ListDelimiterHandler
object was set for the
configuration instance, the value is split automatically, and
you can retrieve an array or a java.util.List
directly with:
String[] colors = config.getStringArray("colors.pie");
List<Object> colorList = config.getList("colors.pie");
Splitting of string values at list delimiter characters is disabled
by default. It can be enabled by specifying an instance of
DefaultListDelimiterHandler
. This can be done when loading
the configuration via the builder:
Parameters params = new Parameters();
FileBasedConfigurationBuilder<Configuration> builder =
new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("usergui.properties")
.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
Configuration config = builder.getConfiguration();
Alternatively, you can specify a list of values in your properties file by using
the same key on several lines as shown in the following example. This is an
example of a feature not provided by java.util.Properties
:
# chart colors
colors.pie = #FF0000;
colors.pie = #00FF00;
colors.pie = #0000FF;
All of the features related to list handling described for
AbstractConfiguration
also apply to properties files,
including changing the list delimiter or disabling list handling at
all.
Saving
To save your configuration, just call the save()
method
on the associated configuration builder.
Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("usergui.properties")
.setListDelimiterHandler(new DefaultListDelimiterHandler(',')));
Configuration config = builder.getConfiguration();
config.setProperty("colors.background", "#000000");
builder.save();
More information about saving properties files (and file-based
configurations in general) can be found in the section about
File-based Configurations.
Special Characters and Escaping
If you need a special character in a property like a line feed, a tabulation or
an unicode character, you can specify it with the same escaped notation used for
Java Strings. The list separator ("," by default), can also be escaped:
key = This \n string \t contains \, escaped \\ characters \u0020
When dealing with lists of elements that contain backslash characters
(e.g. file paths on Windows systems) escaping rules can become pretty
complex. The first thing to keep in mind is that in order to get a
single backslash, you have to write two:
config.dir = C:\\Temp\\
This issue is not specific to Commons Configuration, but is related to
the standard format for properties files. Refer to the Javadocs of the
load()
method of java.util.Properties
for more
information. Now if you want to define a list with file paths, you may
be tempted to write the following:
# Wrong way to define a list of directories
config.dirs = C:\\Temp\\,D:\\data\\
As the comment indicates, this will not work. The trailing backslash of
the first directory is interpreted as escape character for the list
delimiter. So instead of a list with two elements only a single value
of the property is defined - clearly not what was desired. To get a
correct list the trailing backslash has to be escaped. This is achieved
by duplicating it (yes, in a properties file that means that we now need
4 backslashes):
# Correct way to define a list of directories
config.dirs = C:\\Temp\\\\,D:\\data\\
So a sequence of 4 backslashes in the value of a property is interpreted
as an escaped backslash and eventually results in a single backslash.
This creates another problem when a properties file should refer to the
names of network shares. Typically these names start with two
backslashes, so the obvious way to define such a property is as follows:
# Wrong way to define a list of network shares
config.dirs = \\\\share1,\\\\share2
Unfortunately, this will not work because the shares contain the reserved
sequence of 4 backslashes. So when reading the value of the
config.dirs property a list with two elements is returned
starting only with a single backslash. To fix the problem the sequence
for escaping a backslash has to be duplicated - we are now at 8
backslashes:
# Correct way to define a list of network shares
config.dirs = \\\\\\\\share1,\\\\\\\\share2
As becomes obvious, escape sequences can become pretty complex and
unreadable. In such situations it is recommended to use the alternative
way of defining a list: just use the same key multiple times. In this
case no additional escaping of backslashes (beyond the usual duplicating
required by properties files) is needed because there is no list
delimiter character involved. Using this syntax the list of network
shares looks like the following:
# Straightforward way to define a list of network shares
config.dirs = \\\\share1
config.dirs = \\\\share2
Please also refer to the Javadocs of the
DefaultListDelimiterHandler
class; it describes the
escaping rules to be applied in detail.
Layout Objects
Each PropertiesConfiguration
object is associated with a
Layout object, an instance of the class
PropertiesConfigurationLayout
. This layout object is
responsible for preserving most of the structure of loaded configuration
files. This means that things like comments or blank lines in a saved
properties file will closely resemble the original properties file
(the algorithm is not 100 percent perfect, but for most use cases it
should be sufficient).
Normally a developer does not have to deal with these layout objects.
However, there are some methods that might be of interest if enhanced
control over the output of properties files is needed. The following
list describes these methods (note that corresponding get methods are
of course also provided):
setComment()
With this method a comment can be set for a specified property. When
storing the configuration the comment is output before the property,
followed by a line break. The comment can span multiple lines; in this
case the newline character "\n" must be used as line
separator.
setHeaderComment()
With setHeaderComment()
a global comment can be set for the
properties file. This comment is written at the very start of the file,
followed by an empty line.
setFooterComment()
Analogous to setHeaderComment()
, but the comment defined by
this method is written at the very end of the properties file.
setBlancLinesBefore()
This methods allows defining the number of empty lines to be written
before the specified property. It can be used, for instance, to
divide the properties file into multiple logical sections.
setSingleLine()
If a property has multiple values, with setSingleLine()
it
can be specified that all these values should be written into a single
line separated by the default list separator. It is also possible to
write multiple definitions for this property (i.e. multiple lines of the
form property = value1
, property = value2
etc.).
This is supported by PropertiesConfiguration
, but will
probably not work when processing the properties file with other tools.
setForceSingleLine()
This is similar to setSingleLine()
, but sets a global
single line flag. If set to true, all properties with multiple
values are always written on a single line.
setGlobalSeparator()
Sometimes it may be necessary to define the properties separator, i.e.
the string that separates the property key from the value. This can be
done using setGlobalSeparator()
. Here an arbitrary string
can be specified that will be used as separator. (Note: In order to
produce valid properties files only the characters =
and
:
should be used as separators (with or without leading or
trailing whitespace), but the method does not enforce this.
setSeparator()
This method is similar to setGlobalSeparator()
, but
allows setting the property separator for a specific property.
setLineSeparator()
Using this method the line separator can be specified. Per default the
platform-specific line separator is used (e.g. \n
on Unix).
The default settings of PropertiesConfigurationLayout
are
chosen in a way that most of the original layout of a properties file
is retained. With the methods listed above specific layout restrictions
can be enforced.
Custom properties readers and writers
There are situations when more control over the process of reading and
writing properties files is needed. For instance, an application might
have to deal with some legacy properties file in a specific format,
which is not supported out of the box by
PropertiesConfiguration
, but must not be modified. In these
cases it is possible to inject a custom reader and writer for
properties files.
Per default properties files are read and written by the nested classes
PropertiesReader
and PropertiesWriter
(defined within PropertiesConfiguration
). These classes are
regular reader and writer classes (both are derived from typical base
classes of the java.io
package) that provide some
additional methods making dealing with properties files more
convenient. Custom implementations of properties readers and writers
must extend these base classes.
For installing a custom properties reader or writer
PropertiesConfiguration
provides the IOFactory
interface (which is also defined as a nested class). An object
implementing this interface is stored in each
PropertiesConfiguration
instance. Whenever a properties
file is to be read or written (i.e. when one of the load()
or save()
methods is called), the IOFactory
object is asked for creating the properties reader or writer to be
used.
The IOFactory
interface is pretty simple; it defines one
method for creating a properties reader and another one for creating a
properties writer. A default implementation called
DefaultIOFactory
is also available and is used by
PropertiesConfiguration
when no specific
IOFactory
is set.
The original goal of PropertiesConfiguration
wasn't to be
strictly compatible with the exact file format defined in
java.util.Properties
(JUP). However, in cases where this
compatibility is required, the alternative JupIOFactory
can
be used, as it aims to mimic the exact behavior of JUP
. The
main differences concern the handling of leading and trailing whitespace
and the handling of escape sequences. JupIOFactory
can also
be configured to avoid Unicode escape sequences (like \u00DF) when
the used encoding already supports all characters natively. E.g. UTF-8
is the new default encoding for resource bundle properties files since
Java 9, so Unicode escapes are not required anymore and not using them
can make properties files much more readable in regular text editors.
To make this discussion more concrete we provide an example of how to
inject a custom properties reader. The use case is that we have to load
a properties file that contains keys with whitespace, which is not
supported by PropertiesConfiguration
per default. A
fragment from such a properties file could look as follows:
Background Color = #800080
Foreground Color = #000080
The first step is to create a custom properties reader implementation
that can deal with such properties. The class is derived from
PropertiesConfiguration.PropertiesReader
and overrides the
parseProperty()
method:
public class WhitespacePropertiesReader extends PropertiesConfiguration.PropertiesReader
{
public WhitespacePropertiesReader(Reader in, char delimiter)
{
super(in, delimiter);
}
/**
* Special algorithm for parsing properties keys with whitespace. This
* method is called for each non-comment line read from the properties
* file.
*/
@Override
protected void parseProperty(String line)
{
// simply split the line at the first '=' character
// (this should be more robust in production code)
int pos = line.indexOf('=');
String key = line.substring(0, pos).trim();
String value = line.substring(pos + 1).trim();
// now store the key and the value of the property
initPropertyName(key);
initPropertyValue(value);
}
}
Notice the calls to the methods initPropertyName()
and
initPropertyValue()
. Here the results of the parsing
operation are stored. The next step is to provide a specialized
implementation of the IOFactory
interface that returns
the new properties reader class. As we only want to replace the
properties reader (and use the standard writer), we can derive our
implementation from DefaultIOFactory
and thus only have
to override the createPropertiesReader()
method.
public class WhitespaceIOFactory extends PropertiesConfiguration.DefaultIOFactory
{
/**
* Return our special properties reader.
*/
@Override
public PropertiesReader createPropertiesReader(Reader in, char delimiter)
{
return new WhitespacePropertiesReader(in, delimiter);
}
}
Finally an instance of our new IOFactory
implementation
has to be created and passed to the PropertiesConfiguration
object. This can be done via the initialization parameters passed to
the configuration builder:
Parameters params = new Parameters();
FileBasedConfigurationBuilder<Configuration> builder =
new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("myfile.properties")
.setIOFactory(new WhitespaceIOFactory());
Configuration config = builder.getConfiguration();
Builder Configuration Related to Properties Files
When setting up a configuration builder to produce a
PropertiesConfiguration
instance typically an object
implementing the
PropertiesBuilderParameters
interface is used. In addition
to the parameters common to all file-based configurations, there are
settings specific to properties files which are defined by the
PropertiesBuilderProperties
interface. These include
- A flag whether include files are supported.
This is true by default, but can be switched off if
properties named include should not have a special meaning.
- A custom layout object.
- A custom I/O
factory.
A parameters object for a properties configuration can be obtained using
the properties()
method of a
Parameters
instance