/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing;
import java.util.*;
import java.io.Serializable;
/**
* A SpinnerModel for sequences of Dates.
* The upper and lower bounds of the sequence are defined by properties called
* start and end and the size
* of the increase or decrease computed by the nextValue
* and previousValue methods is defined by a property
* called calendarField. The start
* and end properties can be null to
* indicate that the sequence has no lower or upper limit.
*
* The value of the calendarField property must be one of the
* java.util.Calendar constants that specify a field
* within a Calendar. The getNextValue
* and getPreviousValue
* methods change the date forward or backwards by this amount.
* For example, if calendarField is Calendar.DAY_OF_WEEK,
* then nextValue produces a Date that's 24
* hours after the current value, and previousValue
* produces a Date that's 24 hours earlier.
*
* The legal values for calendarField are:
*
Calendar.ERA
* Calendar.YEAR
* Calendar.MONTH
* Calendar.WEEK_OF_YEAR
* Calendar.WEEK_OF_MONTH
* Calendar.DAY_OF_MONTH
* Calendar.DAY_OF_YEAR
* Calendar.DAY_OF_WEEK
* Calendar.DAY_OF_WEEK_IN_MONTH
* Calendar.AM_PM
* Calendar.HOUR
* Calendar.HOUR_OF_DAY
* Calendar.MINUTE
* Calendar.SECOND
* Calendar.MILLISECOND
*
* This model inherits a ChangeListener. The
* ChangeListeners are notified whenever the models
* value, calendarField,
* start, or end properties changes.
*
* @see JSpinner
* @see SpinnerModel
* @see AbstractSpinnerModel
* @see SpinnerListModel
* @see SpinnerNumberModel
* @see Calendar#add
*
* @author Hans Muller
* @since 1.4
*/
public class SpinnerDateModel extends AbstractSpinnerModel implements Serializable
{
private Comparable start, end;
private Calendar value;
private int calendarField;
private boolean calendarFieldOK(int calendarField) {
switch(calendarField) {
case Calendar.ERA:
case Calendar.YEAR:
case Calendar.MONTH:
case Calendar.WEEK_OF_YEAR:
case Calendar.WEEK_OF_MONTH:
case Calendar.DAY_OF_MONTH:
case Calendar.DAY_OF_YEAR:
case Calendar.DAY_OF_WEEK:
case Calendar.DAY_OF_WEEK_IN_MONTH:
case Calendar.AM_PM:
case Calendar.HOUR:
case Calendar.HOUR_OF_DAY:
case Calendar.MINUTE:
case Calendar.SECOND:
case Calendar.MILLISECOND:
return true;
default:
return false;
}
}
/**
* Creates a SpinnerDateModel that represents a sequence of dates
* between start and end. The
* nextValue and previousValue methods
* compute elements of the sequence by advancing or reversing
* the current date value by the
* calendarField time unit. For a precise description
* of what it means to increment or decrement a Calendar
* field, see the add method in
* java.util.Calendar.
*
* The start and end parameters can be
* null to indicate that the range doesn't have an
* upper or lower bound. If value or
* calendarField is null, or if both
* start and end are specified and
* minimum > maximum then an
* IllegalArgumentException is thrown.
* Similarly if (minimum <= value <= maximum) is false,
* an IllegalArgumentException is thrown.
*
* @param value the current (non null) value of the model
* @param start the first date in the sequence or null
* @param end the last date in the sequence or null
* @param calendarField one of
*
Calendar.ERA
* Calendar.YEAR
* Calendar.MONTH
* Calendar.WEEK_OF_YEAR
* Calendar.WEEK_OF_MONTH
* Calendar.DAY_OF_MONTH
* Calendar.DAY_OF_YEAR
* Calendar.DAY_OF_WEEK
* Calendar.DAY_OF_WEEK_IN_MONTH
* Calendar.AM_PM
* Calendar.HOUR
* Calendar.HOUR_OF_DAY
* Calendar.MINUTE
* Calendar.SECOND
* Calendar.MILLISECOND
* value or
* calendarField are null,
* if calendarField isn't valid,
* or if the following expression is
* false: (start <= value <= end).
*
* @see Calendar#add
* @see #setValue
* @see #setStart
* @see #setEnd
* @see #setCalendarField
*/
public SpinnerDateModel(Date value, Comparable start, Comparable end, int calendarField) {
if (value == null) {
throw new IllegalArgumentException("value is null");
}
if (!calendarFieldOK(calendarField)) {
throw new IllegalArgumentException("invalid calendarField");
}
if (!(((start == null) || (start.compareTo(value) <= 0)) &&
((end == null) || (end.compareTo(value) >= 0)))) {
throw new IllegalArgumentException("(start <= value <= end) is false");
}
this.value = Calendar.getInstance();
this.start = start;
this.end = end;
this.calendarField = calendarField;
this.value.setTime(value);
}
/**
* Constructs a SpinnerDateModel whose initial
* value is the current date, calendarField
* is equal to Calendar.DAY_OF_MONTH, and for which
* there are no start/end limits.
*/
public SpinnerDateModel() {
this(new Date(), null, null, Calendar.DAY_OF_MONTH);
}
/**
* Changes the lower limit for Dates in this sequence.
* If start is null,
* then there is no lower limit. No bounds checking is done here:
* the new start value may invalidate the
* (start <= value <= end)
* invariant enforced by the constructors. This is to simplify updating
* the model. Naturally one should ensure that the invariant is true
* before calling the nextValue, previousValue,
* or setValue methods.
*
* Typically this property is a Date however it's possible to use
* a Comparable with a compareTo method for Dates.
* For example start might be an instance of a class like this:
*
* MyStartDate implements Comparable {
* long t = 12345;
* public int compareTo(Date d) {
* return (t < d.getTime() ? -1 : (t == d.getTime() ? 0 : 1));
* }
* public int compareTo(Object o) {
* return compareTo((Date)o);
* }
* }
*
* Note that the above example will throw a ClassCastException
* if the Object passed to compareTo(Object)
* is not a Date.
*
* This method fires a ChangeEvent if the
* start has changed.
*
* @param start defines the first date in the sequence
* @see #getStart
* @see #setEnd
* @see #addChangeListener
*/
public void setStart(Comparable start) {
if ((start == null) ? (this.start != null) : !start.equals(this.start)) {
this.start = start;
fireStateChanged();
}
}
/**
* Returns the first Date in the sequence.
*
* @return the value of the start property
* @see #setStart
*/
public Comparable getStart() {
return start;
}
/**
* Changes the upper limit for Dates in this sequence.
* If start is null, then there is no upper
* limit. No bounds checking is done here: the new
* start value may invalidate the (start <= value <= end)
* invariant enforced by the constructors. This is to simplify updating
* the model. Naturally, one should ensure that the invariant is true
* before calling the nextValue, previousValue,
* or setValue methods.
*
* Typically this property is a Date however it's possible to use
* Comparable with a compareTo method for
* Dates. See setStart for an example.
*
* This method fires a ChangeEvent if the end
* has changed.
*
* @param end defines the last date in the sequence
* @see #getEnd
* @see #setStart
* @see #addChangeListener
*/
public void setEnd(Comparable end) {
if ((end == null) ? (this.end != null) : !end.equals(this.end)) {
this.end = end;
fireStateChanged();
}
}
/**
* Returns the last Date in the sequence.
*
* @return the value of the end property
* @see #setEnd
*/
public Comparable getEnd() {
return end;
}
/**
* Changes the size of the date value change computed
* by the nextValue and previousValue methods.
* The calendarField parameter must be one of the
* Calendar field constants like Calendar.MONTH
* or Calendar.MINUTE.
* The nextValue and previousValue methods
* simply move the specified Calendar field forward or backward
* by one unit with the Calendar.add method.
* You should use this method with care as some UIs may set the
* calendarField before committing the edit to spin the field under
* the cursor. If you only want one field to spin you can subclass
* and ignore the setCalendarField calls.
*
* @param calendarField one of
*
Calendar.ERA
* Calendar.YEAR
* Calendar.MONTH
* Calendar.WEEK_OF_YEAR
* Calendar.WEEK_OF_MONTH
* Calendar.DAY_OF_MONTH
* Calendar.DAY_OF_YEAR
* Calendar.DAY_OF_WEEK
* Calendar.DAY_OF_WEEK_IN_MONTH
* Calendar.AM_PM
* Calendar.HOUR
* Calendar.HOUR_OF_DAY
* Calendar.MINUTE
* Calendar.SECOND
* Calendar.MILLISECOND
*
* This method fires a ChangeEvent if the
* calendarField has changed.
*
* @see #getCalendarField
* @see #getNextValue
* @see #getPreviousValue
* @see Calendar#add
* @see #addChangeListener
*/
public void setCalendarField(int calendarField) {
if (!calendarFieldOK(calendarField)) {
throw new IllegalArgumentException("invalid calendarField");
}
if (calendarField != this.calendarField) {
this.calendarField = calendarField;
fireStateChanged();
}
}
/**
* Returns the Calendar field that is added to or subtracted from
* by the nextValue and previousValue methods.
*
* @return the value of the calendarField property
* @see #setCalendarField
*/
public int getCalendarField() {
return calendarField;
}
/**
* Returns the next Date in the sequence, or null if
* the next date is after end.
*
* @return the next Date in the sequence, or null if
* the next date is after end.
*
* @see SpinnerModel#getNextValue
* @see #getPreviousValue
* @see #setCalendarField
*/
public Object getNextValue() {
Calendar cal = Calendar.getInstance();
cal.setTime(value.getTime());
cal.add(calendarField, 1);
Date next = cal.getTime();
return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
}
/**
* Returns the previous Date in the sequence, or null
* if the previous date is before start.
*
* @return the previous Date in the sequence, or
* null if the previous date
* is before start
*
* @see SpinnerModel#getPreviousValue
* @see #getNextValue
* @see #setCalendarField
*/
public Object getPreviousValue() {
Calendar cal = Calendar.getInstance();
cal.setTime(value.getTime());
cal.add(calendarField, -1);
Date prev = cal.getTime();
return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
}
/**
* Returns the current element in this sequence of Dates.
* This method is equivalent to (Date)getValue.
*
* @return the value property
* @see #setValue
*/
public Date getDate() {
return value.getTime();
}
/**
* Returns the current element in this sequence of Dates.
*
* @return the value property
* @see #setValue
* @see #getDate
*/
public Object getValue() {
return value.getTime();
}
/**
* Sets the current Date for this sequence.
* If value is null,
* an IllegalArgumentException is thrown. No bounds
* checking is done here:
* the new value may invalidate the (start <= value < end)
* invariant enforced by the constructors. Naturally, one should ensure
* that the (start <= value <= maximum) invariant is true
* before calling the nextValue, previousValue,
* or setValue methods.
*
* This method fires a ChangeEvent if the
* value has changed.
*
* @param value the current (non null)
* Date for this sequence
* @throws IllegalArgumentException if value is null
* or not a Date
* @see #getDate
* @see #getValue
* @see #addChangeListener
*/
public void setValue(Object value) {
if ((value == null) || !(value instanceof Date)) {
throw new IllegalArgumentException("illegal value");
}
if (!value.equals(this.value.getTime())) {
this.value.setTime((Date)value);
fireStateChanged();
}
}
}