package org.springunit.examples;
import java.io.Serializable;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* Simple domain object class to be used for demonstrating
* Data Driven Tests.
* CompositeDate is composed of a year, month and day of year.
* The fact that there are many constraints on the allowed
* combinations makes for interesting test cases.
* Further, operations like increment and decrement on
* each of the fields leads to many other boundary
* conditions for which to test.
* <br/>
* The following are groundrules set by the class:
* <ul>
* <li>A year may be any positive or negative integer</li>
* <li>A month may be any integer between 1 and 12, inclusive</li>
* <li>A day may be any integer between 1 and 31, inclusive</li>
* </ul>
* <br/>
* These being preconditions imposed on clients, the class
* does not check, for instance,
* for days greater than 31, or months greater than 12.
* Of course, not all combinations of year, month and day
* that satisfy these preconditions are valid. These are
* validated internally by the class.
*
* @author Ted.Velkoff
*
*/
public class CompositeDate implements Comparable<CompositeDate>, Serializable {
private static final long serialVersionUID = 4537123990358625629L;
/**
* Create CompositeDate having day, month and year.<br/>
* @Pre("1 <= day && day <= 31")
* @Pre("1 <= month && month <= 12")
* @throws InvalidDateException if day, month and year do
* not specify a valid date
*/
public CompositeDate(int year, int month, int day) throws InvalidDateException {
assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
this.day = day;
this.month = month;
this.year = year;
validateDate(year, month, day);
}
/**
* Is this less than, equal to, or greater than that?<br/>
* @param that Object to be compared with this.
* @return boolean
*/
public int compareTo(CompositeDate that) {
return new CompareToBuilder().
append(getYear(), that.getYear()).
append(getMonth(), that.getMonth()).
append(getDay(), that.getDay()).
toComparison();
}
/**
* Decrement by one day.<br/>
* @return true if field underflow detected, false otherwise
* @throws DateUnderflowException if object underflow detected
*/
public boolean decrementDay() throws DateUnderflowException {
boolean underflow = false;
if (this.day == 1 && (this.month == 1 || this.month == 2 || this.month == 4 || this.month == 6 || this.month == 8 || this.month == 9 || this.month == 11)) {
underflow = decrementMonth();
this.day = 31;
}
else if (this.day == 1 && (this.month == 5 || this.month == 7 || this.month == 10 || this.month == 12)) {
underflow = decrementMonth();
this.day = 30;
}
else if (this.day == 1 && this.month == 3) {
underflow = decrementMonth();
if (this.year % 400 == 0 || (this.year % 4 == 0 && this.year % 100 != 0)) {
this.day = 29;
}
else {
this.day = 28;
}
}
else {
this.day--;
}
return underflow;
}
/**
* Decrement by one month.<br/>
* @return true if field underflow detected, false otherwise
* @throws DateUnderflowException if object underflow detected
*/
public boolean decrementMonth() throws DateUnderflowException {
boolean underflow = false;
if (this.day > 30 && (this.month == 5 || this.month == 7 || this.month == 10 || this.month == 12)) {
this.day = 30;
}
else if (this.month == 3 && (this.day > 28)) {
this.day = 28;
}
if (this.month == 1) {
underflow = decrementYear();
this.month = 12;
}
else {
this.month--;
}
return underflow;
}
/**
* Decrement by one year.
* @return true if field underflow detected, false otherwise
* @throws DateUnderflowException if object underflow detected
*/
public boolean decrementYear() throws DateUnderflowException {
if (this.year == Integer.MIN_VALUE) {
throw new DateUnderflowException("Can't decrement year past -2147483648");
}
if (this.month == 2 && this.day == 29) {
this.day = 28;
}
this.year--;
return false;
}
/**
* Is this equal to obj?<br/>
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CompositeDate)) {
return false;
}
CompositeDate other = (CompositeDate) obj;
return new EqualsBuilder().
append(getYear(), other.getYear()).
append(getMonth(), other.getMonth()).
append(getDay(), other.getDay()).
isEquals();
}
/**
* @return Returns the day.
*/
public int getDay() {
return this.day;
}
/**
* @return Returns the month.
*/
public int getMonth() {
return this.month;
}
/**
* @return Returns the year.
*/
public int getYear() {
return this.year;
}
/**
* Hash code.<br/>
*/
public int hashCode() {
return new HashCodeBuilder(17, 37).
append(getYear()).
append(getMonth()).
append(getDay()).
toHashCode();
}
/**
* Increment by one day.<br/>
* @return true if field overflow detected, false otherwise
* @throws DateOverflowException if object overflow detected
*/
public boolean incrementDay() throws DateOverflowException {
if (this.month == 1 || this.month == 3 || this.month == 5 || this.month == 7 || this.month == 8 || this.month == 10 || this.month == 12) {
this.day = (this.day % 31) + 1;
}
else if (this.month == 4 || this.month == 6 || this.month == 9 || this.month == 11) {
this.day = (this.day % 30) + 1;
}
else if (this.month == 2 && (this.year % 400 == 0 || (this.year % 4 == 0 && this.year % 100 != 0))) {
this.day = (this.day % 29) + 1;
}
else {
this.day = (this.day % 28) + 1;
}
boolean overflow = this.day == 1;
if (overflow) {
return incrementMonth();
}
return overflow;
}
/**
* Increment by one month.<br/>
* @return true if field overflow detected, false otherwise
* @throws DateOverflowException if object overflow detected
*/
public boolean incrementMonth() throws DateOverflowException {
if (this.month == 1 && this.day > 28) {
this.day = 28;
}
else if ((this.month == 3 || this.month == 5 || this.month == 8 || this.month == 10) && this.day > 30) {
this.day = 30;
}
this.month = (this.month % 12) + 1;
boolean overflow = this.month == 1;
if (overflow) {
return incrementYear();
}
return overflow;
}
/**
* Increment by one year.<br/>
* @return true if field overflow detected, false otherwise
* @throws DateOverflowException if object overflow detected
*/
public boolean incrementYear() throws DateOverflowException {
if (this.year == Integer.MAX_VALUE) {
throw new DateOverflowException("Can't increment year past 2147483647");
}
if (this.month == 2 && this.day == 29) {
this.day = 28;
}
this.year++;
return false;
}
/**
* @param day The day to set.
* @Pre("1 <= day && day <= 31")
* @throws InvalidDateException if day, month and year do
* not specify a valid date
*/
public void setDay(int day) throws InvalidDateException {
assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
validateDate(this.year, this.month, day);
this.day = day;
}
/**
* @param month The month to set.
* @Pre("1 <= month && month <= 12")
* @throws InvalidDateException if day, month and year do
* not specify a valid date
*/
public void setMonth(int month) throws InvalidDateException {
assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
validateDate(this.year, month, this.day);
this.month = month;
}
/**
* @param year The year to set.
* @throws InvalidDateException if day, month and year do
* not specify a valid date
*/
public void setYear(int year) throws InvalidDateException {
validateDate(year, this.month, this.day);
this.year = year;
}
/**
* String representation.<br/>
*/
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).
append("day", getDay()).
append("month", getMonth()).
append("year", getYear()).
toString();
}
/**
* Is combination of <code>year</code>, <code>month</code>,
* and <code>day</code> valid?<br/>
* @Pre("1 <= month && month <= 12")
* @Pre("1 <= day && day <= 31")
* @return true if valid, false otherwise
*/
public static boolean isValidDate(int year, int month, int day) {
return isValidLeapDay(year, month, day) || isValidMonthAndDay(month, day);
}
/**
* Is combination of <code>year</code>, <code>month</code>,
* and <code>day</code> a valid leap day?<br/>
* @return true if valid, false otherwise
*/
public static boolean isValidLeapDay(int year, int month, int day) {
return (day != 29 || month != 2) || ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0));
}
/**
* Is combination of <code>month</code>
* and <code>day</code> a valid month/day pair?<br/>
* @Pre("1 <= month && month <= 12")
* @Pre("1 <= day && day <= 31")
* @return true if valid, false otherwise
*/
public static boolean isValidMonthAndDay(int month, int day) {
assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
return !((day > 29 && month == 2) || (day > 30 && (month == 4 || month == 6 || month == 9 || month == 11)));
}
/**
* Determine whether the combination of day, month and year
* is valid and throw InvalidDateException if not.<br/>
*/
protected static void validateDate(int year, int month, int day) throws InvalidDateException {
validateLeapDay(year, month, day);
validateMonthAndDay(month, day);
}
/**
* Determine whether the combination of day, month and year
* is a leap day and throw InvalidDateException if it is not
* a valid leap day.<br/>
*/
protected static void validateLeapDay(int year, int month, int day) throws InvalidDateException {
if (!isValidLeapDay(year, month, day)) {
throw new InvalidDateException("February 29 is not valid for the year " + year);
}
}
/**
* Determine whether the combination of day, month and year
* is a leap day and throw InvalidDateException if it is not
* a valid leap day.<br/>
*/
protected static void validateMonthAndDay(int month, int day) throws InvalidDateException {
if (!isValidMonthAndDay(month, day)) {
throw new InvalidDateException(day + " is not valid for the month " + month);
}
}
private int day;
private int month;
private int year;
}
|