Properties files
Properties files are a popular mean 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 and
many of the features described in this section (e.g. list handling or
interpolation) are supported by other configuration classes as well.
This is because most configuration implementations that ship with
Commons Configuration are derived from the common base class
AbstractConfiguration
,
which implements these features.
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:
Configuration config = new PropertiesConfiguration("usergui.properties");
If you do not specify an absolute path, the file will be searched automatically
in the following locations:
- in the current directory
- in the user home directory
- in the classpath
Instead of using a constructor that takes a file name you can also
invoke one of the load()
methods. There are several
overloaded variants that allow you to load properties from various
sources. More information about loading properties files (and file-based
configurations in general) can be found in the section about
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. Here is
an example:
# usergui.properties
include = colors.properties
include = 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
You don't have to split the value manually, 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");
Alternatively, you can specify a list of values in your properties file by using
the same key on several lines:
# 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:
PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setProperty("colors.background", "#000000");
config.save();
You can also save a copy of the configuration to another file:
PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
config.setProperty("colors.background", "#000000");
config.save("usergui.backup.properties);
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
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.
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 file 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. 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.
*/
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.
*/
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 must be done before the load()
method is
called. So we cannot use one of the constructors that load the data.
The code for setting up the configuration object could look as follows:
PropertiesConfiguration config = new PropertiesConfiguration();
config.setIOFactory(new WhitespaceIOFactory());
config.setFile(...); // set the file to be loaded
config.load();