KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > joda > time > format > DateTimeParserBucket


1 /*
2  * Copyright 2001-2006 Stephen Colebourne
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.joda.time.format;
17
18 import java.util.Arrays JavaDoc;
19 import java.util.Locale JavaDoc;
20
21 import org.joda.time.Chronology;
22 import org.joda.time.DateTimeField;
23 import org.joda.time.DateTimeFieldType;
24 import org.joda.time.DateTimeUtils;
25 import org.joda.time.DateTimeZone;
26 import org.joda.time.DurationField;
27 import org.joda.time.IllegalFieldValueException;
28
29 /**
30  * DateTimeParserBucket is an advanced class, intended mainly for parser
31  * implementations. It can also be used during normal parsing operations to
32  * capture more information about the parse.
33  * <p>
34  * This class allows fields to be saved in any order, but be physically set in
35  * a consistent order. This is useful for parsing against formats that allow
36  * field values to contradict each other.
37  * <p>
38  * Field values are applied in an order where the "larger" fields are set
39  * first, making their value less likely to stick. A field is larger than
40  * another when it's range duration is longer. If both ranges are the same,
41  * then the larger field has the longer duration. If it cannot be determined
42  * which field is larger, then the fields are set in the order they were saved.
43  * <p>
44  * For example, these fields were saved in this order: dayOfWeek, monthOfYear,
45  * dayOfMonth, dayOfYear. When computeMillis is called, the fields are set in
46  * this order: monthOfYear, dayOfYear, dayOfMonth, dayOfWeek.
47  * <p>
48  * DateTimeParserBucket is mutable and not thread-safe.
49  *
50  * @author Brian S O'Neill
51  * @author Fredrik Borgh
52  * @since 1.0
53  */

