Sunday, March 4, 2012

Properties File Wrapper Class

If you're a Java programmer, you know that a properties file is a text file that contains a series of key/value pairs and is often used to store the configuration settings of an application. Each key/value pair resides on its own line. The key comes first, followed by an equals sign, followed by the value. The convention is to organize the key/value pairs into a hierarchy by giving the keys "package-like" names. For example, if I want to store the username, password, and email address of an admin user, I might prepend all of the key names with "admin.", naming the keys "admin.username", "admin.password", and "admin.email". Property files in Java are read using the Properties class.

admin.username=jdoe
admin.password=secret
admin.email=jdoe@company.com
log.file=/home/user/logs/myapp.txt
state.AZ=Arizona
state.TX=Texas
state.MD=Maryland
colors=red,yellow,blue

One technique I like to use when reading properties files in Java is to create a wrapper class whose sole purpose is to read the data from the properties file. A method is created in this class for every key/value pair in the properties file. An advantage to this is that, if the programmer needs to access a property in multiple places throughout the application, the key name (a hard-coded String), need not be duplicated (and potentially misspelled) because it is centralized inside the wrapper class. The wrapper class can also perform extra manipulations on the values in order to "unmarshal" them into more programmer-friendly data structures (for example, converting a file path into a File object or a string of comma-delimited values into a List object).

As an example, let's create a properties wrapper class out of the properties file example shown above.

import java.util.*;
import java.io.*;

public class AppProperties {
  private final Properties props;

  public AppPropeties(Reader reader) throws IOException {
    props = new Properties();
    props.load(reader);
  }

  public String getAdminUsername(){
    return props.getProperty("admin.username");
  }

  public String getAdminPassword(){
    return props.getProperty("admin.password");
  }

  public String getAdminEmail(){
    return props.getProperty("admin.email");
  }

  public File getLogFile(){
    String value = props.getProperty("log.file");
    return (value == null) ? null : new File(value);
  }

  public String getStateName(String abbr){
    return props.getProperty("state." + abbr);
  }

  public List<String> getColors(){
    String value = props.getProperty("colors");
    if (value == null){
      return null;
    }
    String values[] = value.split(",");
    return Arrays.asList(values);
  }
}

The constructor takes a Reader object. This makes the class more flexible and easier to unit test, as it is not limited to reading the property data from a file (for example, a StringReader could be passed into it for unit testing purposes).

The "admin.*" properties each have their own method. For example, to retrieve the "admin.username" field, the getAdminUsername() method is called.

The "log.file" property is converted to a File object because this is what this value represents--a path to a file.

The "getStateName()" method for the "state.*" properties is more dynamic. It takes the state abbreviation as an argument and uses that to construct the key name. For example, if "TX" is passed into this method, the "state.TX" property will be accessed and "Texas" will be returned. Methods like this can be used if you need to retrieve property values based on input from the user.

Lastly, the getColors() method retrieves the value of the "colors" property, which is a comma-delimited string. It then parses the values out of this string and returns a List object.

Using a properties wrapper class like this can really neaten up your codebase. I've used this design pattern in multiple projects and have been happy with the results.

No comments: