1. /*
  2. * @(#)SpinnerDateModel.java 1.7 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.util.*;
  9. import java.io.Serializable;
  10. /**
  11. * A <code>SpinnerModel</code> for sequences of <code>Date</code>s.
  12. * The upper and lower bounds of the sequence are defined by properties called
  13. * <code>start</code> and <code>end</code> and the size
  14. * of the increase or decrease computed by the <code>nextValue</code>
  15. * and <code>previousValue</code> methods is defined by a property
  16. * called <code>calendarField</code>. The <code>start</code>
  17. * and <code>end</code> properties can be <code>null</code> to
  18. * indicate that the sequence has no lower or upper limit.
  19. * <p>
  20. * The value of the <code>calendarField</code> property must be one of the
  21. * <code>java.util.Calendar</code> constants that specify a field
  22. * within a <code>Calendar</code>. The <code>getNextValue</code>
  23. * and <code>getPreviousValue</code>
  24. * methods change the date forward or backwards by this amount.
  25. * For example, if <code>calendarField</code> is <code>Calendar.DAY_OF_WEEK</code>,
  26. * then <code>nextValue</code> produces a <code>Date</code> that's 24
  27. * hours after the current <code>value</code>, and <code>previousValue</code>
  28. * produces a <code>Date</code> that's 24 hours earlier.
  29. * <p>
  30. * The legal values for <code>calendarField</code> are:
  31. * <ul>
  32. * <li><code>Calendar.ERA</code>
  33. * <li><code>Calendar.YEAR</code>
  34. * <li><code>Calendar.MONTH</code>
  35. * <li><code>Calendar.WEEK_OF_YEAR</code>
  36. * <li><code>Calendar.WEEK_OF_MONTH</code>
  37. * <li><code>Calendar.DAY_OF_MONTH</code>
  38. * <li><code>Calendar.DAY_OF_YEAR</code>
  39. * <li><code>Calendar.DAY_OF_WEEK</code>
  40. * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
  41. * <li><code>Calendar.AM_PM</code>
  42. * <li><code>Calendar.HOUR</code>
  43. * <li><code>Calendar.HOUR_OF_DAY</code>
  44. * <li><code>Calendar.MINUTE</code>
  45. * <li><code>Calendar.SECOND</code>
  46. * <li><code>Calendar.MILLISECOND</code>
  47. * </ul>
  48. * <p>
  49. * This model inherits a <code>ChangeListener</code>. The
  50. * <code>ChangeListeners</code> are notified whenever the models
  51. * <code>value</code>, <code>calendarField</code>,
  52. * <code>start</code>, or <code>end</code> properties changes.
  53. *
  54. * @see JSpinner
  55. * @see SpinnerModel
  56. * @see AbstractSpinnerModel
  57. * @see SpinnerListModel
  58. * @see SpinnerNumberModel
  59. * @see Calendar#add
  60. *
  61. * @version 1.7 01/23/03
  62. * @author Hans Muller
  63. * @since 1.4
  64. */
  65. public class SpinnerDateModel extends AbstractSpinnerModel implements Serializable
  66. {
  67. private Comparable start, end;
  68. private Calendar value;
  69. private int calendarField;
  70. private boolean calendarFieldOK(int calendarField) {
  71. switch(calendarField) {
  72. case Calendar.ERA:
  73. case Calendar.YEAR:
  74. case Calendar.MONTH:
  75. case Calendar.WEEK_OF_YEAR:
  76. case Calendar.WEEK_OF_MONTH:
  77. case Calendar.DAY_OF_MONTH:
  78. case Calendar.DAY_OF_YEAR:
  79. case Calendar.DAY_OF_WEEK:
  80. case Calendar.DAY_OF_WEEK_IN_MONTH:
  81. case Calendar.AM_PM:
  82. case Calendar.HOUR:
  83. case Calendar.HOUR_OF_DAY:
  84. case Calendar.MINUTE:
  85. case Calendar.SECOND:
  86. case Calendar.MILLISECOND:
  87. return true;
  88. default:
  89. return false;
  90. }
  91. }
  92. /**
  93. * Creates a <code>SpinnerDateModel</code> that represents a sequence of dates
  94. * between <code>start</code> and <code>end</code>. The
  95. * <code>nextValue</code> and <code>previousValue</code> methods
  96. * compute elements of the sequence by advancing or reversing
  97. * the current date <code>value</code> by the
  98. * <code>calendarField</code> time unit. For a precise description
  99. * of what it means to increment or decrement a <code>Calendar</code>
  100. * <code>field</code>, see the <code>add</code> method in
  101. * <code>java.util.Calendar</code>.
  102. * <p>
  103. * The <code>start</code> and <code>end</code> parameters can be
  104. * <code>null</code> to indicate that the range doesn't have an
  105. * upper or lower bound. If <code>value</code> or
  106. * <code>calendarField</code> is <code>null</code>, or if both
  107. * <code>start</code> and <code>end</code> are specified and
  108. * <code>mininum > maximum</code> then an
  109. * <code>IllegalArgumentException</code> is thrown.
  110. * Similarly if <code>(minimum <= value <= maximum)</code> is false,
  111. * an IllegalArgumentException is thrown.
  112. *
  113. * @param value the current (non <code>null</code>) value of the model
  114. * @param start the first date in the sequence or <code>null</code>
  115. * @param end the last date in the sequence or <code>null</code>
  116. * @param calendarField one of
  117. * <ul>
  118. * <li><code>Calendar.ERA</code>
  119. * <li><code>Calendar.YEAR</code>
  120. * <li><code>Calendar.MONTH</code>
  121. * <li><code>Calendar.WEEK_OF_YEAR</code>
  122. * <li><code>Calendar.WEEK_OF_MONTH</code>
  123. * <li><code>Calendar.DAY_OF_MONTH</code>
  124. * <li><code>Calendar.DAY_OF_YEAR</code>
  125. * <li><code>Calendar.DAY_OF_WEEK</code>
  126. * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
  127. * <li><code>Calendar.AM_PM</code>
  128. * <li><code>Calendar.HOUR</code>
  129. * <li><code>Calendar.HOUR_OF_DAY</code>
  130. * <li><code>Calendar.MINUTE</code>
  131. * <li><code>Calendar.SECOND</code>
  132. * <li><code>Calendar.MILLISECOND</code>
  133. * </ul>
  134. *
  135. * @throws IllegalArgumentException if <code>value</code> or
  136. * <code>calendarField</code> are <code>null</code>,
  137. * if <code>calendarField</code> isn't valid,
  138. * or if the following expression is
  139. * false: <code>(start <= value <= end)</code>.
  140. *
  141. * @see Calendar#add
  142. * @see #setValue
  143. * @see #setStart
  144. * @see #setEnd
  145. * @see #setCalendarField
  146. */
  147. public SpinnerDateModel(Date value, Comparable start, Comparable end, int calendarField) {
  148. if (value == null) {
  149. throw new IllegalArgumentException("value is null");
  150. }
  151. if (!calendarFieldOK(calendarField)) {
  152. throw new IllegalArgumentException("invalid calendarField");
  153. }
  154. if (!(((start == null) || (start.compareTo(value) <= 0)) &&
  155. ((end == null) || (end.compareTo(value) >= 0)))) {
  156. throw new IllegalArgumentException("(start <= value <= end) is false");
  157. }
  158. this.value = Calendar.getInstance();
  159. this.start = start;
  160. this.end = end;
  161. this.calendarField = calendarField;
  162. this.value.setTime(value);
  163. }
  164. /**
  165. * Constructs a <code>SpinnerDateModel</code> whose initial
  166. * <code>value</code> is the current date, <code>calendarField</code>
  167. * is equal to <code>Calendar.DAY_OF_MONTH</code>, and for which
  168. * there are no <code>start</code>/<code>end</code> limits.
  169. */
  170. public SpinnerDateModel() {
  171. this(new Date(), null, null, Calendar.DAY_OF_MONTH);
  172. }
  173. /**
  174. * Changes the lower limit for Dates in this sequence.
  175. * If <code>start</code> is <code>null</code>,
  176. * then there is no lower limit. No bounds checking is done here:
  177. * the new start value may invalidate the
  178. * <code>(start <= value <= end)</code>
  179. * invariant enforced by the constructors. This is to simplify updating
  180. * the model. Naturally one should ensure that the invariant is true
  181. * before calling the <code>nextValue</code>, <code>previousValue</code>,
  182. * or <code>setValue</code> methods.
  183. * <p>
  184. * Typically this property is a <code>Date</code> however it's possible to use
  185. * a <code>Comparable</code> with a <code>compareTo</code> method for Dates.
  186. * For example <code>start</code> might be an instance of a class like this:
  187. * <pre>
  188. * MyStartDate implements Comparable {
  189. * long t = 12345;
  190. * public int compareTo(Date d) {
  191. * return (t < d.getTime() ? -1 : (t == d.getTime() ? 0 : 1));
  192. * }
  193. * public int compareTo(Object o) {
  194. * return compareTo((Date)o);
  195. * }
  196. * }
  197. * </pre>
  198. * Note that the above example will throw a <code>ClassCastException</code>
  199. * if the <code>Object</code> passed to <code>compareTo(Object)</code>
  200. * is not a <code>Date</code>.
  201. * <p>
  202. * This method fires a <code>ChangeEvent</code> if the
  203. * <code>start</code> has changed.
  204. *
  205. * @param start defines the first date in the sequence
  206. * @see #getStart
  207. * @see #setEnd
  208. * @see #addChangeListener
  209. */
  210. public void setStart(Comparable start) {
  211. if ((start == null) ? (this.start != null) : !start.equals(this.start)) {
  212. this.start = start;
  213. fireStateChanged();
  214. }
  215. }
  216. /**
  217. * Returns the first <code>Date</code> in the sequence.
  218. *
  219. * @return the value of the <code>start</code> property
  220. * @see #setStart
  221. */
  222. public Comparable getStart() {
  223. return start;
  224. }
  225. /**
  226. * Changes the upper limit for <code>Date</code>s in this sequence.
  227. * If <code>start</code> is <code>null</code>, then there is no upper
  228. * limit. No bounds checking is done here: the new
  229. * start value may invalidate the <code>(start <= value <= end)</code>
  230. * invariant enforced by the constructors. This is to simplify updating
  231. * the model. Naturally, one should ensure that the invariant is true
  232. * before calling the <code>nextValue</code>, <code>previousValue</code>,
  233. * or <code>setValue</code> methods.
  234. * <p>
  235. * Typically this property is a <code>Date</code> however it's possible to use
  236. * <code>Comparable</code> with a <code>compareTo</code> method for
  237. * <code>Date</code>s. See <code>setStart</code> for an example.
  238. * <p>
  239. * This method fires a <code>ChangeEvent</code> if the <code>end</code>
  240. * has changed.
  241. *
  242. * @param end defines the last date in the sequence
  243. * @see #getEnd
  244. * @see #setStart
  245. * @see #addChangeListener
  246. */
  247. public void setEnd(Comparable end) {
  248. if ((end == null) ? (this.end != null) : !end.equals(this.end)) {
  249. this.end = end;
  250. fireStateChanged();
  251. }
  252. }
  253. /**
  254. * Returns the last <code>Date</code> in the sequence.
  255. *
  256. * @return the value of the <code>end</code> property
  257. * @see #setEnd
  258. */
  259. public Comparable getEnd() {
  260. return end;
  261. }
  262. /**
  263. * Changes the size of the date value change computed
  264. * by the <code>nextValue</code> and <code>previousValue</code> methods.
  265. * The <code>calendarField</code> parameter must be one of the
  266. * <code>Calendar</code> field constants like <code>Calendar.MONTH</code>
  267. * or <code>Calendar.MINUTE</code>.
  268. * The <code>nextValue</code> and <code>previousValue</code> methods
  269. * simply move the specified <code>Calendar</code> field forward or backward
  270. * by one unit with the <code>Calendar.add</code> method.
  271. *
  272. * @param calendarField one of
  273. * <ul>
  274. * <li><code>Calendar.ERA</code>
  275. * <li><code>Calendar.YEAR</code>
  276. * <li><code>Calendar.MONTH</code>
  277. * <li><code>Calendar.WEEK_OF_YEAR</code>
  278. * <li><code>Calendar.WEEK_OF_MONTH</code>
  279. * <li><code>Calendar.DAY_OF_MONTH</code>
  280. * <li><code>Calendar.DAY_OF_YEAR</code>
  281. * <li><code>Calendar.DAY_OF_WEEK</code>
  282. * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
  283. * <li><code>Calendar.AM_PM</code>
  284. * <li><code>Calendar.HOUR</code>
  285. * <li><code>Calendar.HOUR_OF_DAY</code>
  286. * <li><code>Calendar.MINUTE</code>
  287. * <li><code>Calendar.SECOND</code>
  288. * <li><code>Calendar.MILLISECOND</code>
  289. * </ul>
  290. * <p>
  291. * This method fires a <code>ChangeEvent</code> if the
  292. * <code>calendarField</code> has changed.
  293. *
  294. * @see #getCalendarField
  295. * @see #getNextValue
  296. * @see #getPreviousValue
  297. * @see Calendar#add
  298. * @see #addChangeListener
  299. */
  300. public void setCalendarField(int calendarField) {
  301. if (!calendarFieldOK(calendarField)) {
  302. throw new IllegalArgumentException("invalid calendarField");
  303. }
  304. if (calendarField != this.calendarField) {
  305. this.calendarField = calendarField;
  306. fireStateChanged();
  307. }
  308. }
  309. /**
  310. * Returns the <code>Calendar</code> field that is added to or subtracted from
  311. * by the <code>nextValue</code> and <code>previousValue</code> methods.
  312. *
  313. * @return the value of the <code>calendarField</code> property
  314. * @see #setCalendarField
  315. */
  316. public int getCalendarField() {
  317. return calendarField;
  318. }
  319. /**
  320. * Returns the next <code>Date</code> in the sequence, or <code>null</code> if
  321. * the next date is after <code>end</code>.
  322. *
  323. * @return the next <code>Date</code> in the sequence, or <code>null</code> if
  324. * the next date is after <code>end</code>.
  325. *
  326. * @see SpinnerModel#getNextValue
  327. * @see #getPreviousValue
  328. * @see #setCalendarField
  329. */
  330. public Object getNextValue() {
  331. Calendar cal = Calendar.getInstance();
  332. cal.setTime(value.getTime());
  333. cal.add(calendarField, 1);
  334. Date next = cal.getTime();
  335. return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
  336. }
  337. /**
  338. * Returns the previous <code>Date</code> in the sequence, or <code>null</code>
  339. * if the previous date is before <code>start</code>.
  340. *
  341. * @return the previous <code>Date</code> in the sequence, or
  342. * <code>null</code> if the previous date
  343. * is before <code>start</code>
  344. *
  345. * @see SpinnerModel#getPreviousValue
  346. * @see #getNextValue
  347. * @see #setCalendarField
  348. */
  349. public Object getPreviousValue() {
  350. Calendar cal = Calendar.getInstance();
  351. cal.setTime(value.getTime());
  352. cal.add(calendarField, -1);
  353. Date prev = cal.getTime();
  354. return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
  355. }
  356. /**
  357. * Returns the current element in this sequence of <code>Date</code>s.
  358. * This method is equivalent to <code>(Date)getValue</code>.
  359. *
  360. * @return the <code>value</code> property
  361. * @see #setValue
  362. */
  363. public Date getDate() {
  364. return value.getTime();
  365. }
  366. /**
  367. * Returns the current element in this sequence of <code>Date</code>s.
  368. *
  369. * @return the <code>value</code> property
  370. * @see #setValue
  371. * @see #getDate
  372. */
  373. public Object getValue() {
  374. return value.getTime();
  375. }
  376. /**
  377. * Sets the current <code>Date</code> for this sequence.
  378. * If <code>value</code> is <code>null</code>,
  379. * an <code>IllegalArgumentException</code> is thrown. No bounds
  380. * checking is done here:
  381. * the new value may invalidate the <code>(start <= value < end)</code>
  382. * invariant enforced by the constructors. Naturally, one should ensure
  383. * that the <code>(start <= value <= maximum)</code> invariant is true
  384. * before calling the <code>nextValue</code>, <code>previousValue</code>,
  385. * or <code>setValue</code> methods.
  386. * <p>
  387. * This method fires a <code>ChangeEvent</code> if the
  388. * <code>value</code> has changed.
  389. *
  390. * @param value the current (non <code>null</code>)
  391. * <code>Date</code> for this sequence
  392. * @throws IllegalArgumentException if value is <code>null</code>
  393. * or not a <code>Date</code>
  394. * @see #getDate
  395. * @see #getValue
  396. * @see #addChangeListener
  397. */
  398. public void setValue(Object value) {
  399. if ((value == null) || !(value instanceof Date)) {
  400. throw new IllegalArgumentException("null value");
  401. }
  402. if (!value.equals(this.value.getTime())) {
  403. this.value.setTime((Date)value);
  404. fireStateChanged();
  405. }
  406. }
  407. }