I have what seems to be a pretty good solution, please let me know if you see any problems with this.
I wrote a class TouchableDate which extends java.util.Date (see below). This class un-deprecates the getters/setters from Date and tracks which properties have been set.
I set a new TouchableDate() on my formBackingObject, then I implemented onBindAndValidate to test whether the properties were accessed:
Code:
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors) throws Exception {
Donation donation = (Donation) command;
TouchableDate tDate = (TouchableDate) donation.getCreditCard().getExpirationDate();
if (!tDate.hasMonth() || !tDate.hasYear()) {
errors.rejectValue("creditCard.expirationDate", "empty", "Expiration date is required");
} else {
tDate.setDate(1);
}
}
This lets me determine whether a value has been set or not, as opposed to a regular Date object which cannot have empty/invalid values (month, for example).
My binding looks the same, e.g.:
Code:
<spring:bind path="command.creditCard.expirationDate.month">
The only thing I wonder is whether spring:bind is using reflection to see that expirationDate is actually a TouchableDate, and therefore has a getMonth method, or whether spring:bind is using the deprecated Date.getMonth() method.
Any other thoughts?
Code:
import java.util.Calendar;
import java.util.Date;
/**
* A special Date object that un-deprecates the getXXX, setXXX methods from Date and
* returns -1 for properties that haven't been set. Use hasXXX methods to determine
* if a property has been set.Uses a Calendar internally to set properties correctly.
*
* @author Ryan Carver
*/
public class TouchableDate extends Date {
public static final int NOT_SET = -1;
private Calendar c;
private boolean hasHours = false;
private boolean hasMinutes = false;
private boolean hasSeconds = false;
private boolean hasDate = false;
private boolean hasDay = false;
private boolean hasMonth = false;
private boolean hasYear = false;
//~ Constructors
public TouchableDate() {
super();
init();
}
public TouchableDate(long arg0) {
super(arg0);
init();
}
//~ Internal
private void init() {
c = Calendar.getInstance();
c.setTime(this);
}
private void setProperty(int code, int value) {
c.set(code, value);
}
private int getProperty(int code) {
return c.get(code);
}
//~ Behavior
public long getTime() {
return c.getTimeInMillis();
}
//~ Setters
public int getHours() {
return (hasHours) ? getProperty(Calendar.HOUR_OF_DAY) : NOT_SET;
}
public int getMinutes() {
return (hasMinutes) ? getProperty(Calendar.MINUTE) : NOT_SET;
}
public int getSeconds() {
return (hasSeconds) ? getProperty(Calendar.SECOND) : NOT_SET;
}
public int getDay() {
return (hasDay) ? getProperty(Calendar.DAY_OF_WEEK) : NOT_SET;
}
public int getDate() {
return (hasDate) ? getProperty(Calendar.DAY_OF_MONTH) : NOT_SET;
}
public int getMonth() {
return (hasMonth) ? getProperty(Calendar.MONTH) : NOT_SET;
}
public int getYear() {
return (hasYear) ? getProperty(Calendar.YEAR) : NOT_SET;
}
//~ Setters
public void setHours(int hours) {
hasHours = true;
setProperty(Calendar.HOUR_OF_DAY, hours);
}
public void setMinutes(int minutes) {
hasMinutes = true;
setProperty(Calendar.MINUTE, minutes);
}
public void setSeconds(int seconds) {
hasSeconds = true;
setProperty(Calendar.SECOND, seconds);
}
public void setDay(int day) {
hasDay = true;
setProperty(Calendar.DAY_OF_WEEK, day);
}
public void setDate(int date) {
hasDate = true;
setProperty(Calendar.DAY_OF_MONTH, date);
}
public void setMonth(int months) {
hasMonth = true;
setProperty(Calendar.MONTH, months);
}
public void setYear(int year) {
hasYear = true;
setProperty(Calendar.YEAR, year);
}
//~ Boolean checkers
public boolean hasDate() {
return hasDate;
}
public boolean hasDay() {
return hasDay;
}
public boolean hasHours() {
return hasHours;
}
public boolean hasMinutes() {
return hasMinutes;
}
public boolean hasMonth() {
return hasMonth;
}
public boolean hasSeconds() {
return hasSeconds;
}
public boolean hasYear() {
return hasYear;
}
}