001: package org.springunit.examples;
002:
003: import java.io.Serializable;
004:
005: import org.apache.commons.lang.builder.CompareToBuilder;
006: import org.apache.commons.lang.builder.EqualsBuilder;
007: import org.apache.commons.lang.builder.HashCodeBuilder;
008: import org.apache.commons.lang.builder.ToStringBuilder;
009: import org.apache.commons.lang.builder.ToStringStyle;
010:
011: /**
012: * Simple domain object class to be used for demonstrating
013: * Data Driven Tests.
014: * CompositeDate is composed of a year, month and day of year.
015: * The fact that there are many constraints on the allowed
016: * combinations makes for interesting test cases.
017: * Further, operations like increment and decrement on
018: * each of the fields leads to many other boundary
019: * conditions for which to test.
020: * <br/>
021: * The following are groundrules set by the class:
022: * <ul>
023: * <li>A year may be any positive or negative integer</li>
024: * <li>A month may be any integer between 1 and 12, inclusive</li>
025: * <li>A day may be any integer between 1 and 31, inclusive</li>
026: * </ul>
027: * <br/>
028: * These being preconditions imposed on clients, the class
029: * does not check, for instance,
030: * for days greater than 31, or months greater than 12.
031: * Of course, not all combinations of year, month and day
032: * that satisfy these preconditions are valid. These are
033: * validated internally by the class.
034: *
035: * @author Ted.Velkoff
036: *
037: */
038: public class CompositeDate implements Comparable<CompositeDate>,
039: Serializable {
040:
041: private static final long serialVersionUID = 4537123990358625629L;
042:
043: /**
044: * Create CompositeDate having day, month and year.<br/>
045: * @Pre("1 <= day && day <= 31")
046: * @Pre("1 <= month && month <= 12")
047: * @throws InvalidDateException if day, month and year do
048: * not specify a valid date
049: */
050: public CompositeDate(int year, int month, int day)
051: throws InvalidDateException {
052: assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
053: assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
054: this .day = day;
055: this .month = month;
056: this .year = year;
057: validateDate(year, month, day);
058: }
059:
060: /**
061: * Is this less than, equal to, or greater than that?<br/>
062: * @param that Object to be compared with this.
063: * @return boolean
064: */
065: public int compareTo(CompositeDate that) {
066: return new CompareToBuilder().append(getYear(), that.getYear())
067: .append(getMonth(), that.getMonth()).append(getDay(),
068: that.getDay()).toComparison();
069: }
070:
071: /**
072: * Decrement by one day.<br/>
073: * @return true if field underflow detected, false otherwise
074: * @throws DateUnderflowException if object underflow detected
075: */
076: public boolean decrementDay() throws DateUnderflowException {
077: boolean underflow = false;
078: if (this .day == 1
079: && (this .month == 1 || this .month == 2
080: || this .month == 4 || this .month == 6
081: || this .month == 8 || this .month == 9 || this .month == 11)) {
082: underflow = decrementMonth();
083: this .day = 31;
084: } else if (this .day == 1
085: && (this .month == 5 || this .month == 7
086: || this .month == 10 || this .month == 12)) {
087: underflow = decrementMonth();
088: this .day = 30;
089: } else if (this .day == 1 && this .month == 3) {
090: underflow = decrementMonth();
091: if (this .year % 400 == 0
092: || (this .year % 4 == 0 && this .year % 100 != 0)) {
093: this .day = 29;
094: } else {
095: this .day = 28;
096: }
097: } else {
098: this .day--;
099: }
100: return underflow;
101: }
102:
103: /**
104: * Decrement by one month.<br/>
105: * @return true if field underflow detected, false otherwise
106: * @throws DateUnderflowException if object underflow detected
107: */
108: public boolean decrementMonth() throws DateUnderflowException {
109: boolean underflow = false;
110: if (this .day > 30
111: && (this .month == 5 || this .month == 7
112: || this .month == 10 || this .month == 12)) {
113: this .day = 30;
114: } else if (this .month == 3 && (this .day > 28)) {
115: this .day = 28;
116: }
117: if (this .month == 1) {
118: underflow = decrementYear();
119: this .month = 12;
120: } else {
121: this .month--;
122: }
123: return underflow;
124: }
125:
126: /**
127: * Decrement by one year.
128: * @return true if field underflow detected, false otherwise
129: * @throws DateUnderflowException if object underflow detected
130: */
131: public boolean decrementYear() throws DateUnderflowException {
132: if (this .year == Integer.MIN_VALUE) {
133: throw new DateUnderflowException(
134: "Can't decrement year past -2147483648");
135: }
136: if (this .month == 2 && this .day == 29) {
137: this .day = 28;
138: }
139: this .year--;
140: return false;
141: }
142:
143: /**
144: * Is this equal to obj?<br/>
145: */
146: public boolean equals(Object obj) {
147: if (this == obj) {
148: return true;
149: }
150: if (!(obj instanceof CompositeDate)) {
151: return false;
152: }
153: CompositeDate other = (CompositeDate) obj;
154: return new EqualsBuilder().append(getYear(), other.getYear())
155: .append(getMonth(), other.getMonth()).append(getDay(),
156: other.getDay()).isEquals();
157: }
158:
159: /**
160: * @return Returns the day.
161: */
162: public int getDay() {
163: return this .day;
164: }
165:
166: /**
167: * @return Returns the month.
168: */
169: public int getMonth() {
170: return this .month;
171: }
172:
173: /**
174: * @return Returns the year.
175: */
176: public int getYear() {
177: return this .year;
178: }
179:
180: /**
181: * Hash code.<br/>
182: */
183: public int hashCode() {
184: return new HashCodeBuilder(17, 37).append(getYear()).append(
185: getMonth()).append(getDay()).toHashCode();
186: }
187:
188: /**
189: * Increment by one day.<br/>
190: * @return true if field overflow detected, false otherwise
191: * @throws DateOverflowException if object overflow detected
192: */
193: public boolean incrementDay() throws DateOverflowException {
194: if (this .month == 1 || this .month == 3 || this .month == 5
195: || this .month == 7 || this .month == 8
196: || this .month == 10 || this .month == 12) {
197: this .day = (this .day % 31) + 1;
198: } else if (this .month == 4 || this .month == 6
199: || this .month == 9 || this .month == 11) {
200: this .day = (this .day % 30) + 1;
201: } else if (this .month == 2
202: && (this .year % 400 == 0 || (this .year % 4 == 0 && this .year % 100 != 0))) {
203: this .day = (this .day % 29) + 1;
204: } else {
205: this .day = (this .day % 28) + 1;
206: }
207: boolean overflow = this .day == 1;
208: if (overflow) {
209: return incrementMonth();
210: }
211: return overflow;
212: }
213:
214: /**
215: * Increment by one month.<br/>
216: * @return true if field overflow detected, false otherwise
217: * @throws DateOverflowException if object overflow detected
218: */
219: public boolean incrementMonth() throws DateOverflowException {
220: if (this .month == 1 && this .day > 28) {
221: this .day = 28;
222: } else if ((this .month == 3 || this .month == 5
223: || this .month == 8 || this .month == 10)
224: && this .day > 30) {
225: this .day = 30;
226: }
227: this .month = (this .month % 12) + 1;
228: boolean overflow = this .month == 1;
229: if (overflow) {
230: return incrementYear();
231: }
232: return overflow;
233: }
234:
235: /**
236: * Increment by one year.<br/>
237: * @return true if field overflow detected, false otherwise
238: * @throws DateOverflowException if object overflow detected
239: */
240: public boolean incrementYear() throws DateOverflowException {
241: if (this .year == Integer.MAX_VALUE) {
242: throw new DateOverflowException(
243: "Can't increment year past 2147483647");
244: }
245: if (this .month == 2 && this .day == 29) {
246: this .day = 28;
247: }
248: this .year++;
249: return false;
250: }
251:
252: /**
253: * @param day The day to set.
254: * @Pre("1 <= day && day <= 31")
255: * @throws InvalidDateException if day, month and year do
256: * not specify a valid date
257: */
258: public void setDay(int day) throws InvalidDateException {
259: assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
260: validateDate(this .year, this .month, day);
261: this .day = day;
262: }
263:
264: /**
265: * @param month The month to set.
266: * @Pre("1 <= month && month <= 12")
267: * @throws InvalidDateException if day, month and year do
268: * not specify a valid date
269: */
270: public void setMonth(int month) throws InvalidDateException {
271: assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
272: validateDate(this .year, month, this .day);
273: this .month = month;
274: }
275:
276: /**
277: * @param year The year to set.
278: * @throws InvalidDateException if day, month and year do
279: * not specify a valid date
280: */
281: public void setYear(int year) throws InvalidDateException {
282: validateDate(year, this .month, this .day);
283: this .year = year;
284: }
285:
286: /**
287: * String representation.<br/>
288: */
289: public String toString() {
290: return new ToStringBuilder(this , ToStringStyle.MULTI_LINE_STYLE)
291: .append("day", getDay()).append("month", getMonth())
292: .append("year", getYear()).toString();
293: }
294:
295: /**
296: * Is combination of <code>year</code>, <code>month</code>,
297: * and <code>day</code> valid?<br/>
298: * @Pre("1 <= month && month <= 12")
299: * @Pre("1 <= day && day <= 31")
300: * @return true if valid, false otherwise
301: */
302: public static boolean isValidDate(int year, int month, int day) {
303: return isValidLeapDay(year, month, day)
304: || isValidMonthAndDay(month, day);
305: }
306:
307: /**
308: * Is combination of <code>year</code>, <code>month</code>,
309: * and <code>day</code> a valid leap day?<br/>
310: * @return true if valid, false otherwise
311: */
312: public static boolean isValidLeapDay(int year, int month, int day) {
313: return (day != 29 || month != 2)
314: || ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0));
315:
316: }
317:
318: /**
319: * Is combination of <code>month</code>
320: * and <code>day</code> a valid month/day pair?<br/>
321: * @Pre("1 <= month && month <= 12")
322: * @Pre("1 <= day && day <= 31")
323: * @return true if valid, false otherwise
324: */
325: public static boolean isValidMonthAndDay(int month, int day) {
326: assert 1 <= month && month <= 12 : "1 <= month && month <= 12";
327: assert 1 <= day && day <= 31 : "1 <= day && day <= 31";
328: return !((day > 29 && month == 2) || (day > 30 && (month == 4
329: || month == 6 || month == 9 || month == 11)));
330: }
331:
332: /**
333: * Determine whether the combination of day, month and year
334: * is valid and throw InvalidDateException if not.<br/>
335: */
336: protected static void validateDate(int year, int month, int day)
337: throws InvalidDateException {
338: validateLeapDay(year, month, day);
339: validateMonthAndDay(month, day);
340: }
341:
342: /**
343: * Determine whether the combination of day, month and year
344: * is a leap day and throw InvalidDateException if it is not
345: * a valid leap day.<br/>
346: */
347: protected static void validateLeapDay(int year, int month, int day)
348: throws InvalidDateException {
349: if (!isValidLeapDay(year, month, day)) {
350: throw new InvalidDateException(
351: "February 29 is not valid for the year " + year);
352: }
353: }
354:
355: /**
356: * Determine whether the combination of day, month and year
357: * is a leap day and throw InvalidDateException if it is not
358: * a valid leap day.<br/>
359: */
360: protected static void validateMonthAndDay(int month, int day)
361: throws InvalidDateException {
362: if (!isValidMonthAndDay(month, day)) {
363: throw new InvalidDateException(day
364: + " is not valid for the month " + month);
365: }
366: }
367:
368: private int day;
369: private int month;
370: private int year;
371:
372: }
|