Properties filesProperties 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 PropertiesConfigurationLet'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")); IncludesIf 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 IncludesIf 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 arraysAs 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. SavingTo 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 EscapingIf 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 ObjectsEach 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):
Custom properties readers and writersThere 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. 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 FilesWhen 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 parameters object for a properties configuration can be obtained using the properties() method of a Parameters instance |