By Mitch Stuart
Copyright © 2005 FullSpan Software
-
Usage subject to license
Software Version: 1.0
-
Document Version: $Revision: 1.3 $, $Date: 2005/04/10 06:04:51 $
Java property files are commonly used for messages and other text strings. In this document, we will first briefly discuss the motivation for using property files, and then describe how PropKeyConst can make using them safer and more convenient.
Getting PropKeyConst
Property files are often used to hold text messages. As an example, let's say that we have a Java program that needs to check a password, and if it is incorrect, display the message "Incorrect password". For simplicity, we'll assume that we have a method called displayErr that shows the error message. There are several ways that we could store the error message text.
1. Hardcoded String
The most primitive way is to just use a hardcoded string:The problems with this are:displayErr("Incorrect password");
- It is embedded in the code and difficult to change.
- If the same message is used in multiple places, it is tedious to find and replace all the occurrences.
- To customize or internationalize the message requires access to the source code and a custom build of the class file(s). This causes distribution and maintenance headaches.
2. String Constant
The next option is to use a string constant:This is better than a hardcoded string because it is centralized, but it still has the other problems mentioned above.public static final String ERR_INCORRECT_PASSWORD = "Incorrect password"; . . . displayErr(ERR_INCORRECT_PASSWORD);3. Property File
The next option is to store the messages in a property file. You would have a file, saymymessages.properties, with a line like this:In your code you need to load the properties. There are various ways to do this. One example is:err_incorrect_password=Incorrect passwordThen when you need the text of the message, you would use code like this:ResourceBundle bundle = ResourceBundle.getBundle("com/mycompany/myapp/resource/mymessages");This solves all of the problems we identified earlier. The messages are centralized and separated from the code, so they can easily be customized and internationalized.displayErr(bundle.getString("err_incorrect_password"));
Property Key Mismatch
Using property files is convenient, but there is one major problem with the example above: it uses a hardcoded string as the property key to retrieve the message: bundle.getString("err_incorrect_password"). What if there is a typo in our Java code, or if someone removes or changes the key in the property file but forgets to update the Java file? We will not be able to retrieve the message, so the user will not be properly informed of the error. Furthermore, we do not have any way to detect this "property key mismatch" between property file and Java code condition at compile or build time. It can only be discovered at runtime.
Theoretically, our application test suite should provide 100% code coverage, so we should find this problem during testing. But in reality, very few tests suites provide this level of coverage, particularly for unexpected or environmental conditions (out of disk space, network down, etc.). Thus, the problem may never be discovered until it happens in production. Furthermore, the type of error that is not easily simulated during testing is exactly the type of error where you want to give the best error reporting to the user.
Notice that the property key mismatch cannot be detected by simply introducing string constants. Compare this code:
String msg = bundle.getString("err_incorrect_password");
to this:
public static final String PROPKEY_ERR_INCORRECT_PASSWORD = "err_incorrect_password";
. . .
String msg = bundle.getString(PROPKEY_ERR_INCORRECT_PASSWORD);
You might prefer the second approach, but it does not solve the property key mismatch vulnerability. There is still no build-time checking that the property key is valid.
Avoiding Property Key Mismatch
PropKeyConst solves the property key mismatch problem by generating a Java class to hold your property keys. By using this Java class, you are guaranteed at compile time that the properties you use in your program actually exist in the property file.
ConstantGenerator class to parse the property file and generate a Java file with constants for each property in the file. Finally, you write your Java code to use the property keys.
For example, say you have a property file entry like this:
err_incorrect_password=Incorrect password
PropKeyConst will generate a constant like this:
public static final String ERR_INCORRECT_PASSWORD = "err_incorrect_password";
Then in your Java code, instead of writing:
String msg = bundle.getString("err_incorrect_password");
You write:
String msg = bundle.getString(MyPropKeys.ERR_INCORRECT_PASSWORD);Because the Java class
MyPropKeys is generated at build time from your property file, you are guaranteed that every property key constant within the class refers to an actual property within the property file.
ConstantGenerator class is just a normal Java class that you can run in any way that you wish: in an Ant script; with a shell script or batch file; or inside of an IDE like Eclipse. The important thing is that your build sequence must be:
ConstantGenerator on the property file to generate the property key class. This has to be done after the above step, because the properties need to be in the property file before ConstantGenerator can read them. java -jar lib/propkeyconst.jar
src/jgenerated/com/fullspan/propkeyexample/simple
com.fullspan.propkeyexample.simple
SimpleExamplePropkeys
src/java/com/fullspan/propkeyexample/simple/resource/SimpleExample.properties
This example shows the following:
ConstantGenerator is the Main-Class defined for propkeyconst.jar, so if you "run" propkeyconst.jar, the ConstantGenerator class is the one that will be run. You can see this by running the following at the command line: java -jar propkeyconst.jar.ConstantGenerator takes four parameters. In order, they are:
.java file will be created.SimpleExample.properties file, because this was the fourth parameter in the above invocation.SimpleExamplePropkeys.java, because this was the third parameter.SimpleExamplePropkeys class in the package com.fullspan.propkeyexample.simple, because this was the second parameter.SimpleExamplePropkeys.java in the directory .../jgenerated/com/fullspan/propkeyexample/simple, because this was the first parameter.
Multiple Files
After the four required parameters to ConstantGenerator, you can also specify additional file names if you have more than one property file for which you want to generate constants. All of the property files specified in a single invocation of ContantGenerator will go into the same generated Java class. If you have multiple property files and you want separate constant key classes, simply run ConstantGenerator multiple times, specifying different input file name(s) and output class names each time.
ConstantGenerator using Ant. This example is taken from the propkeyexample/build.xml file included with the PropKeyConst distribution:
<target name="generatePropKeys" depends="init" unless="upToDate.SimpleExamplePropkeys">
<java jar="${dir.lib}/propkeyconst.jar"
fork="true"
failonerror="true">
<arg value="${dir.src}/jgenerated/com/fullspan/propkeyexample/simple" />
<arg value="com.fullspan.propkeyexample.simple" />
<arg value="SimpleExamplePropkeys" />
<arg value="${dir.src}/java/com/fullspan/propkeyexample/simple/resource/SimpleExample.properties" />
</java>
</target>
This is identical to the Command Line example shown earlier, except that it uses the Ant sytax for invocation and parameters.
Notice that the generatePropKeys target in the above example has the following condition: unless="upToDate.SimpleExamplePropkeys".
This unless condition tells Ant that it can skip the generatePropKeys step if the property keys are already up to date. The upToDate.SimpleExamplePropkeys property is set earlier in the build.xml file:
<uptodate property="upToDate.SimpleExamplePropkeys"
srcfile="${dir.src}/java/com/fullspan/propkeyexample/simple/resource/SimpleExample.properties"
targetfile="${dir.src}/jgenerated/com/fullspan/propkeyexample/simple/SimpleExamplePropkeys.java" />
This test simply compares the timestamps of the .properties file and the .java file. If the .properties file has a timestamp less than or equal to that of the .java file, then the .java file is up to date, and the generatePropKeys target does not need to be executed.
When you edit the .properties file (say, adding a new property or editing the text of an existing one), and then run Ant, the uptodate check will fail. The .java file will be regenerated from the revised .properties file, and then the .class file will be compiled from the .java file.
You can view the project properties for the propkeyexample project to see how to enable this automation for your own project. The steps are:
max_free_logins=3
form.login.title=Login
Then if you look in the generated SimpleExamplePropkeys.java, you see code like this (condensed here for brevity):
public final class SimpleExamplePropkeys
{
public static final String MAX_FREE_LOGINS = "max_free_logins";
public static final class FORM
{
public static final class LOGIN
{
public static final String TITLE = "form.login.title";
}
}
}
You can see that the property key max_free_logins results in the constant SimpleExamplePropkeys.MAX_FREE_LOGINS.
But notice the property key form.login.title. It results in the constant TITLE. This constant is nested within two inner classes, namely FORM and LOGIN. So in your code when you refer to the property key form.login.title, you would use the constant SimpleExamplePropkeys.FORM.LOGIN.TITLE, just as you would expect.
This mapping from dotted property keys to inner classes has several benefits:
SimpleExamplePropkeys) to minimize typing.# or ! as their first non-whitespace character. It is common to document the property file to explain the usage of various properties. This works fine when the file is manually edited, but when the file is read in, processed, and written out, there is no simple or standard way to maintain the comments. For example, the method java.util.Properties.load ignores comment lines. Similarly, the method java.util.Properties.store does not provide any way to emit comments into the property file it creates.
PropKeyConst provides the ability to read and write property file comments. This provides several benefits:
ConstantGenerator creates the Java file containing the property key constants, it also also includes the comments from the property file as JavaDoc comments for each class and variable. Therefore the comments flow from the property file to the generated Java file, and then to the JavaDoc documentation created from the Java file. When you are browsing the JavaDoc for your property keys, not only can you see the hierarchy of property keys, but you can also see the comments for each key.
Comment Syntax
Comments for PropKeyConst are specified using the same key=value syntax as normal properties in the property file. There are two defined suffixes that can be used on property keys to indicate to PropKeyConst that the entries are comments, as opposed to "normal" properties. The suffixes are:
@classcomment denotes a comment for a generated PropKeyConst class: either the top-level (outermost) class that contains all the constants for a given run of ConstantGenerator, or an inner class within the top-level class.@comment denotes a comment for a property key.
Example Comments
Looking in the example SimpleExample.properties, you see lines like this (condensed here for brevity):
@classcomment=Property key constants for the Music Manager application
form@classcomment=Titles, field labels, validation data, etc. for \
forms and fields
form.login.field.confirm_new_password@comment=For confirming new password \
when changing existing user account
form.login.field.confirm_new_password=Confirm New Password
Then if you look in the generated SimpleExamplePropkeys.java, you see code like this (condensed here for brevity):
/**
* Property key constants for the Music Manager application
*/
public final class SimpleExamplePropkeys
{
/**
* Titles, field labels, validation data, etc. for forms and fields
*/
public static final class FORM
{
public static final class LOGIN
{
public static final class FIELD
{
/**
* For confirming new password when changing existing user account
*/
public static final String CONFIRM_NEW_PASSWORD = "form.login.field.confirm_new_password";
}
}
}
}
Notice that the @comment and @classcomment property values have been converted to JavaDoc comment text.
If you look in the generated JavaDoc for SimpleExamplePropkeys, you can see the resulting JavaDoc output.
Comment Retention Example
The file PropertyFileGeneratorTests.java shows an example of reading in an existing property file and saving it as a new file, with (PropKeyConst-style) comments retained. Notice that the constants are emitted in alphabetical order, ensuring that comments stay next to their property keys, and related properties stay together.
a.b.3.d=fooa.b."c".d=foo
The second known limitation is that the comment text for a PropKeyConst-style comment (using the @comment or @classcomment syntax) must be valid to appear within a Java comment. The reason for this limitation is that PropKeyConst directly copies the comment text into a Java comment block in the generated Java file. Here is an example of an invalid comment:
max_free_logins@comment=Maximum number of free logins */ before registering
because the */ within the comment will terminate the Java comment and cause a syntax error when the generated file is compiled.