Statistics
| Revision:

gvsig-tools / org.gvsig.tools / library / trunk / org.gvsig.tools / org.gvsig.tools.swing / org.gvsig.tools.swing.serv / org.gvsig.tools.swing.serv.field / src / main / java / org / gvsig / tools / swing / serv / field / component / spinner / NullableDateSpinnerModel.java @ 270

History | View | Annotate | Download (17.7 KB)

1
package org.gvsig.tools.swing.serv.field.component.spinner;
2

    
3
/*
4
 * @(#)SpinnerDateModel.java        1.11 04/05/12
5
 *
6
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
7
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
8
 */
9

    
10
import java.text.DateFormat;
11
import java.text.ParseException;
12
import java.util.*;
13
import java.io.Serializable;
14

    
15
import javax.swing.AbstractSpinnerModel;
16
import javax.swing.SpinnerDateModel;
17

    
18
import org.gvsig.tools.dataTypes.DataTypes;
19
import org.gvsig.tools.swing.serv.field.component.date.DateFormatter;
20

    
21
/**
22
 * A <code>SpinnerModel</code> for sequences of <code>Date</code>s.
23
 * The upper and lower bounds of the sequence are defined by properties called
24
 * <code>start</code> and <code>end</code> and the size
25
 * of the increase or decrease computed by the <code>nextValue</code> and
26
 * <code>previousValue</code> methods is defined by a property
27
 * called <code>calendarField</code>. The <code>start</code> and
28
 * <code>end</code> properties can be <code>null</code> to
29
 * indicate that the sequence has no lower or upper limit.
30
 * <p>
31
 * The value of the <code>calendarField</code> property must be one of the
32
 * <code>java.util.Calendar</code> constants that specify a field within a
33
 * <code>Calendar</code>. The <code>getNextValue</code> and
34
 * <code>getPreviousValue</code> methods change the date forward or backwards by
35
 * this amount. For example, if <code>calendarField</code> is
36
 * <code>Calendar.DAY_OF_WEEK</code>, then <code>nextValue</code> produces a
37
 * <code>Date</code> that's 24 hours after the current <code>value</code>, and
38
 * <code>previousValue</code> produces a <code>Date</code> that's 24 hours
39
 * earlier.
40
 * <p>
41
 * The legal values for <code>calendarField</code> are:
42
 * <ul>
43
 * <li><code>Calendar.ERA</code>
44
 * <li><code>Calendar.YEAR</code>
45
 * <li><code>Calendar.MONTH</code>
46
 * <li><code>Calendar.WEEK_OF_YEAR</code>
47
 * <li><code>Calendar.WEEK_OF_MONTH</code>
48
 * <li><code>Calendar.DAY_OF_MONTH</code>
49
 * <li><code>Calendar.DAY_OF_YEAR</code>
50
 * <li><code>Calendar.DAY_OF_WEEK</code>
51
 * <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
52
 * <li><code>Calendar.AM_PM</code>
53
 * <li><code>Calendar.HOUR</code>
54
 * <li><code>Calendar.HOUR_OF_DAY</code>
55
 * <li><code>Calendar.MINUTE</code>
56
 * <li><code>Calendar.SECOND</code>
57
 * <li><code>Calendar.MILLISECOND</code>
58
 * </ul>
59
 * However some UIs may set the calendarField before commiting the edit to spin
60
 * the field under the cursor. If you only want one field to spin you can
61
 * subclass and ignore the setCalendarField calls.
62
 * <p>
63
 * This model inherits a <code>ChangeListener</code>. The
64
 * <code>ChangeListeners</code> are notified whenever the models
65
 * <code>value</code>, <code>calendarField</code>, <code>start</code>, or
66
 * <code>end</code> properties changes.
67
 * 
68
 * @see JSpinner
69
 * @see SpinnerModel
70
 * @see AbstractSpinnerModel
71
 * @see SpinnerListModel
72
 * @see SpinnerNumberModel
73
 * @see Calendar#add
74
 * 
75
 * @version 1.11 05/12/04
76
 * @author Hans Muller
77
 * @since 1.4
78
 */
