The enum Layer
Now let's look at how I implement all of this. I use a two-layer system, where a very lightweight
enum defines the configuration keys, and the bulk of the implementation is delegated to a support class.
This structure makes it a trivial matter to implement your own custom configuration.
More Insights
White Papers
- Transitioning to Multicore Development
- Data Center Flexibility & Efficiency: Increasing the Business Value of IT
Reports
More >>Webcasts
More >>
Starting with the outermost layer, the TestConfiguration is defined in Listing One.
Listing One
package com.holub.testSupport;
import com.holub.util.ConfigurationError;
import com.holub.util.Configurations;
import com.holub.util.CalendarDate;
import com.holub.util.Places;
import java.io.File;
import java.net.URL;
import java.util.Date;
/**
* A class for testing the Configurations class, represents a small configuration
* file that tests all possible configuration types.
*
* @author Allen Holub
*
* <div style='font-size:8pt; margin-top:.25in;'>
* ©2012 <!--copyright 2012--> Allen I Holub. All rights reserved.
* This code is licensed under a variant on the BSD license. View
* the complete text at <a href="http://holub.com/license.html">
* http://holub.com/license.html</a>.
* </div>
*/
public enum TestConfiguration implements Configurations
{
key_string ( String.class, "Doo What Ditty", null),
key_int ( Integer.class, 10, null), // "boxed" to new Integer(10)
key_long ( Long.class, Long.MAX_VALUE - 1, null), // "boxed" to new Long(...)
key_double ( Double.class, 1.23, null), // "boxed" to new Double(1.23)
key_boolean ( Boolean.class, false, null), // "boxed" to new Boolean(...)
key_location( File.class, "/tmp/foo", null), // could also use new File("/tmp/foo").
key_url ( URL.class, "http://www.holub.com",null), // can't catch MalformedURLException from new URL(...),
// so use String. Support constructor creates URL object.
key_dateNow ( CalendarDate.class, CalendarDate.NOW, null),
key_dateThen( CalendarDate.class, "2001-07-04", null),
intBound ( Integer.class, 2, new RangeVerifier(1, 3) ), // Defaults to 2. 1 <= value <= 3;
phoneNumber ( String.class, "(510)555-1212",
new RegExVerifier("(\\(\\d\\d\\d\\))?\\s*\\d\\d\\d[-.]\\d\\d\\d\\d")),
dateBound ( CalendarDate.class, "2001-07-04",
new RangeVerifier( new CalendarDate("2001-01-01"), new CalendarDate("2001-12-31")) ),
intNoDefault( Integer.class, null, null), // no default, must be defined in the file
strNoDefault( String.class, null, null); // no default, must be defined in the file
//----------------------------------------------------------------------
/** The name of the underlying properties file (currently test.properties),
* which must be in a Directory that {@link Places#CONFIG} can find.
*/
public static final File INPUT_FILE = Places.CONFIG.file("test.properties");
//----------------------------------------------------------------------
private static Configurations.Support<TestConfiguration> support = null; //{=TestConfiguration.supportDeclaration}
/** After this call, the first call to any of the value methods (or to
* {@link #load()}) will reload the properties from the underlying file.
*/
public static void reset(){ support = null; }
/** Create the support object only if it doesn't already exist. Can call this
* method at any time, but bear in mind that it can throw an error. To force a load,
* call {@link #reset()}, then call the current method. If you load with this
* method, all enum fields must be present as keys in the properties file, and
* unused keys are not permitted in the file.
*
* @throws ConfigurationError if there are any problems with the configuration file
*/
public static void load()
{ if( support == null )
support = new Configurations.Support<TestConfiguration>( values(), INPUT_FILE, false, false );
}
/** This method exists primarily for testing. You would typically create the support object
* in the correct mode automatically without having the end user decide which mode to use.
*/
public static void load( boolean paramsWithDefaultNotRequiredInPropertiesFile, boolean allowUnusedKeys )
{ if( support == null )
support = new Configurations.Support<TestConfiguration>( values(), INPUT_FILE,
paramsWithDefaultNotRequiredInPropertiesFile, allowUnusedKeys );
}
//----------------------------------------------------------------------
private final Object defaultValue;
private final Verifier verifier;
private final Class<?> type;
private TestConfiguration( Class<?> type, Object defaultValue, Verifier verifier )
{
this.type = type;
this.defaultValue = defaultValue;
this.verifier = verifier;
}
@Override public Object defaultValue(){ return defaultValue; }
@Override public Class<?> type() { return type; }
@Override public Verifier getVerifier() { return verifier; }
//----------------------------------------------------------------------
/** Same as {@link #stringValue()} */
@Override public String toString(){ return stringValue(); }
/** Return the value of the current enum as a String. toString just
* calls this method, but the value-object's toString() method is
* used to create the returned string..
*/
public Object value (){ load(); return support.value(this); }
public String stringValue (){ load(); return support.stringValue(this); }
public Integer intValue (){ load(); return support.intValue(this); }
public Long longValue (){ load(); return support.longValue(this); }
public Double doubleValue (){ load(); return support.doubleValue(this); }
public File fileValue (){ load(); return support.fileValue(this); }
public URL URLValue (){ load(); return support.URLValue(this); }
public Boolean booleanValue(){ load(); return support.booleanValue(this); }
}
The enum
key_string ( String.class, "Doo What Ditty", null),
key_int ( Integer.class, 10, null),
key_long ( Long.class, Long.MAX_VALUE - 1, null),
key_double ( Double.class, 1.23, null),
key_boolean ( Boolean.class, false, null),
key_location( File.class, "/tmp/foo", null),
The properties-file names must be identical to the enumkey_string
key.string= the value
Leading and trailing whitespace on the value side of the equals sign is removed, so you can format the file for readability.
Three constructor arguments are specified for each element.
The first is a ClassStringStringStringURLString
The second argument is the default value, which can be null
intNoDefault( Integer.class, null, null), // no default, must be defined in the file
strNoDefault( String.class, null, null); // no default, must be defined in the file
so something like
intNoDefult=10
strNoDefult=the value
must appear in the underlying properties file.
The object used to specify a default value in the enumintInteger
key_int( Integer.class, 10, null),
to
key_int( Integer.class, new Integer(10), null),
for you.
If the classes don't match exactly, then the default value is
created by calling toString()StringStringkey_location
key_location( File.class, "/tmp/foo", null),
with the default value provided as a StringFile
Object defaultValueArgument = "/tmp/foo";
//...
key_location( File.class, new File( defaultValueArgument.toString() ), null),
except that, when I use a Stringnew File(...)enum
key_url( URL.class, "http://www.holub.com", null),
instead of
key_url( URL.class, new URL("http://www.holub.com"), null),
The problem is that new URL()MalformedUrlExceptionenumenumtrycatch
By using a Stringload()load()trycatchload()MalformedURLExceptionConfigurationError
In the case of the File
key_location( File.class, new File("/tmp/foo"), null ),
because that particular constructor doesn't throw any checked exceptions.


