EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.format]

COVERAGE SUMMARY FOR SOURCE FILE [DateTimeParserBucket.java]

nameclass, %method, %block, %line, %
DateTimeParserBucket.java100% (3/3)85%  (23/27)91%  (487/538)90%  (116.8/130)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class DateTimeParserBucket100% (1/1)80%  (16/20)87%  (321/370)86%  (72.8/85)
DateTimeParserBucket (long, Chronology, Locale): void 0%   (0/1)0%   (0/7)0%   (0/2)
computeMillis (): long 0%   (0/1)0%   (0/5)0%   (0/1)
computeMillis (boolean): long 0%   (0/1)0%   (0/5)0%   (0/1)
setPivotYear (Integer): void 0%   (0/1)0%   (0/4)0%   (0/2)
computeMillis (boolean, String): long 100% (1/1)86%  (101/118)83%  (20/24)
restoreState (Object): boolean 100% (1/1)87%  (13/15)80%  (4/5)
sort (Comparable [], int): void 100% (1/1)90%  (45/50)88%  (7/8)
saveField (DateTimeParserBucket$SavedField): void 100% (1/1)92%  (48/52)99%  (10.8/11)
DateTimeParserBucket (long, Chronology, Locale, Integer): void 100% (1/1)100% (31/31)100% (9/9)
getChronology (): Chronology 100% (1/1)100% (3/3)100% (1/1)
getLocale (): Locale 100% (1/1)100% (3/3)100% (1/1)
getOffset (): int 100% (1/1)100% (3/3)100% (1/1)
getPivotYear (): Integer 100% (1/1)100% (3/3)100% (1/1)
getZone (): DateTimeZone 100% (1/1)100% (3/3)100% (1/1)
saveField (DateTimeField, int): void 100% (1/1)100% (8/8)100% (2/2)
saveField (DateTimeFieldType, String, Locale): void 100% (1/1)100% (12/12)100% (2/2)
saveField (DateTimeFieldType, int): void 100% (1/1)100% (11/11)100% (2/2)
saveState (): Object 100% (1/1)100% (12/12)100% (3/3)
setOffset (int): void 100% (1/1)100% (10/10)100% (4/4)
setZone (DateTimeZone): void 100% (1/1)100% (15/15)100% (4/4)
     
class DateTimeParserBucket$SavedState100% (1/1)100% (2/2)97%  (57/59)93%  (14/15)
restoreState (DateTimeParserBucket): boolean 100% (1/1)95%  (35/37)89%  (8/9)
DateTimeParserBucket$SavedState (DateTimeParserBucket): void 100% (1/1)100% (22/22)100% (6/6)
     
class DateTimeParserBucket$SavedField100% (1/1)100% (5/5)100% (109/109)100% (30/30)
DateTimeParserBucket$SavedField (DateTimeField, String, Locale): void 100% (1/1)100% (15/15)100% (6/6)
DateTimeParserBucket$SavedField (DateTimeField, int): void 100% (1/1)100% (15/15)100% (6/6)
compareReverse (DurationField, DurationField): int 100% (1/1)100% (26/26)100% (7/7)
compareTo (Object): int 100% (1/1)100% (24/24)100% (5/5)
set (long, boolean): long 100% (1/1)100% (29/29)100% (6/6)

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 */
16package org.joda.time.format;
17 
18import java.util.Arrays;
19import java.util.Locale;
20 
21import org.joda.time.Chronology;
22import org.joda.time.DateTimeField;
23import org.joda.time.DateTimeFieldType;
24import org.joda.time.DateTimeUtils;
25import org.joda.time.DateTimeZone;
26import org.joda.time.DurationField;
27import 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 */
54public 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 iLocale;
65    /** Used for parsing two-digit years. */
66    private Integer iPivotYear;
67 
68    private SavedField[] iSavedFields = new SavedField[8];
69    private int iSavedFieldsCount;
70    private boolean iSavedFieldsShared;
71    
72    private Object 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 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 locale, Integer 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 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 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 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 text, Locale 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 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 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 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 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(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[] 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 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 {
408        final DateTimeField iField;
409        final int iValue;
410        final String iText;
411        final Locale 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 text, Locale 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 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}

[all classes][org.joda.time.format]
EMMA 2.0.5312 (C) Vladimir Roubtsov