Properties filesProperties 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 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: Configuration config = new PropertiesConfiguration("usergui.properties"); If you do not specify an absolute path, the file will be searched automatically in the following locations:
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")); 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. Here is an example: # usergui.properties include = colors.properties include = 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 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. SavingTo 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 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 delimter 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 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 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(); |