First of all let me point out, that your code has an error:
if (properties.getProperty("totalnum").equals("null")) { ... }
If the property is not defined, getProperty()
returns null
, thus your code would raise a NullPointerException
when trying to access equals()
. It does not return a string with value "null". That's the case for all your lines.
I'd approach this using reflection, with a config class that declares public
fields that might be annotated with value checks. A method will then set values on an instance of that config class, reading from the properties map.
Further reading:
The advantage of this is that the config shows the valid values in an intuitive, speaking format. The drawback is that the "unmarshalling" code is a bit complex. though this approach is quite powerful.
The config could look like this:
static class Config {
@RegExp("^(one|two)$")
public String number;
public Boolean check;
@Required @Range(min=1, max=6)
public Integer totalnum;
}
If a field is lacking the @Required
annotation, a missing property does not result in an exception, thus the initialization value of Config
is used.
Unmarshalling is done using this:
Config config = new Config();
setProperties(properties, config);
setProperties()
will throw several exceptions when values are missing, have the wrong type or value. The exception can be catched and differentiated to display proper error messages.
In you application you can then access the config like simple objects:
if (config.totalnum == 3) {
// do something when totalnum is 3
}
This is the unmarshalling code:
private void setProperties(Properties properties, Props props) throws SecurityException, IllegalArgumentException, IllegalAccessException {
Class<?> clazz = props.getClass();
for (Field field : clazz.getDeclaredFields()) {
if ((field.getModifiers() & Modifier.PUBLIC) == 0) {
// ignore non-public properties
continue;
}
// the type of the field
Class<?> fieldType = field.getType();
// the field name of the class
String fieldName = field.getName();
// the raw value loaded from the .properties file
String value = properties.getProperty(fieldName);
// fields may be annotated with @Required
boolean required = (field.getAnnotation(Required.class) != null);
if (required && value == null) {
// field required but not defined in the properties, fail
throw new IllegalArgumentException(
String.format(
"Field %s is required",
fieldName
)
);
} else if (value == null) {
// ignore undefined field, default to class initialized value
continue;
}
// checks per type follow ...
if (fieldType == String.class) {
// fields may be annotated with RegExp to specify a matcher
RegExp regExp = field.getAnnotation(RegExp.class);
if (regExp != null && !Pattern.matches(regExp.value(), value)) {
throw new IllegalArgumentException(
String.format(
"Value for field %s does not match %s: %s",
fieldName,
regExp.value(),
value
)
);
}
field.set(props, value);
} else if (fieldType == Integer.class) {
// may throw NumberFormatException if not a valid integer
Integer intValue = Integer.parseInt(value);
// fields may be annotated with Range to specify an integer range
Range range = field.getAnnotation(Range.class);
if (range != null && !(intValue >= range.min() && intValue <= range.max())) {
throw new IllegalArgumentException(
String.format(
"Value for field %s out of range (%d..%d): %d",
fieldName,
range.min(),
range.max(),
intValue
)
);
}
field.set(props, intValue);
} else if (fieldType == Boolean.class) {
// strictly check valid boolean values
if (!Pattern.matches("^(true|false)$", value)) {
throw new IllegalArgumentException(
String.format(
"Value for field %s is not a valid boolean (true|false): %s",
fieldName,
value
)
);
}
field.set(props, Boolean.parseBoolean(value));
}
}
}
Though already quite complex this code is rather simple. It does not handle other number types like Long
or primitive types like int
yet. These can be implemented using further if
branches.
These are the annotations (defined in separate classes):
@Retention(RUNTIME)
@Target(FIELD)
public @interface Range {
public int min() default Integer.MIN_VALUE;
public int max() default Integer.MAX_VALUE;
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface RegExp {
public String value() default "^.*$";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Required {
}