79
public class NullableDateSpinnerModel extends SpinnerDateModel implements
80
    Serializable {
81

    
82
    private Comparable start, end;
83
    private DateFormatter formatter;
84
    private Date iniDate;
85
    private Date curValue;
86

    
87
    private boolean calendarFieldOK(int calendarField) {
88
        switch (calendarField) {
89
        case Calendar.ERA:
90
        case Calendar.YEAR:
91
        case Calendar.MONTH:
92
        case Calendar.WEEK_OF_YEAR:
93
        case Calendar.WEEK_OF_MONTH:
94
        case Calendar.DAY_OF_MONTH:
95
        case Calendar.DAY_OF_YEAR:
96
        case Calendar.DAY_OF_WEEK:
97
        case Calendar.DAY_OF_WEEK_IN_MONTH:
98
        case Calendar.AM_PM:
99
        case Calendar.HOUR:
100
        case Calendar.HOUR_OF_DAY:
101
        case Calendar.MINUTE:
102
        case Calendar.SECOND:
103
        case Calendar.MILLISECOND:
104
            return true;
105
        default:
106
            return false;
107
        }
108
    }
109

    
110
    /**
111
     * Creates a <code>SpinnerDateModel</code> that represents a sequence of
112
     * dates
113
     * between <code>start</code> and <code>end</code>. The
114
     * <code>nextValue</code> and <code>previousValue</code> methods
115
     * compute elements of the sequence by advancing or reversing
116
     * the current date <code>value</code> by the <code>calendarField</code>
117
     * time unit. For a precise description
118
     * of what it means to increment or decrement a <code>Calendar</code>
119
     * <code>field</code>, see the <code>add</code> method in
120
     * <code>java.util.Calendar</code>.
121
     * <p>
122
     * The <code>start</code> and <code>end</code> parameters can be
123
     * <code>null</code> to indicate that the range doesn't have an upper or
124
     * lower bound. If <code>value</code> or <code>calendarField</code> is
125
     * <code>null</code>, or if both <code>start</code> and <code>end</code> are
126
     * specified and <code>mininum &gt; maximum</code> then an
127
     * <code>IllegalArgumentException</code> is thrown. Similarly if
128
     * <code>(minimum &lt;= value &lt;= maximum)</code> is false, an
129
     * IllegalArgumentException is thrown.
130
     * 
131
     * @param value
132
     *            the current (non <code>null</code>) value of the model
133
     * @param start
134
     *            the first date in the sequence or <code>null</code>
135
     * @param end
136
     *            the last date in the sequence or <code>null</code>
137
     * @param calendarField
138
     *            one of
139
     *            <ul>
140
     *            <li><code>Calendar.ERA</code> <li><code>Calendar.YEAR</code>
141
     *            <li><code>Calendar.MONTH</code> <li><code>
142
     *            Calendar.WEEK_OF_YEAR</code> <li><code>Calendar.WEEK_OF_MONTH
143
     *            </code> <li><code>Calendar.DAY_OF_MONTH</code> <li><code>
144
     *            Calendar.DAY_OF_YEAR</code> <li><code>Calendar.DAY_OF_WEEK
145
     *            </code> <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code> <li>
146
     *            <code>Calendar.AM_PM</code> <li><code>Calendar.HOUR</code> 
147
     *            <li><code>Calendar.HOUR_OF_DAY</code> <li><code>
148
     *            Calendar.MINUTE</code> <li><code>Calendar.SECOND</code> <li>
149
     *            <code>Calendar.MILLISECOND</code>
150
     *            </ul>
151
     * 
152
     * @throws IllegalArgumentException
153
     *             if <code>value</code> or <code>calendarField</code> are
154
     *             <code>null</code>,
155
     *             if <code>calendarField</code> isn't valid,
156
     *             or if the following expression is
157
     *             false: <code>(start &lt;= value &lt;= end)</code>.
158
     * 
159
     * @see Calendar#add
160
     * @see #setValue
161
     * @see #setStart
162
     * @see #setEnd
163
     * @see #setCalendarField
164
     */
165
    public NullableDateSpinnerModel(Date value, Comparable start,
166
        Comparable end, int dynType, Locale loc) {
167

    
168
        formatter = new DateFormatter(loc).setDynTypeFormatParametters(dynType);
169

    
170
        if (!calendarFieldOK(formatter.getDateStepByType())) {
171
            throw new IllegalArgumentException("invalid calendarField");
172
        }
173
        if (!(((start == null) || (start.compareTo(value) <= 0)) && ((end == null) || (end
174
            .compareTo(value) >= 0)))) {
175
            throw new IllegalArgumentException(
176
                "(start <= value <= end) is false");
177
        }
178
        this.start = start;
179
        this.end = end;
180
        this.iniDate = value;
181
    }
182

    
183
    /**
184
     * Constructs a <code>SpinnerDateModel</code> whose initial
185
     * <code>value</code> is the current date, <code>calendarField</code> is
186
     * equal to <code>Calendar.DAY_OF_MONTH</code>, and for which
187
     * there are no <code>start</code>/<code>end</code> limits.
188
     */
189
    public NullableDateSpinnerModel() {
190
        this(null, null, null, DataTypes.DATE, Locale.getDefault());
191
    }
192

    
193
    /**
194
     * Changes the lower limit for Dates in this sequence.
195
     * If <code>start</code> is <code>null</code>,
196
     * then there is no lower limit. No bounds checking is done here:
197
     * the new start value may invalidate the
198
     * <code>(start &lt;= value &lt;= end)</code> invariant enforced by the
199
     * constructors. This is to simplify updating
200
     * the model. Naturally one should ensure that the invariant is true
201
     * before calling the <code>nextValue</code>, <code>previousValue</code>,
202
     * or <code>setValue</code> methods.
203
     * <p>
204
     * Typically this property is a <code>Date</code> however it's possible to
205
     * use a <code>Comparable</code> with a <code>compareTo</code> method for
206
     * Dates. For example <code>start</code> might be an instance of a class
207
     * like this:
208
     * 
209
     * <pre>
210
     * MyStartDate implements Comparable { 
211
     *     long t = 12345;
212
     *     public int compareTo(Date d) {
213
     *            return (t < d.getTime() ? -1 : (t == d.getTime() ? 0 : 1));
214
     *     }
215
     *     public int compareTo(Object o) {
216
     *            return compareTo((Date)o);
217
     *     }
218
     * }
219
     * </pre>
220
     * 
221
     * Note that the above example will throw a <code>ClassCastException</code>
222
     * if the <code>Object</code> passed to <code>compareTo(Object)</code> is
223
     * not a <code>Date</code>.
224
     * <p>
225
     * This method fires a <code>ChangeEvent</code> if the <code>start</code>
226
     * has changed.
227
     * 
228
     * @param start
229
     *            defines the first date in the sequence
230
     * @see #getStart
231
     * @see #setEnd
232
     * @see #addChangeListener
233
     */
234
    public void setStart(Comparable start) {
235
        if ((start == null) ? (this.start != null) : !start.equals(this.start)) {
236
            this.start = start;
237
            fireStateChanged();
238
        }
239
    }
240

    
241
    /**
242
     * Returns the first <code>Date</code> in the sequence.
243
     * 
244
     * @return the value of the <code>start</code> property
245
     * @see #setStart
246
     */
247
    public Comparable getStart() {
248
        return start;
249
    }
250

    
251
    /**
252
     * Changes the upper limit for <code>Date</code>s in this sequence.
253
     * If <code>start</code> is <code>null</code>, then there is no upper
254
     * limit. No bounds checking is done here: the new
255
     * start value may invalidate the <code>(start &lt;= value &lt;= end)</code>
256
     * invariant enforced by the constructors. This is to simplify updating
257
     * the model. Naturally, one should ensure that the invariant is true
258
     * before calling the <code>nextValue</code>, <code>previousValue</code>,
259
     * or <code>setValue</code> methods.
260
     * <p>
261
     * Typically this property is a <code>Date</code> however it's possible to
262
     * use <code>Comparable</code> with a <code>compareTo</code> method for
263
     * <code>Date</code>s. See <code>setStart</code> for an example.
264
     * <p>
265
     * This method fires a <code>ChangeEvent</code> if the <code>end</code> has
266
     * changed.
267
     * 
268
     * @param end
269
     *            defines the last date in the sequence
270
     * @see #getEnd
271
     * @see #setStart
272
     * @see #addChangeListener
273
     */
274
    public void setEnd(Comparable end) {
275
        if ((end == null) ? (this.end != null) : !end.equals(this.end)) {
276
            this.end = end;
277
            fireStateChanged();
278
        }
279
    }
280

    
281
    /**
282
     * Returns the last <code>Date</code> in the sequence.
283
     * 
284
     * @return the value of the <code>end</code> property
285
     * @see #setEnd
286
     */
287
    public Comparable getEnd() {
288
        return end;
289
    }
290

    
291
    /**
292
     * Changes the size of the date value change computed
293
     * by the <code>nextValue</code> and <code>previousValue</code> methods.
294
     * The <code>calendarField</code> parameter must be one of the
295
     * <code>Calendar</code> field constants like <code>Calendar.MONTH</code> or
296
     * <code>Calendar.MINUTE</code>.
297
     * The <code>nextValue</code> and <code>previousValue</code> methods
298
     * simply move the specified <code>Calendar</code> field forward or backward
299
     * by one unit with the <code>Calendar.add</code> method.
300
     * You should use this method with care as some UIs may set the
301
     * calendarField before commiting the edit to spin the field under
302
     * the cursor. If you only want one field to spin you can subclass
303
     * and ignore the setCalendarField calls.
304
     * 
305
     * @param calendarField
306
     *            one of
307
     *            <ul>
308
     *            <li><code>Calendar.ERA</code>
309
     *            <li><code>Calendar.YEAR</code>
310
     *            <li><code>Calendar.MONTH</code>
311
     *            <li><code>Calendar.WEEK_OF_YEAR</code>
312
     *            <li><code>Calendar.WEEK_OF_MONTH</code>
313
     *            <li><code>Calendar.DAY_OF_MONTH</code>
314
     *            <li><code>Calendar.DAY_OF_YEAR</code>
315
     *            <li><code>Calendar.DAY_OF_WEEK</code>
316
     *            <li><code>Calendar.DAY_OF_WEEK_IN_MONTH</code>
317
     *            <li><code>Calendar.AM_PM</code>
318
     *            <li><code>Calendar.HOUR</code>
319
     *            <li><code>Calendar.HOUR_OF_DAY</code>
320
     *            <li><code>Calendar.MINUTE</code>
321
     *            <li><code>Calendar.SECOND</code>
322
     *            <li><code>Calendar.MILLISECOND</code>
323
     *            </ul>
324
     *            <p>
325
     *            This method fires a <code>ChangeEvent</code> if the
326
     *            <code>calendarField</code> has changed.
327
     * 
328
     * @see #getCalendarField
329
     * @see #getNextValue
330
     * @see #getPreviousValue
331
     * @see Calendar#add
332
     * @see #addChangeListener
333
     */
334
    public void setCalendarField(int calendarField) {
335
        if (!calendarFieldOK(calendarField)) {
336
            throw new IllegalArgumentException("invalid calendarField");
337
        }
338
        if (calendarField != getCalendarField()) {
339
            this.formatter.setDateStepByType(calendarField);
340
            fireStateChanged();
341
        }
342
    }
343

    
344
    /**
345
     * Returns the <code>Calendar</code> field that is added to or subtracted
346
     * from
347
     * by the <code>nextValue</code> and <code>previousValue</code> methods.
348
     * 
349
     * @return the value of the <code>calendarField</code> property
350
     * @see #setCalendarField
351
     */
352
    public int getCalendarField() {
353
        return this.formatter.getDateStepByType();
354
    }
355

    
356
    /**
357
     * Returns the next <code>Date</code> in the sequence, or <code>null</code>
358
     * if
359
     * the next date is after <code>end</code>.
360
     * 
361
     * @return the next <code>Date</code> in the sequence, or <code>null</code>
362
     *         if
363
     *         the next date is after <code>end</code>.
364
     * 
365
     * @see SpinnerModel#getNextValue
366
     * @see #getPreviousValue
367
     * @see #setCalendarField
368
     */
369
    public Object getNextValue() {
370
        Date next = getDate();
371
        if (next != null) {
372
            Calendar cal = formatter.getCalendar();
373
            cal.setTime(next);
374
            cal.add(getCalendarField(), 1);
375
            next = cal.getTime();
376
        } else {
377
            next = new Date();
378
        }
379
        return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
380
    }
381

    
382
    /**
383
     * Returns the previous <code>Date</code> in the sequence, or
384
     * <code>null</code> if the previous date is before <code>start</code>.
385
     * 
386
     * @return the previous <code>Date</code> in the sequence, or
387
     *         <code>null</code> if the previous date
388
     *         is before <code>start</code>
389
     * 
390
     * @see SpinnerModel#getPreviousValue
391
     * @see #getNextValue
392
     * @see #setCalendarField
393
     */
394
    public Object getPreviousValue() {
395
        Date date = getDate();
396
        if (date == null) {
397
            return null;
398
        }
399
        Calendar cal = formatter.getCalendar();
400
        cal.setTime(date);
401
        cal.add(getCalendarField(), -1);
402
        Date prev = cal.getTime();
403
        return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
404
    }
405

    
406
    /**
407
     * Returns the current element in this sequence of <code>Date</code>s.
408
     * This method is equivalent to <code>(Date)getValue</code>.
409
     * 
410
     * @return the <code>value</code> property
411
     * @see #setValue
412
     */
413
    public Date getDate() {
414
        return (Date) getValue();
415
    }
416

    
417
    /**
418
     * Returns the current element in this sequence of <code>Date</code>s.
419
     * 
420
     * @return the <code>value</code> property
421
     * @see #setValue
422
     * @see #getDate
423
     */
424
    public Object getValue() {
425
        return this.curValue;
426
    }
427

    
428
    /**
429
     * Sets the current <code>Date</code> for this sequence.
430
     * If <code>value</code> is <code>null</code>,
431
     * an <code>IllegalArgumentException</code> is thrown. No bounds
432
     * checking is done here:
433
     * the new value may invalidate the <code>(start &lt= value &lt end)</code>
434
     * invariant enforced by the constructors. Naturally, one should ensure
435
     * that the <code>(start &lt;= value &lt;= maximum)</code> invariant is true
436
     * before calling the <code>nextValue</code>, <code>previousValue</code>,
437
     * or <code>setValue</code> methods.
438
     * <p>
439
     * This method fires a <code>ChangeEvent</code> if the <code>value</code>
440
     * has changed.
441
     * 
442
     * @param value
443
     *            the current (non <code>null</code>) <code>Date</code> for this
444
     *            sequence
445
     * @throws IllegalArgumentException
446
     *             if value is <code>null</code> or not a <code>Date</code>
447
     * @see #getDate
448
     * @see #getValue
449
     * @see #addChangeListener
450
     */
451
    public void setValue(Object value) {
452
        if ((value != null) && (!(value instanceof Date))) {
453
            throw new IllegalArgumentException(value.toString());
454
        }
455

    
456
        if (!((this.curValue == null) && (value == null))) {
457
            this.curValue = (Date) value;
458
            fireStateChanged();
459
        }
460
        this.curValue = (Date) value;
461
    }
462

    
463
    public Date format(Date date) throws ParseException {
464
        return formatter.parse(formatter.format(date));
465
    }
466
}