54 public class DateTimeParserBucket {
55
56     /** The chronology to use for parsing. */
57     private final Chronology iChrono;
58     private final long iMillis;
59     
60     // TimeZone to switch to in computeMillis. If null, use offset.
61
private DateTimeZone iZone;
62     private int iOffset;
63     /** The locale to use for parsing. */
64     private Locale JavaDoc iLocale;
65     /** Used for parsing two-digit years. */
66     private Integer JavaDoc iPivotYear;
67
68     private SavedField[] iSavedFields = new SavedField[8];
69     private int iSavedFieldsCount;
70     private boolean iSavedFieldsShared;
71     
72     private Object JavaDoc iSavedState;
73
74     /**
75      * Constucts a bucket.
76      *
77      * @param instantLocal the initial millis from 1970-01-01T00:00:00, local time
78      * @param chrono the chronology to use
79      * @param locale the locale to use
80      */

81     public DateTimeParserBucket(long instantLocal, Chronology chrono, Locale JavaDoc locale) {
82         this(instantLocal, chrono, locale, null);
83     }
84
85     /**
86      * Constucts a bucket, with the option of specifying the pivot year for
87      * two-digit year parsing.
88      *
89      * @param instantLocal the initial millis from 1970-01-01T00:00:00, local time
90      * @param chrono the chronology to use
91      * @param locale the locale to use
92      * @param pivotYear the pivot year to use when parsing two-digit years
93      * @since 1.1
94      */

95     public DateTimeParserBucket(long instantLocal, Chronology chrono, Locale JavaDoc locale, Integer JavaDoc pivotYear) {
96         super();
97         chrono = DateTimeUtils.getChronology(chrono);
98         iMillis = instantLocal;
99         iChrono = chrono.withUTC();
100         iLocale = (locale == null ? Locale.getDefault() : locale);
101         setZone(chrono.getZone());
102         iPivotYear = pivotYear;
103     }
104
105     //-----------------------------------------------------------------------
106
/**
107      * Gets the chronology of the bucket, which will be a local (UTC) chronology.
108      */

109     public Chronology getChronology() {
110         return iChrono;
111     }
112
113     //-----------------------------------------------------------------------
114
/**
115      * Returns the locale to be used during parsing.
116      *
117      * @return the locale to use
118      */

119     public Locale JavaDoc getLocale() {
120         return iLocale;
121     }
122
123     //-----------------------------------------------------------------------
124
/**
125      * Returns the time zone used by computeMillis, or null if an offset is
126      * used instead.
127      */

128     public DateTimeZone getZone() {
129         return iZone;
130     }
131     
132     /**
133      * Set a time zone to be used when computeMillis is called, which
134      * overrides any set time zone offset.
135      *
136      * @param zone the date time zone to operate in, or null if UTC
137      */

138     public void setZone(DateTimeZone zone) {
139         iSavedState = null;
140         iZone = zone == DateTimeZone.UTC ? null : zone;
141         iOffset = 0;
142     }
143     
144     //-----------------------------------------------------------------------
145
/**
146      * Returns the time zone offset in milliseconds used by computeMillis,
147      * unless getZone doesn't return null.
148      */

149     public int getOffset() {
150         return iOffset;
151     }
152     
153     /**
154      * Set a time zone offset to be used when computeMillis is called, which
155      * overrides the time zone.
156      */

157     public void setOffset(int offset) {
158         iSavedState = null;
159         iOffset = offset;
160         iZone = null;
161     }
162
163     //-----------------------------------------------------------------------
164
/**
165      * Returns the pivot year used for parsing two-digit years.
166      * <p>
167      * If null is returned, this indicates default behaviour
168      *
169      * @return Integer value of the pivot year, null if not set
170      * @since 1.1
171      */

172     public Integer JavaDoc getPivotYear() {
173         return iPivotYear;
174     }
175
176     /**
177      * Sets the pivot year to use when parsing two digit years.
178      * <p>
179      * If the value is set to null, this will indicate that default
180      * behaviour should be used.
181      *
182      * @param pivotYear the pivot year to use
183      * @since 1.1
184      */

185     public void setPivotYear(Integer JavaDoc pivotYear) {
186         iPivotYear = pivotYear;
187     }
188
189     //-----------------------------------------------------------------------
190
/**
191      * Saves a datetime field value.
192      *
193      * @param field the field, whose chronology must match that of this bucket
194      * @param value the value
195      */

196     public void saveField(DateTimeField field, int value) {
197         saveField(new SavedField(field, value));
198     }
199     
200     /**
201      * Saves a datetime field value.
202      *
203      * @param fieldType the field type
204      * @param value the value
205      */

206     public void saveField(DateTimeFieldType fieldType, int value) {
207         saveField(new SavedField(fieldType.getField(iChrono), value));
208     }
209     
210     /**
211      * Saves a datetime field text value.
212      *
213      * @param fieldType the field type
214      * @param text the text value
215      * @param locale the locale to use
216      */

217     public void saveField(DateTimeFieldType fieldType, String JavaDoc text, Locale JavaDoc locale) {
218         saveField(new SavedField(fieldType.getField(iChrono), text, locale));
219     }
220     
221     private void saveField(SavedField field) {
222         SavedField[] savedFields = iSavedFields;
223         int savedFieldsCount = iSavedFieldsCount;
224         
225         if (savedFieldsCount == savedFields.length || iSavedFieldsShared) {
226             // Expand capacity or merely copy if saved fields are shared.
227
SavedField[] newArray = new SavedField
228                 [savedFieldsCount == savedFields.length ? savedFieldsCount * 2 : savedFields.length];
229             System.arraycopy(savedFields, 0, newArray, 0, savedFieldsCount);
230             iSavedFields = savedFields = newArray;
231             iSavedFieldsShared = false;
232         }
233         
234         iSavedState = null;
235         savedFields[savedFieldsCount] = field;
236         iSavedFieldsCount = savedFieldsCount + 1;
237     }
238     
239     /**
240      * Saves the state of this bucket, returning it in an opaque object. Call
241      * restoreState to undo any changes that were made since the state was
242      * saved. Calls to saveState may be nested.
243      *
244      * @return opaque saved state, which may be passed to restoreState
245      */

246     public Object JavaDoc saveState() {
247         if (iSavedState == null) {
248             iSavedState = new SavedState();
249         }
250         return iSavedState;
251     }
252     
253     /**
254      * Restores the state of this bucket from a previously saved state. The
255      * state object passed into this method is not consumed, and it can be used
256      * later to restore to that state again.
257      *
258      * @param savedState opaque saved state, returned from saveState
259      * @return true state object is valid and state restored
260      */

261     public boolean restoreState(Object JavaDoc savedState) {
262         if (savedState instanceof SavedState) {
263             if (((SavedState) savedState).restoreState(this)) {
264                 iSavedState = savedState;
265                 return true;
266             }
267         }
268         return false;
269     }
270     
271     /**
272      * Computes the parsed datetime by setting the saved fields.
273      * This method is idempotent, but it is not thread-safe.
274      *
275      * @return milliseconds since 1970-01-01T00:00:00Z
276      * @throws IllegalArgumentException if any field is out of range
277      */

278     public long computeMillis() {
279         return computeMillis(false, null);
280     }
281     
282     /**
283      * Computes the parsed datetime by setting the saved fields.
284      * This method is idempotent, but it is not thread-safe.
285      *
286      * @param resetFields false by default, but when true, unsaved field values are cleared
287      * @return milliseconds since 1970-01-01T00:00:00Z
288      * @throws IllegalArgumentException if any field is out of range
289      */

290     public long computeMillis(boolean resetFields) {
291         return computeMillis(resetFields, null);
292     }
293
294     /**
295      * Computes the parsed datetime by setting the saved fields.
296      * This method is idempotent, but it is not thread-safe.
297      *
298      * @param resetFields false by default, but when true, unsaved field values are cleared
299      * @param text optional text being parsed, to be included in any error message
300      * @return milliseconds since 1970-01-01T00:00:00Z
301      * @throws IllegalArgumentException if any field is out of range
302      * @since 1.3
303      */

304     public long computeMillis(boolean resetFields, String JavaDoc text) {
305         SavedField[] savedFields = iSavedFields;
306         int count = iSavedFieldsCount;
307         if (iSavedFieldsShared) {
308             iSavedFields = savedFields = (SavedField[])iSavedFields.clone();
309             iSavedFieldsShared = false;
310         }
311         sort(savedFields, count);
312
313         long millis = iMillis;
314         try {
315             for (int i=0; i<count; i++) {
316                 millis = savedFields[i].set(millis, resetFields);
317             }
318         } catch (IllegalFieldValueException e) {
319             if (text != null) {
320                 e.prependMessage("Cannot parse \"" + text + '"');
321             }
322             throw e;
323         }
324         
325         if (iZone == null) {
326             millis -= iOffset;
327         } else {
328             int offset = iZone.getOffsetFromLocal(millis);
329             millis -= offset;
330             if (offset != iZone.getOffset(millis)) {
331                 String JavaDoc message =
332                     "Illegal instant due to time zone offset transition (" + iZone + ')';
333                 if (text != null) {
334                     message = "Cannot parse \"" + text + "\": " + message;
335                 }
336                 throw new IllegalArgumentException JavaDoc(message);
337             }
338         }
339         
340         return millis;
341     }
342     
343     /**
344      * Sorts elements [0,high). Calling java.util.Arrays isn't always the right
345      * choice since it always creates an internal copy of the array, even if it
346      * doesn't need to. If the array slice is small enough, an insertion sort
347      * is chosen instead, but it doesn't need a copy!
348      * <p>
349      * This method has a modified version of that insertion sort, except it
350      * doesn't create an unnecessary array copy. If high is over 10, then
351      * java.util.Arrays is called, which will perform a merge sort, which is
352      * faster than insertion sort on large lists.
353      * <p>
354      * The end result is much greater performace when computeMillis is called.
355      * Since the amount of saved fields is small, the insertion sort is a
356      * better choice. Additional performance is gained since there is no extra
357      * array allocation and copying. Also, the insertion sort here does not
358      * perform any casting operations. The version in java.util.Arrays performs
359      * casts within the insertion sort loop.
360      */

361     private static void sort(Comparable JavaDoc[] array, int high) {
362         if (high > 10) {
363             Arrays.sort(array, 0, high);
364         } else {
365             for (int i=0; i<high; i++) {
366                 for (int j=i; j>0 && (array[j-1]).compareTo(array[j])>0; j--) {
367                     Comparable JavaDoc t = array[j];
368                     array[j] = array[j-1];
369                     array[j-1] = t;
370                 }
371             }
372         }
373     }
374
375     class SavedState {
376         final DateTimeZone iZone;
377         final int iOffset;
378         final SavedField[] iSavedFields;
379         final int iSavedFieldsCount;
380         
381         SavedState() {
382             this.iZone = DateTimeParserBucket.this.iZone;
383             this.iOffset = DateTimeParserBucket.this.iOffset;
384             this.iSavedFields = DateTimeParserBucket.this.iSavedFields;
385             this.iSavedFieldsCount = DateTimeParserBucket.this.iSavedFieldsCount;
386         }
387         
388         boolean restoreState(DateTimeParserBucket enclosing) {
389             if (enclosing != DateTimeParserBucket.this) {
390                 return false;
391             }
392             enclosing.iZone = this.iZone;
393             enclosing.iOffset = this.iOffset;
394             enclosing.iSavedFields = this.iSavedFields;
395             if (this.iSavedFieldsCount < enclosing.iSavedFieldsCount) {
396                 // Since count is being restored to a lower count, the
397
// potential exists for new saved fields to destroy data being
398
// shared by another state. Set this flag such that the array
399
// of saved fields is cloned prior to modification.
400
enclosing.iSavedFieldsShared = true;
401             }
402             enclosing.iSavedFieldsCount = this.iSavedFieldsCount;
403             return true;
404         }
405     }
406     
407     static class SavedField implements Comparable JavaDoc {
408         final DateTimeField iField;
409         final int iValue;
410         final String JavaDoc iText;
411         final Locale JavaDoc iLocale;
412         
413         SavedField(DateTimeField field, int value) {
414             iField = field;
415             iValue = value;
416             iText = null;
417             iLocale = null;
418         }
419         
420         SavedField(DateTimeField field, String JavaDoc text, Locale JavaDoc locale) {
421             iField = field;
422             iValue = 0;
423             iText = text;
424             iLocale = locale;
425         }
426         
427         long set(long millis, boolean reset) {
428             if (iText == null) {
429                 millis = iField.set(millis, iValue);
430             } else {
431                 millis = iField.set(millis, iText, iLocale);
432             }
433             if (reset) {
434                 millis = iField.roundFloor(millis);
435             }
436             return millis;
437         }
438         
439         /**
440          * The field with the longer range duration is ordered first, where
441          * null is considered infinite. If the ranges match, then the field
442          * with the longer duration is ordered first.
443          */

444         public int compareTo(Object JavaDoc obj) {
445             DateTimeField other = ((SavedField)obj).iField;
446             int result = compareReverse
447                 (iField.getRangeDurationField(), other.getRangeDurationField());
448             if (result != 0) {
449                 return result;
450             }
451             return compareReverse
452                 (iField.getDurationField(), other.getDurationField());
453         }
454         
455         private int compareReverse(DurationField a, DurationField b) {
456             if (a == null || !a.isSupported()) {
457                 if (b == null || !b.isSupported()) {
458                     return 0;
459                 }
460                 return -1;
461             }
462             if (b == null || !b.isSupported()) {
463                 return 1;
464             }
465             return -a.compareTo(b);
466         }
467     }
468 }
469
Popular Tags