001/*
002 *  Copyright 2001-2011 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.time.format;
017
018import java.io.IOException;
019import java.io.Writer;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026import java.util.Set;
027
028import org.joda.time.Chronology;
029import org.joda.time.DateTimeConstants;
030import org.joda.time.DateTimeField;
031import org.joda.time.DateTimeFieldType;
032import org.joda.time.DateTimeUtils;
033import org.joda.time.DateTimeZone;
034import org.joda.time.MutableDateTime;
035import org.joda.time.ReadablePartial;
036import org.joda.time.MutableDateTime.Property;
037import org.joda.time.field.MillisDurationField;
038import org.joda.time.field.PreciseDateTimeField;
039
040/**
041 * Factory that creates complex instances of DateTimeFormatter via method calls.
042 * <p>
043 * Datetime formatting is performed by the {@link DateTimeFormatter} class.
044 * Three classes provide factory methods to create formatters, and this is one.
045 * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}.
046 * <p>
047 * DateTimeFormatterBuilder is used for constructing formatters which are then
048 * used to print or parse. The formatters are built by appending specific fields
049 * or other formatters to an instance of this builder.
050 * <p>
051 * For example, a formatter that prints month and year, like "January 1970",
052 * can be constructed as follows:
053 * <p>
054 * <pre>
055 * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder()
056 *     .appendMonthOfYearText()
057 *     .appendLiteral(' ')
058 *     .appendYear(4, 4)
059 *     .toFormatter();
060 * </pre>
061 * <p>
062 * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the
063 * formatters that it builds are thread-safe and immutable.
064 *
065 * @author Brian S O'Neill
066 * @author Stephen Colebourne
067 * @author Fredrik Borgh
068 * @since 1.0
069 * @see DateTimeFormat
070 * @see ISODateTimeFormat
071 */
072public class DateTimeFormatterBuilder {
073
074    /** Array of printers and parsers (alternating). */
075    private ArrayList<Object> iElementPairs;
076    /** Cache of the last returned formatter. */
077    private Object iFormatter;
078
079    //-----------------------------------------------------------------------
080    /**
081     * Creates a DateTimeFormatterBuilder.
082     */
083    public DateTimeFormatterBuilder() {
084        super();
085        iElementPairs = new ArrayList<Object>();
086    }
087
088    //-----------------------------------------------------------------------
089    /**
090     * Constructs a DateTimeFormatter using all the appended elements.
091     * <p>
092     * This is the main method used by applications at the end of the build
093     * process to create a usable formatter.
094     * <p>
095     * Subsequent changes to this builder do not affect the returned formatter.
096     * <p>
097     * The returned formatter may not support both printing and parsing.
098     * The methods {@link DateTimeFormatter#isPrinter()} and
099     * {@link DateTimeFormatter#isParser()} will help you determine the state
100     * of the formatter.
101     *
102     * @throws UnsupportedOperationException if neither printing nor parsing is supported
103     */
104    public DateTimeFormatter toFormatter() {
105        Object f = getFormatter();
106        DateTimePrinter printer = null;
107        if (isPrinter(f)) {
108            printer = (DateTimePrinter) f;
109        }
110        DateTimeParser parser = null;
111        if (isParser(f)) {
112            parser = (DateTimeParser) f;
113        }
114        if (printer != null || parser != null) {
115            return new DateTimeFormatter(printer, parser);
116        }
117        throw new UnsupportedOperationException("Both printing and parsing not supported");
118    }
119
120    /**
121     * Internal method to create a DateTimePrinter instance using all the
122     * appended elements.
123     * <p>
124     * Most applications will not use this method.
125     * If you want a printer in an application, call {@link #toFormatter()}
126     * and just use the printing API.
127     * <p>
128     * Subsequent changes to this builder do not affect the returned printer.
129     *
130     * @throws UnsupportedOperationException if printing is not supported
131     */
132    public DateTimePrinter toPrinter() {
133        Object f = getFormatter();
134        if (isPrinter(f)) {
135            return (DateTimePrinter) f;
136        }
137        throw new UnsupportedOperationException("Printing is not supported");
138    }
139
140    /**
141     * Internal method to create a DateTimeParser instance using all the
142     * appended elements.
143     * <p>
144     * Most applications will not use this method.
145     * If you want a parser in an application, call {@link #toFormatter()}
146     * and just use the parsing API.
147     * <p>
148     * Subsequent changes to this builder do not affect the returned parser.
149     *
150     * @throws UnsupportedOperationException if parsing is not supported
151     */
152    public DateTimeParser toParser() {
153        Object f = getFormatter();
154        if (isParser(f)) {
155            return (DateTimeParser) f;
156        }
157        throw new UnsupportedOperationException("Parsing is not supported");
158    }
159
160    //-----------------------------------------------------------------------
161    /**
162     * Returns true if toFormatter can be called without throwing an
163     * UnsupportedOperationException.
164     * 
165     * @return true if a formatter can be built
166     */
167    public boolean canBuildFormatter() {
168        return isFormatter(getFormatter());
169    }
170
171    /**
172     * Returns true if toPrinter can be called without throwing an
173     * UnsupportedOperationException.
174     * 
175     * @return true if a printer can be built
176     */
177    public boolean canBuildPrinter() {
178        return isPrinter(getFormatter());
179    }
180
181    /**
182     * Returns true if toParser can be called without throwing an
183     * UnsupportedOperationException.
184     * 
185     * @return true if a parser can be built
186     */
187    public boolean canBuildParser() {
188        return isParser(getFormatter());
189    }
190
191    //-----------------------------------------------------------------------
192    /**
193     * Clears out all the appended elements, allowing this builder to be
194     * reused.
195     */
196    public void clear() {
197        iFormatter = null;
198        iElementPairs.clear();
199    }
200
201    //-----------------------------------------------------------------------
202    /**
203     * Appends another formatter.
204     * <p>
205     * This extracts the underlying printer and parser and appends them
206     * The printer and parser interfaces are the low-level part of the formatting API.
207     * Normally, instances are extracted from another formatter.
208     * Note however that any formatter specific information, such as the locale,
209     * time-zone, chronology, offset parsing or pivot/default year, will not be
210     * extracted by this method.
211     *
212     * @param formatter  the formatter to add
213     * @return this DateTimeFormatterBuilder, for chaining
214     * @throws IllegalArgumentException if formatter is null or of an invalid type
215     */
216    public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
217        if (formatter == null) {
218            throw new IllegalArgumentException("No formatter supplied");
219        }
220        return append0(formatter.getPrinter(), formatter.getParser());
221    }
222
223    /**
224     * Appends just a printer. With no matching parser, a parser cannot be
225     * built from this DateTimeFormatterBuilder.
226     * <p>
227     * The printer interface is part of the low-level part of the formatting API.
228     * Normally, instances are extracted from another formatter.
229     * Note however that any formatter specific information, such as the locale,
230     * time-zone, chronology, offset parsing or pivot/default year, will not be
231     * extracted by this method.
232     *
233     * @param printer  the printer to add, not null
234     * @return this DateTimeFormatterBuilder, for chaining
235     * @throws IllegalArgumentException if printer is null or of an invalid type
236     */
237    public DateTimeFormatterBuilder append(DateTimePrinter printer) {
238        checkPrinter(printer);
239        return append0(printer, null);
240    }
241
242    /**
243     * Appends just a parser. With no matching printer, a printer cannot be
244     * built from this builder.
245     * <p>
246     * The parser interface is part of the low-level part of the formatting API.
247     * Normally, instances are extracted from another formatter.
248     * Note however that any formatter specific information, such as the locale,
249     * time-zone, chronology, offset parsing or pivot/default year, will not be
250     * extracted by this method.
251     *
252     * @param parser  the parser to add, not null
253     * @return this DateTimeFormatterBuilder, for chaining
254     * @throws IllegalArgumentException if parser is null or of an invalid type
255     */
256    public DateTimeFormatterBuilder append(DateTimeParser parser) {
257        checkParser(parser);
258        return append0(null, parser);
259    }
260
261    /**
262     * Appends a printer/parser pair.
263     * <p>
264     * The printer and parser interfaces are the low-level part of the formatting API.
265     * Normally, instances are extracted from another formatter.
266     * Note however that any formatter specific information, such as the locale,
267     * time-zone, chronology, offset parsing or pivot/default year, will not be
268     * extracted by this method.
269     *
270     * @param printer  the printer to add, not null
271     * @param parser  the parser to add, not null
272     * @return this DateTimeFormatterBuilder, for chaining
273     * @throws IllegalArgumentException if printer or parser is null or of an invalid type
274     */
275    public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) {
276        checkPrinter(printer);
277        checkParser(parser);
278        return append0(printer, parser);
279    }
280
281    /**
282     * Appends a printer and a set of matching parsers. When parsing, the first
283     * parser in the list is selected for parsing. If it fails, the next is
284     * chosen, and so on. If none of these parsers succeeds, then the failed
285     * position of the parser that made the greatest progress is returned.
286     * <p>
287     * Only the printer is optional. In addition, it is illegal for any but the
288     * last of the parser array elements to be null. If the last element is
289     * null, this represents the empty parser. The presence of an empty parser
290     * indicates that the entire array of parse formats is optional.
291     * <p>
292     * The printer and parser interfaces are the low-level part of the formatting API.
293     * Normally, instances are extracted from another formatter.
294     * Note however that any formatter specific information, such as the locale,
295     * time-zone, chronology, offset parsing or pivot/default year, will not be
296     * extracted by this method.
297     *
298     * @param printer  the printer to add
299     * @param parsers  the parsers to add
300     * @return this DateTimeFormatterBuilder, for chaining
301     * @throws IllegalArgumentException if any printer or parser is of an invalid type
302     * @throws IllegalArgumentException if any parser element but the last is null
303     */
304    public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) {
305        if (printer != null) {
306            checkPrinter(printer);
307        }
308        if (parsers == null) {
309            throw new IllegalArgumentException("No parsers supplied");
310        }
311        int length = parsers.length;
312        if (length == 1) {
313            if (parsers[0] == null) {
314                throw new IllegalArgumentException("No parser supplied");
315            }
316            return append0(printer, parsers[0]);
317        }
318
319        DateTimeParser[] copyOfParsers = new DateTimeParser[length];
320        int i;
321        for (i = 0; i < length - 1; i++) {
322            if ((copyOfParsers[i] = parsers[i]) == null) {
323                throw new IllegalArgumentException("Incomplete parser array");
324            }
325        }
326        copyOfParsers[i] = parsers[i];
327
328        return append0(printer, new MatchingParser(copyOfParsers));
329    }
330
331    /**
332     * Appends just a parser element which is optional. With no matching
333     * printer, a printer cannot be built from this DateTimeFormatterBuilder.
334     * <p>
335     * The parser interface is part of the low-level part of the formatting API.
336     * Normally, instances are extracted from another formatter.
337     * Note however that any formatter specific information, such as the locale,
338     * time-zone, chronology, offset parsing or pivot/default year, will not be
339     * extracted by this method.
340     *
341     * @return this DateTimeFormatterBuilder, for chaining
342     * @throws IllegalArgumentException if parser is null or of an invalid type
343     */
344    public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) {
345        checkParser(parser);
346        DateTimeParser[] parsers = new DateTimeParser[] {parser, null};
347        return append0(null, new MatchingParser(parsers));
348    }
349
350    //-----------------------------------------------------------------------
351    /**
352     * Checks if the parser is non null and a provider.
353     * 
354     * @param parser  the parser to check
355     */
356    private void checkParser(DateTimeParser parser) {
357        if (parser == null) {
358            throw new IllegalArgumentException("No parser supplied");
359        }
360    }
361
362    /**
363     * Checks if the printer is non null and a provider.
364     * 
365     * @param printer  the printer to check
366     */
367    private void checkPrinter(DateTimePrinter printer) {
368        if (printer == null) {
369            throw new IllegalArgumentException("No printer supplied");
370        }
371    }
372
373    private DateTimeFormatterBuilder append0(Object element) {
374        iFormatter = null;
375        // Add the element as both a printer and parser.
376        iElementPairs.add(element);
377        iElementPairs.add(element);
378        return this;
379    }
380
381    private DateTimeFormatterBuilder append0(
382            DateTimePrinter printer, DateTimeParser parser) {
383        iFormatter = null;
384        iElementPairs.add(printer);
385        iElementPairs.add(parser);
386        return this;
387    }
388
389    //-----------------------------------------------------------------------
390    /**
391     * Instructs the printer to emit a specific character, and the parser to
392     * expect it. The parser is case-insensitive.
393     *
394     * @return this DateTimeFormatterBuilder, for chaining
395     */
396    public DateTimeFormatterBuilder appendLiteral(char c) {
397        return append0(new CharacterLiteral(c));
398    }
399
400    /**
401     * Instructs the printer to emit specific text, and the parser to expect
402     * it. The parser is case-insensitive.
403     *
404     * @return this DateTimeFormatterBuilder, for chaining
405     * @throws IllegalArgumentException if text is null
406     */
407    public DateTimeFormatterBuilder appendLiteral(String text) {
408        if (text == null) {
409            throw new IllegalArgumentException("Literal must not be null");
410        }
411        switch (text.length()) {
412            case 0:
413                return this;
414            case 1:
415                return append0(new CharacterLiteral(text.charAt(0)));
416            default:
417                return append0(new StringLiteral(text));
418        }
419    }
420
421    /**
422     * Instructs the printer to emit a field value as a decimal number, and the
423     * parser to expect an unsigned decimal number.
424     *
425     * @param fieldType  type of field to append
426     * @param minDigits  minimum number of digits to <i>print</i>
427     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
428     * maximum number of digits to print
429     * @return this DateTimeFormatterBuilder, for chaining
430     * @throws IllegalArgumentException if field type is null
431     */
432    public DateTimeFormatterBuilder appendDecimal(
433            DateTimeFieldType fieldType, int minDigits, int maxDigits) {
434        if (fieldType == null) {
435            throw new IllegalArgumentException("Field type must not be null");
436        }
437        if (maxDigits < minDigits) {
438            maxDigits = minDigits;
439        }
440        if (minDigits < 0 || maxDigits <= 0) {
441            throw new IllegalArgumentException();
442        }
443        if (minDigits <= 1) {
444            return append0(new UnpaddedNumber(fieldType, maxDigits, false));
445        } else {
446            return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits));
447        }
448    }
449
450    /**
451     * Instructs the printer to emit a field value as a fixed-width decimal
452     * number (smaller numbers will be left-padded with zeros), and the parser
453     * to expect an unsigned decimal number with the same fixed width.
454     * 
455     * @param fieldType  type of field to append
456     * @param numDigits  the exact number of digits to parse or print, except if
457     * printed value requires more digits
458     * @return this DateTimeFormatterBuilder, for chaining
459     * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
460     * @since 1.5
461     */
462    public DateTimeFormatterBuilder appendFixedDecimal(
463            DateTimeFieldType fieldType, int numDigits) {
464        if (fieldType == null) {
465            throw new IllegalArgumentException("Field type must not be null");
466        }
467        if (numDigits <= 0) {
468            throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
469        }
470        return append0(new FixedNumber(fieldType, numDigits, false));
471    }
472
473    /**
474     * Instructs the printer to emit a field value as a decimal number, and the
475     * parser to expect a signed decimal number.
476     *
477     * @param fieldType  type of field to append
478     * @param minDigits  minimum number of digits to <i>print</i>
479     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
480     * maximum number of digits to print
481     * @return this DateTimeFormatterBuilder, for chaining
482     * @throws IllegalArgumentException if field type is null
483     */
484    public DateTimeFormatterBuilder appendSignedDecimal(
485            DateTimeFieldType fieldType, int minDigits, int maxDigits) {
486        if (fieldType == null) {
487            throw new IllegalArgumentException("Field type must not be null");
488        }
489        if (maxDigits < minDigits) {
490            maxDigits = minDigits;
491        }
492        if (minDigits < 0 || maxDigits <= 0) {
493            throw new IllegalArgumentException();
494        }
495        if (minDigits <= 1) {
496            return append0(new UnpaddedNumber(fieldType, maxDigits, true));
497        } else {
498            return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits));
499        }
500    }
501
502    /**
503     * Instructs the printer to emit a field value as a fixed-width decimal
504     * number (smaller numbers will be left-padded with zeros), and the parser
505     * to expect an signed decimal number with the same fixed width.
506     * 
507     * @param fieldType  type of field to append
508     * @param numDigits  the exact number of digits to parse or print, except if
509     * printed value requires more digits
510     * @return this DateTimeFormatterBuilder, for chaining
511     * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
512     * @since 1.5
513     */
514    public DateTimeFormatterBuilder appendFixedSignedDecimal(
515            DateTimeFieldType fieldType, int numDigits) {
516        if (fieldType == null) {
517            throw new IllegalArgumentException("Field type must not be null");
518        }
519        if (numDigits <= 0) {
520            throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
521        }
522        return append0(new FixedNumber(fieldType, numDigits, true));
523    }
524
525    /**
526     * Instructs the printer to emit a field value as text, and the
527     * parser to expect text.
528     *
529     * @param fieldType  type of field to append
530     * @return this DateTimeFormatterBuilder, for chaining
531     * @throws IllegalArgumentException if field type is null
532     */
533    public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) {
534        if (fieldType == null) {
535            throw new IllegalArgumentException("Field type must not be null");
536        }
537        return append0(new TextField(fieldType, false));
538    }
539
540    /**
541     * Instructs the printer to emit a field value as short text, and the
542     * parser to expect text.
543     *
544     * @param fieldType  type of field to append
545     * @return this DateTimeFormatterBuilder, for chaining
546     * @throws IllegalArgumentException if field type is null
547     */
548    public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) {
549        if (fieldType == null) {
550            throw new IllegalArgumentException("Field type must not be null");
551        }
552        return append0(new TextField(fieldType, true));
553    }
554
555    /**
556     * Instructs the printer to emit a remainder of time as a decimal fraction,
557     * without decimal point. For example, if the field is specified as
558     * minuteOfHour and the time is 12:30:45, the value printed is 75. A
559     * decimal point is implied, so the fraction is 0.75, or three-quarters of
560     * a minute.
561     *
562     * @param fieldType  type of field to append
563     * @param minDigits  minimum number of digits to print.
564     * @param maxDigits  maximum number of digits to print or parse.
565     * @return this DateTimeFormatterBuilder, for chaining
566     * @throws IllegalArgumentException if field type is null
567     */
568    public DateTimeFormatterBuilder appendFraction(
569            DateTimeFieldType fieldType, int minDigits, int maxDigits) {
570        if (fieldType == null) {
571            throw new IllegalArgumentException("Field type must not be null");
572        }
573        if (maxDigits < minDigits) {
574            maxDigits = minDigits;
575        }
576        if (minDigits < 0 || maxDigits <= 0) {
577            throw new IllegalArgumentException();
578        }
579        return append0(new Fraction(fieldType, minDigits, maxDigits));
580    }
581
582    /**
583     * Appends the print/parse of a fractional second.
584     * <p>
585     * This reliably handles the case where fractional digits are being handled
586     * beyond a visible decimal point. The digits parsed will always be treated
587     * as the most significant (numerically largest) digits.
588     * Thus '23' will be parsed as 230 milliseconds.
589     * Contrast this behaviour to {@link #appendMillisOfSecond}.
590     * This method does not print or parse the decimal point itself.
591     * 
592     * @param minDigits  minimum number of digits to print
593     * @param maxDigits  maximum number of digits to print or parse
594     * @return this DateTimeFormatterBuilder, for chaining
595     */
596    public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) {
597        return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits);
598    }
599
600    /**
601     * Appends the print/parse of a fractional minute.
602     * <p>
603     * This reliably handles the case where fractional digits are being handled
604     * beyond a visible decimal point. The digits parsed will always be treated
605     * as the most significant (numerically largest) digits.
606     * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds).
607     * This method does not print or parse the decimal point itself.
608     * 
609     * @param minDigits  minimum number of digits to print
610     * @param maxDigits  maximum number of digits to print or parse
611     * @return this DateTimeFormatterBuilder, for chaining
612     */
613    public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) {
614        return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits);
615    }
616
617    /**
618     * Appends the print/parse of a fractional hour.
619     * <p>
620     * This reliably handles the case where fractional digits are being handled
621     * beyond a visible decimal point. The digits parsed will always be treated
622     * as the most significant (numerically largest) digits.
623     * Thus '23' will be parsed as 0.23 hours (converted to milliseconds).
624     * This method does not print or parse the decimal point itself.
625     * 
626     * @param minDigits  minimum number of digits to print
627     * @param maxDigits  maximum number of digits to print or parse
628     * @return this DateTimeFormatterBuilder, for chaining
629     */
630    public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) {
631        return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits);
632    }
633
634    /**
635     * Appends the print/parse of a fractional day.
636     * <p>
637     * This reliably handles the case where fractional digits are being handled
638     * beyond a visible decimal point. The digits parsed will always be treated
639     * as the most significant (numerically largest) digits.
640     * Thus '23' will be parsed as 0.23 days (converted to milliseconds).
641     * This method does not print or parse the decimal point itself.
642     * 
643     * @param minDigits  minimum number of digits to print
644     * @param maxDigits  maximum number of digits to print or parse
645     * @return this DateTimeFormatterBuilder, for chaining
646     */
647    public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) {
648        return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits);
649    }
650
651    /**
652     * Instructs the printer to emit a numeric millisOfSecond field.
653     * <p>
654     * This method will append a field that prints a three digit value.
655     * During parsing the value that is parsed is assumed to be three digits.
656     * If less than three digits are present then they will be counted as the
657     * smallest parts of the millisecond. This is probably not what you want
658     * if you are using the field as a fraction. Instead, a fractional
659     * millisecond should be produced using {@link #appendFractionOfSecond}.
660     *
661     * @param minDigits  minimum number of digits to print
662     * @return this DateTimeFormatterBuilder, for chaining
663     */
664    public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
665        return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3);
666    }
667
668    /**
669     * Instructs the printer to emit a numeric millisOfDay field.
670     *
671     * @param minDigits  minimum number of digits to print
672     * @return this DateTimeFormatterBuilder, for chaining
673     */
674    public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
675        return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8);
676    }
677
678    /**
679     * Instructs the printer to emit a numeric secondOfMinute field.
680     *
681     * @param minDigits  minimum number of digits to print
682     * @return this DateTimeFormatterBuilder, for chaining
683     */
684    public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
685        return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2);
686    }
687
688    /**
689     * Instructs the printer to emit a numeric secondOfDay field.
690     *
691     * @param minDigits  minimum number of digits to print
692     * @return this DateTimeFormatterBuilder, for chaining
693     */
694    public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
695        return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5);
696    }
697
698    /**
699     * Instructs the printer to emit a numeric minuteOfHour field.
700     *
701     * @param minDigits  minimum number of digits to print
702     * @return this DateTimeFormatterBuilder, for chaining
703     */
704    public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
705        return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2);
706    }
707
708    /**
709     * Instructs the printer to emit a numeric minuteOfDay field.
710     *
711     * @param minDigits  minimum number of digits to print
712     * @return this DateTimeFormatterBuilder, for chaining
713     */
714    public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
715        return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4);
716    }
717
718    /**
719     * Instructs the printer to emit a numeric hourOfDay field.
720     *
721     * @param minDigits  minimum number of digits to print
722     * @return this DateTimeFormatterBuilder, for chaining
723     */
724    public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
725        return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2);
726    }
727
728    /**
729     * Instructs the printer to emit a numeric clockhourOfDay field.
730     *
731     * @param minDigits minimum number of digits to print
732     * @return this DateTimeFormatterBuilder, for chaining
733     */
734    public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
735        return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2);
736    }
737
738    /**
739     * Instructs the printer to emit a numeric hourOfHalfday field.
740     *
741     * @param minDigits  minimum number of digits to print
742     * @return this DateTimeFormatterBuilder, for chaining
743     */
744    public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
745        return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2);
746    }
747
748    /**
749     * Instructs the printer to emit a numeric clockhourOfHalfday field.
750     *
751     * @param minDigits  minimum number of digits to print
752     * @return this DateTimeFormatterBuilder, for chaining
753     */
754    public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) {
755        return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2);
756    }
757
758    /**
759     * Instructs the printer to emit a numeric dayOfWeek field.
760     *
761     * @param minDigits  minimum number of digits to print
762     * @return this DateTimeFormatterBuilder, for chaining
763     */
764    public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
765        return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1);
766    }
767
768    /**
769     * Instructs the printer to emit a numeric dayOfMonth field.
770     *
771     * @param minDigits  minimum number of digits to print
772     * @return this DateTimeFormatterBuilder, for chaining
773     */
774    public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
775        return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2);
776    }
777
778    /**
779     * Instructs the printer to emit a numeric dayOfYear field.
780     *
781     * @param minDigits  minimum number of digits to print
782     * @return this DateTimeFormatterBuilder, for chaining
783     */
784    public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
785        return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3);
786    }
787
788    /**
789     * Instructs the printer to emit a numeric weekOfWeekyear field.
790     *
791     * @param minDigits  minimum number of digits to print
792     * @return this DateTimeFormatterBuilder, for chaining
793     */
794    public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
795        return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2);
796    }
797
798    /**
799     * Instructs the printer to emit a numeric weekyear field.
800     *
801     * @param minDigits  minimum number of digits to <i>print</i>
802     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
803     * maximum number of digits to print
804     * @return this DateTimeFormatterBuilder, for chaining
805     */
806    public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) {
807        return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits);
808    }
809
810    /**
811     * Instructs the printer to emit a numeric monthOfYear field.
812     *
813     * @param minDigits  minimum number of digits to print
814     * @return this DateTimeFormatterBuilder, for chaining
815     */
816    public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
817        return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2);
818    }
819
820    /**
821     * Instructs the printer to emit a numeric year field.
822     *
823     * @param minDigits  minimum number of digits to <i>print</i>
824     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
825     * maximum number of digits to print
826     * @return this DateTimeFormatterBuilder, for chaining
827     */
828    public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) {
829        return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits);
830    }
831
832    /**
833     * Instructs the printer to emit a numeric year field which always prints
834     * and parses two digits. A pivot year is used during parsing to determine
835     * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
836     *
837     * <pre>
838     * pivot   supported range   00 is   20 is   40 is   60 is   80 is
839     * ---------------------------------------------------------------
840     * 1950      1900..1999      1900    1920    1940    1960    1980
841     * 1975      1925..2024      2000    2020    1940    1960    1980
842     * 2000      1950..2049      2000    2020    2040    1960    1980
843     * 2025      1975..2074      2000    2020    2040    2060    1980
844     * 2050      2000..2099      2000    2020    2040    2060    2080
845     * </pre>
846     *
847     * @param pivot  pivot year to use when parsing
848     * @return this DateTimeFormatterBuilder, for chaining
849     */
850    public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
851        return appendTwoDigitYear(pivot, false);
852    }
853
854    /**
855     * Instructs the printer to emit a numeric year field which always prints
856     * two digits. A pivot year is used during parsing to determine the range
857     * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
858     * parse is instructed to be lenient and the digit count is not two, it is
859     * treated as an absolute year. With lenient parsing, specifying a positive
860     * or negative sign before the year also makes it absolute.
861     *
862     * @param pivot  pivot year to use when parsing
863     * @param lenientParse  when true, if digit count is not two, it is treated
864     * as an absolute year
865     * @return this DateTimeFormatterBuilder, for chaining
866     * @since 1.1
867     */
868    public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) {
869        return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse));
870    }
871
872    /**
873     * Instructs the printer to emit a numeric weekyear field which always prints
874     * and parses two digits. A pivot year is used during parsing to determine
875     * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
876     *
877     * <pre>
878     * pivot   supported range   00 is   20 is   40 is   60 is   80 is
879     * ---------------------------------------------------------------
880     * 1950      1900..1999      1900    1920    1940    1960    1980
881     * 1975      1925..2024      2000    2020    1940    1960    1980
882     * 2000      1950..2049      2000    2020    2040    1960    1980
883     * 2025      1975..2074      2000    2020    2040    2060    1980
884     * 2050      2000..2099      2000    2020    2040    2060    2080
885     * </pre>
886     *
887     * @param pivot  pivot weekyear to use when parsing
888     * @return this DateTimeFormatterBuilder, for chaining
889     */
890    public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
891        return appendTwoDigitWeekyear(pivot, false);
892    }
893
894    /**
895     * Instructs the printer to emit a numeric weekyear field which always prints
896     * two digits. A pivot year is used during parsing to determine the range
897     * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
898     * parse is instructed to be lenient and the digit count is not two, it is
899     * treated as an absolute weekyear. With lenient parsing, specifying a positive
900     * or negative sign before the weekyear also makes it absolute.
901     *
902     * @param pivot  pivot weekyear to use when parsing
903     * @param lenientParse  when true, if digit count is not two, it is treated
904     * as an absolute weekyear
905     * @return this DateTimeFormatterBuilder, for chaining
906     * @since 1.1
907     */
908    public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) {
909        return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse));
910    }
911
912    /**
913     * Instructs the printer to emit a numeric yearOfEra field.
914     *
915     * @param minDigits  minimum number of digits to <i>print</i>
916     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
917     * maximum number of digits to print
918     * @return this DateTimeFormatterBuilder, for chaining
919     */
920    public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) {
921        return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits);
922    }
923
924    /**
925     * Instructs the printer to emit a numeric year of century field.
926     *
927     * @param minDigits  minimum number of digits to print
928     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
929     * maximum number of digits to print
930     * @return this DateTimeFormatterBuilder, for chaining
931     */
932    public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) {
933        return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits);
934    }
935
936    /**
937     * Instructs the printer to emit a numeric century of era field.
938     *
939     * @param minDigits  minimum number of digits to print
940     * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
941     * maximum number of digits to print
942     * @return this DateTimeFormatterBuilder, for chaining
943     */
944    public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) {
945        return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits);
946    }
947
948    /**
949     * Instructs the printer to emit a locale-specific AM/PM text, and the
950     * parser to expect it. The parser is case-insensitive.
951     *
952     * @return this DateTimeFormatterBuilder, for chaining
953     */
954    public DateTimeFormatterBuilder appendHalfdayOfDayText() {
955        return appendText(DateTimeFieldType.halfdayOfDay());
956    }
957
958    /**
959     * Instructs the printer to emit a locale-specific dayOfWeek text. The
960     * parser will accept a long or short dayOfWeek text, case-insensitive.
961     *
962     * @return this DateTimeFormatterBuilder, for chaining
963     */
964    public DateTimeFormatterBuilder appendDayOfWeekText() {
965        return appendText(DateTimeFieldType.dayOfWeek());
966    }
967
968    /**
969     * Instructs the printer to emit a short locale-specific dayOfWeek
970     * text. The parser will accept a long or short dayOfWeek text,
971     * case-insensitive.
972     *
973     * @return this DateTimeFormatterBuilder, for chaining
974     */
975    public DateTimeFormatterBuilder appendDayOfWeekShortText() {
976        return appendShortText(DateTimeFieldType.dayOfWeek());
977    }
978
979    /**
980     * Instructs the printer to emit a short locale-specific monthOfYear
981     * text. The parser will accept a long or short monthOfYear text,
982     * case-insensitive.
983     *
984     * @return this DateTimeFormatterBuilder, for chaining
985     */
986    public DateTimeFormatterBuilder appendMonthOfYearText() { 
987        return appendText(DateTimeFieldType.monthOfYear());
988    }
989
990    /**
991     * Instructs the printer to emit a locale-specific monthOfYear text. The
992     * parser will accept a long or short monthOfYear text, case-insensitive.
993     *
994     * @return this DateTimeFormatterBuilder, for chaining
995     */
996    public DateTimeFormatterBuilder appendMonthOfYearShortText() {
997        return appendShortText(DateTimeFieldType.monthOfYear());
998    }
999
1000    /**
1001     * Instructs the printer to emit a locale-specific era text (BC/AD), and
1002     * the parser to expect it. The parser is case-insensitive.
1003     *
1004     * @return this DateTimeFormatterBuilder, for chaining
1005     */
1006    public DateTimeFormatterBuilder appendEraText() {
1007        return appendText(DateTimeFieldType.era());
1008    }
1009
1010    /**
1011     * Instructs the printer to emit a locale-specific time zone name.
1012     * Using this method prevents parsing, because time zone names are not unique.
1013     * See {@link #appendTimeZoneName(Map)}.
1014     *
1015     * @return this DateTimeFormatterBuilder, for chaining
1016     */
1017    public DateTimeFormatterBuilder appendTimeZoneName() {
1018        return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null);
1019    }
1020
1021    /**
1022     * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing.
1023     * Time zone names are not unique, thus the API forces you to supply the lookup.
1024     * The names are searched in the order of the map, thus it is strongly recommended
1025     * to use a {@code LinkedHashMap} or similar.
1026     *
1027     * @param parseLookup  the table of names, not null
1028     * @return this DateTimeFormatterBuilder, for chaining
1029     */
1030    public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) {
1031        TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup);
1032        return append0(pp, pp);
1033    }
1034
1035    /**
1036     * Instructs the printer to emit a short locale-specific time zone name.
1037     * Using this method prevents parsing, because time zone names are not unique.
1038     * See {@link #appendTimeZoneShortName(Map)}.
1039     *
1040     * @return this DateTimeFormatterBuilder, for chaining
1041     */
1042    public DateTimeFormatterBuilder appendTimeZoneShortName() {
1043        return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null);
1044    }
1045
1046    /**
1047     * Instructs the printer to emit a short locale-specific time zone
1048     * name, providing a lookup for parsing.
1049     * Time zone names are not unique, thus the API forces you to supply the lookup.
1050     * The names are searched in the order of the map, thus it is strongly recommended
1051     * to use a {@code LinkedHashMap} or similar.
1052     *
1053     * @param parseLookup  the table of names, null to use the {@link DateTimeUtils#getDefaultTimeZoneNames() default names}
1054     * @return this DateTimeFormatterBuilder, for chaining
1055     */
1056    public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) {
1057        TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup);
1058        return append0(pp, pp);
1059    }
1060
1061    /**
1062     * Instructs the printer to emit the identifier of the time zone.
1063     * From version 2.0, this field can be parsed.
1064     *
1065     * @return this DateTimeFormatterBuilder, for chaining
1066     */
1067    public DateTimeFormatterBuilder appendTimeZoneId() {
1068        return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE);
1069    }
1070
1071    /**
1072     * Instructs the printer to emit text and numbers to display time zone
1073     * offset from UTC. A parser will use the parsed time zone offset to adjust
1074     * the datetime.
1075     * <p>
1076     * If zero offset text is supplied, then it will be printed when the zone is zero.
1077     * During parsing, either the zero offset text, or the offset will be parsed.
1078     *
1079     * @param zeroOffsetText  the text to use if time zone offset is zero. If
1080     * null, offset is always shown.
1081     * @param showSeparators  if true, prints ':' separator before minute and
1082     * second field and prints '.' separator before fraction field.
1083     * @param minFields  minimum number of fields to print, stopping when no
1084     * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1085     * @param maxFields  maximum number of fields to print
1086     * @return this DateTimeFormatterBuilder, for chaining
1087     */
1088    public DateTimeFormatterBuilder appendTimeZoneOffset(
1089            String zeroOffsetText, boolean showSeparators,
1090            int minFields, int maxFields) {
1091        return append0(new TimeZoneOffset
1092                       (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields));
1093    }
1094
1095    /**
1096     * Instructs the printer to emit text and numbers to display time zone
1097     * offset from UTC. A parser will use the parsed time zone offset to adjust
1098     * the datetime.
1099     * <p>
1100     * If zero offset print text is supplied, then it will be printed when the zone is zero.
1101     * If zero offset parse text is supplied, then either it or the offset will be parsed.
1102     *
1103     * @param zeroOffsetPrintText  the text to print if time zone offset is zero. If
1104     * null, offset is always shown.
1105     * @param zeroOffsetParseText  the text to optionally parse to indicate that the time
1106     * zone offset is zero. If null, then always use the offset.
1107     * @param showSeparators  if true, prints ':' separator before minute and
1108     * second field and prints '.' separator before fraction field.
1109     * @param minFields  minimum number of fields to print, stopping when no
1110     * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1111     * @param maxFields  maximum number of fields to print
1112     * @return this DateTimeFormatterBuilder, for chaining
1113     * @since 2.0
1114     */
1115    public DateTimeFormatterBuilder appendTimeZoneOffset(
1116            String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators,
1117            int minFields, int maxFields) {
1118        return append0(new TimeZoneOffset
1119                       (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields));
1120    }
1121
1122    //-----------------------------------------------------------------------
1123    /**
1124     * Calls upon {@link DateTimeFormat} to parse the pattern and append the
1125     * results into this builder.
1126     *
1127     * @param pattern  pattern specification
1128     * @throws IllegalArgumentException if the pattern is invalid
1129     * @see DateTimeFormat
1130     */
1131    public DateTimeFormatterBuilder appendPattern(String pattern) {
1132        DateTimeFormat.appendPatternTo(this, pattern);
1133        return this;
1134    }
1135
1136    //-----------------------------------------------------------------------
1137    private Object getFormatter() {
1138        Object f = iFormatter;
1139
1140        if (f == null) {
1141            if (iElementPairs.size() == 2) {
1142                Object printer = iElementPairs.get(0);
1143                Object parser = iElementPairs.get(1);
1144
1145                if (printer != null) {
1146                    if (printer == parser || parser == null) {
1147                        f = printer;
1148                    }
1149                } else {
1150                    f = parser;
1151                }
1152            }
1153
1154            if (f == null) {
1155                f = new Composite(iElementPairs);
1156            }
1157
1158            iFormatter = f;
1159        }
1160
1161        return f;
1162    }
1163
1164    private boolean isPrinter(Object f) {
1165        if (f instanceof DateTimePrinter) {
1166            if (f instanceof Composite) {
1167                return ((Composite)f).isPrinter();
1168            }
1169            return true;
1170        }
1171        return false;
1172    }
1173
1174    private boolean isParser(Object f) {
1175        if (f instanceof DateTimeParser) {
1176            if (f instanceof Composite) {
1177                return ((Composite)f).isParser();
1178            }
1179            return true;
1180        }
1181        return false;
1182    }
1183
1184    private boolean isFormatter(Object f) {
1185        return (isPrinter(f) || isParser(f));
1186    }
1187
1188    static void appendUnknownString(StringBuffer buf, int len) {
1189        for (int i = len; --i >= 0;) {
1190            buf.append('\ufffd');
1191        }
1192    }
1193
1194    static void printUnknownString(Writer out, int len) throws IOException {
1195        for (int i = len; --i >= 0;) {
1196            out.write('\ufffd');
1197        }
1198    }
1199
1200    //-----------------------------------------------------------------------
1201    static class CharacterLiteral
1202            implements DateTimePrinter, DateTimeParser {
1203
1204        private final char iValue;
1205
1206        CharacterLiteral(char value) {
1207            super();
1208            iValue = value;
1209        }
1210
1211        public int estimatePrintedLength() {
1212            return 1;
1213        }
1214
1215        public void printTo(
1216                StringBuffer buf, long instant, Chronology chrono,
1217                int displayOffset, DateTimeZone displayZone, Locale locale) {
1218            buf.append(iValue);
1219        }
1220
1221        public void printTo(
1222                Writer out, long instant, Chronology chrono,
1223                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1224            out.write(iValue);
1225        }
1226
1227        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1228            buf.append(iValue);
1229        }
1230
1231        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1232            out.write(iValue);
1233        }
1234
1235        public int estimateParsedLength() {
1236            return 1;
1237        }
1238
1239        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1240            if (position >= text.length()) {
1241                return ~position;
1242            }
1243
1244            char a = text.charAt(position);
1245            char b = iValue;
1246
1247            if (a != b) {
1248                a = Character.toUpperCase(a);
1249                b = Character.toUpperCase(b);
1250                if (a != b) {
1251                    a = Character.toLowerCase(a);
1252                    b = Character.toLowerCase(b);
1253                    if (a != b) {
1254                        return ~position;
1255                    }
1256                }
1257            }
1258
1259            return position + 1;
1260        }
1261    }
1262
1263    //-----------------------------------------------------------------------
1264    static class StringLiteral
1265            implements DateTimePrinter, DateTimeParser {
1266
1267        private final String iValue;
1268
1269        StringLiteral(String value) {
1270            super();
1271            iValue = value;
1272        }
1273
1274        public int estimatePrintedLength() {
1275            return iValue.length();
1276        }
1277
1278        public void printTo(
1279                StringBuffer buf, long instant, Chronology chrono,
1280                int displayOffset, DateTimeZone displayZone, Locale locale) {
1281            buf.append(iValue);
1282        }
1283
1284        public void printTo(
1285                Writer out, long instant, Chronology chrono,
1286                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1287            out.write(iValue);
1288        }
1289
1290        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1291            buf.append(iValue);
1292        }
1293
1294        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1295            out.write(iValue);
1296        }
1297
1298        public int estimateParsedLength() {
1299            return iValue.length();
1300        }
1301
1302        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1303            if (text.regionMatches(true, position, iValue, 0, iValue.length())) {
1304                return position + iValue.length();
1305            }
1306            return ~position;
1307        }
1308    }
1309
1310    //-----------------------------------------------------------------------
1311    static abstract class NumberFormatter
1312            implements DateTimePrinter, DateTimeParser {
1313        protected final DateTimeFieldType iFieldType;
1314        protected final int iMaxParsedDigits;
1315        protected final boolean iSigned;
1316
1317        NumberFormatter(DateTimeFieldType fieldType,
1318                int maxParsedDigits, boolean signed) {
1319            super();
1320            iFieldType = fieldType;
1321            iMaxParsedDigits = maxParsedDigits;
1322            iSigned = signed;
1323        }
1324
1325        public int estimateParsedLength() {
1326            return iMaxParsedDigits;
1327        }
1328
1329        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1330            int limit = Math.min(iMaxParsedDigits, text.length() - position);
1331
1332            boolean negative = false;
1333            int length = 0;
1334            while (length < limit) {
1335                char c = text.charAt(position + length);
1336                if (length == 0 && (c == '-' || c == '+') && iSigned) {
1337                    negative = c == '-';
1338
1339                    // Next character must be a digit.
1340                    if (length + 1 >= limit || 
1341                        (c = text.charAt(position + length + 1)) < '0' || c > '9')
1342                    {
1343                        break;
1344                    }
1345
1346                    if (negative) {
1347                        length++;
1348                    } else {
1349                        // Skip the '+' for parseInt to succeed.
1350                        position++;
1351                    }
1352                    // Expand the limit to disregard the sign character.
1353                    limit = Math.min(limit + 1, text.length() - position);
1354                    continue;
1355                }
1356                if (c < '0' || c > '9') {
1357                    break;
1358                }
1359                length++;
1360            }
1361
1362            if (length == 0) {
1363                return ~position;
1364            }
1365
1366            int value;
1367            if (length >= 9) {
1368                // Since value may exceed integer limits, use stock parser
1369                // which checks for this.
1370                value = Integer.parseInt(text.substring(position, position += length));
1371            } else {
1372                int i = position;
1373                if (negative) {
1374                    i++;
1375                }
1376                try {
1377                    value = text.charAt(i++) - '0';
1378                } catch (StringIndexOutOfBoundsException e) {
1379                    return ~position;
1380                }
1381                position += length;
1382                while (i < position) {
1383                    value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1384                }
1385                if (negative) {
1386                    value = -value;
1387                }
1388            }
1389
1390            bucket.saveField(iFieldType, value);
1391            return position;
1392        }
1393    }
1394
1395    //-----------------------------------------------------------------------
1396    static class UnpaddedNumber extends NumberFormatter {
1397
1398        protected UnpaddedNumber(DateTimeFieldType fieldType,
1399                       int maxParsedDigits, boolean signed)
1400        {
1401            super(fieldType, maxParsedDigits, signed);
1402        }
1403
1404        public int estimatePrintedLength() {
1405            return iMaxParsedDigits;
1406        }
1407
1408        public void printTo(
1409                StringBuffer buf, long instant, Chronology chrono,
1410                int displayOffset, DateTimeZone displayZone, Locale locale) {
1411            try {
1412                DateTimeField field = iFieldType.getField(chrono);
1413                FormatUtils.appendUnpaddedInteger(buf, field.get(instant));
1414            } catch (RuntimeException e) {
1415                buf.append('\ufffd');
1416            }
1417        }
1418
1419        public void printTo(
1420                Writer out, long instant, Chronology chrono,
1421                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1422            try {
1423                DateTimeField field = iFieldType.getField(chrono);
1424                FormatUtils.writeUnpaddedInteger(out, field.get(instant));
1425            } catch (RuntimeException e) {
1426                out.write('\ufffd');
1427            }
1428        }
1429
1430        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1431            if (partial.isSupported(iFieldType)) {
1432                try {
1433                    FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType));
1434                } catch (RuntimeException e) {
1435                    buf.append('\ufffd');
1436                }
1437            } else {
1438                buf.append('\ufffd');
1439            }
1440        }
1441
1442        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1443            if (partial.isSupported(iFieldType)) {
1444                try {
1445                    FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType));
1446                } catch (RuntimeException e) {
1447                    out.write('\ufffd');
1448                }
1449            } else {
1450                out.write('\ufffd');
1451            }
1452        }
1453    }
1454
1455    //-----------------------------------------------------------------------
1456    static class PaddedNumber extends NumberFormatter {
1457
1458        protected final int iMinPrintedDigits;
1459
1460        protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1461                     boolean signed, int minPrintedDigits)
1462        {
1463            super(fieldType, maxParsedDigits, signed);
1464            iMinPrintedDigits = minPrintedDigits;
1465        }
1466
1467        public int estimatePrintedLength() {
1468            return iMaxParsedDigits;
1469        }
1470
1471        public void printTo(
1472                StringBuffer buf, long instant, Chronology chrono,
1473                int displayOffset, DateTimeZone displayZone, Locale locale) {
1474            try {
1475                DateTimeField field = iFieldType.getField(chrono);
1476                FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits);
1477            } catch (RuntimeException e) {
1478                appendUnknownString(buf, iMinPrintedDigits);
1479            }
1480        }
1481
1482        public void printTo(
1483                Writer out, long instant, Chronology chrono,
1484                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1485            try {
1486                DateTimeField field = iFieldType.getField(chrono);
1487                FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits);
1488            } catch (RuntimeException e) {
1489                printUnknownString(out, iMinPrintedDigits);
1490            }
1491        }
1492
1493        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1494            if (partial.isSupported(iFieldType)) {
1495                try {
1496                    FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits);
1497                } catch (RuntimeException e) {
1498                    appendUnknownString(buf, iMinPrintedDigits);
1499                }
1500            } else {
1501                appendUnknownString(buf, iMinPrintedDigits);
1502            }
1503        }
1504
1505        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1506            if (partial.isSupported(iFieldType)) {
1507                try {
1508                    FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits);
1509                } catch (RuntimeException e) {
1510                    printUnknownString(out, iMinPrintedDigits);
1511                }
1512            } else {
1513                printUnknownString(out, iMinPrintedDigits);
1514            }
1515        }
1516    }
1517
1518    //-----------------------------------------------------------------------
1519    static class FixedNumber extends PaddedNumber {
1520
1521        protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) {
1522            super(fieldType, numDigits, signed, numDigits);
1523        }
1524
1525        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1526            int newPos = super.parseInto(bucket, text, position);
1527            if (newPos < 0) {
1528                return newPos;
1529            }
1530            int expectedPos = position + iMaxParsedDigits;
1531            if (newPos != expectedPos) {
1532                if (iSigned) {
1533                    char c = text.charAt(position);
1534                    if (c == '-' || c == '+') {
1535                        expectedPos++;
1536                    }
1537                }
1538                if (newPos > expectedPos) {
1539                    // The failure is at the position of the first extra digit.
1540                    return ~(expectedPos + 1);
1541                } else if (newPos < expectedPos) {
1542                    // The failure is at the position where the next digit should be.
1543                    return ~newPos;
1544                }
1545            }
1546            return newPos;
1547        }
1548    }
1549
1550    //-----------------------------------------------------------------------
1551    static class TwoDigitYear
1552            implements DateTimePrinter, DateTimeParser {
1553
1554        /** The field to print/parse. */
1555        private final DateTimeFieldType iType;
1556        /** The pivot year. */
1557        private final int iPivot;
1558        private final boolean iLenientParse;
1559
1560        TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1561            super();
1562            iType = type;
1563            iPivot = pivot;
1564            iLenientParse = lenientParse;
1565        }
1566
1567        public int estimateParsedLength() {
1568            return iLenientParse ? 4 : 2;
1569        }
1570
1571        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1572            int limit = text.length() - position;
1573
1574            if (!iLenientParse) {
1575                limit = Math.min(2, limit);
1576                if (limit < 2) {
1577                    return ~position;
1578                }
1579            } else {
1580                boolean hasSignChar = false;
1581                boolean negative = false;
1582                int length = 0;
1583                while (length < limit) {
1584                    char c = text.charAt(position + length);
1585                    if (length == 0 && (c == '-' || c == '+')) {
1586                        hasSignChar = true;
1587                        negative = c == '-';
1588                        if (negative) {
1589                            length++;
1590                        } else {
1591                            // Skip the '+' for parseInt to succeed.
1592                            position++;
1593                            limit--;
1594                        }
1595                        continue;
1596                    }
1597                    if (c < '0' || c > '9') {
1598                        break;
1599                    }
1600                    length++;
1601                }
1602                
1603                if (length == 0) {
1604                    return ~position;
1605                }
1606
1607                if (hasSignChar || length != 2) {
1608                    int value;
1609                    if (length >= 9) {
1610                        // Since value may exceed integer limits, use stock
1611                        // parser which checks for this.
1612                        value = Integer.parseInt(text.substring(position, position += length));
1613                    } else {
1614                        int i = position;
1615                        if (negative) {
1616                            i++;
1617                        }
1618                        try {
1619                            value = text.charAt(i++) - '0';
1620                        } catch (StringIndexOutOfBoundsException e) {
1621                            return ~position;
1622                        }
1623                        position += length;
1624                        while (i < position) {
1625                            value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1626                        }
1627                        if (negative) {
1628                            value = -value;
1629                        }
1630                    }
1631                    
1632                    bucket.saveField(iType, value);
1633                    return position;
1634                }
1635            }
1636
1637            int year;
1638            char c = text.charAt(position);
1639            if (c < '0' || c > '9') {
1640                return ~position;
1641            }
1642            year = c - '0';
1643            c = text.charAt(position + 1);
1644            if (c < '0' || c > '9') {
1645                return ~position;
1646            }
1647            year = ((year << 3) + (year << 1)) + c - '0';
1648
1649            int pivot = iPivot;
1650            // If the bucket pivot year is non-null, use that when parsing
1651            if (bucket.getPivotYear() != null) {
1652                pivot = bucket.getPivotYear().intValue();
1653            }
1654
1655            int low = pivot - 50;
1656
1657            int t;
1658            if (low >= 0) {
1659                t = low % 100;
1660            } else {
1661                t = 99 + ((low + 1) % 100);
1662            }
1663
1664            year += low + ((year < t) ? 100 : 0) - t;
1665
1666            bucket.saveField(iType, year);
1667            return position + 2;
1668        }
1669        
1670        public int estimatePrintedLength() {
1671            return 2;
1672        }
1673
1674        public void printTo(
1675                StringBuffer buf, long instant, Chronology chrono,
1676                int displayOffset, DateTimeZone displayZone, Locale locale) {
1677            int year = getTwoDigitYear(instant, chrono);
1678            if (year < 0) {
1679                buf.append('\ufffd');
1680                buf.append('\ufffd');
1681            } else {
1682                FormatUtils.appendPaddedInteger(buf, year, 2);
1683            }
1684        }
1685
1686        public void printTo(
1687                Writer out, long instant, Chronology chrono,
1688                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1689            int year = getTwoDigitYear(instant, chrono);
1690            if (year < 0) {
1691                out.write('\ufffd');
1692                out.write('\ufffd');
1693            } else {
1694                FormatUtils.writePaddedInteger(out, year, 2);
1695            }
1696        }
1697
1698        private int getTwoDigitYear(long instant, Chronology chrono) {
1699            try {
1700                int year = iType.getField(chrono).get(instant);
1701                if (year < 0) {
1702                    year = -year;
1703                }
1704                return year % 100;
1705            } catch (RuntimeException e) {
1706                return -1;
1707            }
1708        }
1709
1710        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1711            int year = getTwoDigitYear(partial);
1712            if (year < 0) {
1713                buf.append('\ufffd');
1714                buf.append('\ufffd');
1715            } else {
1716                FormatUtils.appendPaddedInteger(buf, year, 2);
1717            }
1718        }
1719
1720        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1721            int year = getTwoDigitYear(partial);
1722            if (year < 0) {
1723                out.write('\ufffd');
1724                out.write('\ufffd');
1725            } else {
1726                FormatUtils.writePaddedInteger(out, year, 2);
1727            }
1728        }
1729
1730        private int getTwoDigitYear(ReadablePartial partial) {
1731            if (partial.isSupported(iType)) {
1732                try {
1733                    int year = partial.get(iType);
1734                    if (year < 0) {
1735                        year = -year;
1736                    }
1737                    return year % 100;
1738                } catch (RuntimeException e) {}
1739            } 
1740            return -1;
1741        }
1742    }
1743
1744    //-----------------------------------------------------------------------
1745    static class TextField
1746            implements DateTimePrinter, DateTimeParser {
1747
1748        private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache =
1749                    new HashMap<Locale, Map<DateTimeFieldType, Object[]>>();
1750        private final DateTimeFieldType iFieldType;
1751        private final boolean iShort;
1752
1753        TextField(DateTimeFieldType fieldType, boolean isShort) {
1754            super();
1755            iFieldType = fieldType;
1756            iShort = isShort;
1757        }
1758
1759        public int estimatePrintedLength() {
1760            return iShort ? 6 : 20;
1761        }
1762
1763        public void printTo(
1764                StringBuffer buf, long instant, Chronology chrono,
1765                int displayOffset, DateTimeZone displayZone, Locale locale) {
1766            try {
1767                buf.append(print(instant, chrono, locale));
1768            } catch (RuntimeException e) {
1769                buf.append('\ufffd');
1770            }
1771        }
1772
1773        public void printTo(
1774                Writer out, long instant, Chronology chrono,
1775                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1776            try {
1777                out.write(print(instant, chrono, locale));
1778            } catch (RuntimeException e) {
1779                out.write('\ufffd');
1780            }
1781        }
1782
1783        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1784            try {
1785                buf.append(print(partial, locale));
1786            } catch (RuntimeException e) {
1787                buf.append('\ufffd');
1788            }
1789        }
1790
1791        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1792            try {
1793                out.write(print(partial, locale));
1794            } catch (RuntimeException e) {
1795                out.write('\ufffd');
1796            }
1797        }
1798
1799        private String print(long instant, Chronology chrono, Locale locale) {
1800            DateTimeField field = iFieldType.getField(chrono);
1801            if (iShort) {
1802                return field.getAsShortText(instant, locale);
1803            } else {
1804                return field.getAsText(instant, locale);
1805            }
1806        }
1807
1808        private String print(ReadablePartial partial, Locale locale) {
1809            if (partial.isSupported(iFieldType)) {
1810                DateTimeField field = iFieldType.getField(partial.getChronology());
1811                if (iShort) {
1812                    return field.getAsShortText(partial, locale);
1813                } else {
1814                    return field.getAsText(partial, locale);
1815                }
1816            } else {
1817                return "\ufffd";
1818            }
1819        }
1820
1821        public int estimateParsedLength() {
1822            return estimatePrintedLength();
1823        }
1824
1825        @SuppressWarnings("unchecked")
1826        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1827            Locale locale = bucket.getLocale();
1828            // handle languages which might have non ASCII A-Z or punctuation
1829            // bug 1788282
1830            Set<String> validValues = null;
1831            int maxLength = 0;
1832            synchronized (cParseCache) {
1833                Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale);
1834                if (innerMap == null) {
1835                    innerMap = new HashMap<DateTimeFieldType, Object[]>();
1836                    cParseCache.put(locale, innerMap);
1837                }
1838                Object[] array = innerMap.get(iFieldType);
1839                if (array == null) {
1840                    validValues = new HashSet<String>(32);
1841                    MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC);
1842                    Property property = dt.property(iFieldType);
1843                    int min = property.getMinimumValueOverall();
1844                    int max = property.getMaximumValueOverall();
1845                    if (max - min > 32) {  // protect against invalid fields
1846                        return ~position;
1847                    }
1848                    maxLength = property.getMaximumTextLength(locale);
1849                    for (int i = min; i <= max; i++) {
1850                        property.set(i);
1851                        validValues.add(property.getAsShortText(locale));
1852                        validValues.add(property.getAsShortText(locale).toLowerCase(locale));
1853                        validValues.add(property.getAsShortText(locale).toUpperCase(locale));
1854                        validValues.add(property.getAsText(locale));
1855                        validValues.add(property.getAsText(locale).toLowerCase(locale));
1856                        validValues.add(property.getAsText(locale).toUpperCase(locale));
1857                    }
1858                    if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) {
1859                        // hack to support for parsing "BCE" and "CE" if the language is English
1860                        validValues.add("BCE");
1861                        validValues.add("bce");
1862                        validValues.add("CE");
1863                        validValues.add("ce");
1864                        maxLength = 3;
1865                    }
1866                    array = new Object[] {validValues, Integer.valueOf(maxLength)};
1867                    innerMap.put(iFieldType, array);
1868                } else {
1869                    validValues = (Set<String>) array[0];
1870                    maxLength = ((Integer) array[1]).intValue();
1871                }
1872            }
1873            // match the longest string first using our knowledge of the max length
1874            int limit = Math.min(text.length(), position + maxLength);
1875            for (int i = limit; i > position; i--) {
1876                String match = text.substring(position, i);
1877                if (validValues.contains(match)) {
1878                    bucket.saveField(iFieldType, match, locale);
1879                    return i;
1880                }
1881            }
1882            return ~position;
1883        }
1884    }
1885
1886    //-----------------------------------------------------------------------
1887    static class Fraction
1888            implements DateTimePrinter, DateTimeParser {
1889
1890        private final DateTimeFieldType iFieldType;
1891        protected int iMinDigits;
1892        protected int iMaxDigits;
1893
1894        protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1895            super();
1896            iFieldType = fieldType;
1897            // Limit the precision requirements.
1898            if (maxDigits > 18) {
1899                maxDigits = 18;
1900            }
1901            iMinDigits = minDigits;
1902            iMaxDigits = maxDigits;
1903        }
1904
1905        public int estimatePrintedLength() {
1906            return iMaxDigits;
1907        }
1908
1909        public void printTo(
1910                StringBuffer buf, long instant, Chronology chrono,
1911                int displayOffset, DateTimeZone displayZone, Locale locale) {
1912            try {
1913                printTo(buf, null, instant, chrono);
1914            } catch (IOException e) {
1915                // Not gonna happen.
1916            }
1917        }
1918
1919        public void printTo(
1920                Writer out, long instant, Chronology chrono,
1921                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1922            printTo(null, out, instant, chrono);
1923        }
1924
1925        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1926            // removed check whether field is supported, as input field is typically
1927            // secondOfDay which is unsupported by TimeOfDay
1928            long millis = partial.getChronology().set(partial, 0L);
1929            try {
1930                printTo(buf, null, millis, partial.getChronology());
1931            } catch (IOException e) {
1932                // Not gonna happen.
1933            }
1934        }
1935
1936        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1937            // removed check whether field is supported, as input field is typically
1938            // secondOfDay which is unsupported by TimeOfDay
1939            long millis = partial.getChronology().set(partial, 0L);
1940            printTo(null, out, millis, partial.getChronology());
1941        }
1942
1943        protected void printTo(StringBuffer buf, Writer out, long instant, Chronology chrono)
1944            throws IOException
1945        {
1946            DateTimeField field = iFieldType.getField(chrono);
1947            int minDigits = iMinDigits;
1948
1949            long fraction;
1950            try {
1951                fraction = field.remainder(instant);
1952            } catch (RuntimeException e) {
1953                if (buf != null) {
1954                    appendUnknownString(buf, minDigits);
1955                } else {
1956                    printUnknownString(out, minDigits);
1957                }
1958                return;
1959            }
1960
1961            if (fraction == 0) {
1962                if (buf != null) {
1963                    while (--minDigits >= 0) {
1964                        buf.append('0');
1965                    }
1966                } else {
1967                    while (--minDigits >= 0) {
1968                        out.write('0');
1969                    }
1970                }
1971                return;
1972            }
1973
1974            String str;
1975            long[] fractionData = getFractionData(fraction, field);
1976            long scaled = fractionData[0];
1977            int maxDigits = (int) fractionData[1];
1978            
1979            if ((scaled & 0x7fffffff) == scaled) {
1980                str = Integer.toString((int) scaled);
1981            } else {
1982                str = Long.toString(scaled);
1983            }
1984
1985            int length = str.length();
1986            int digits = maxDigits;
1987            while (length < digits) {
1988                if (buf != null) {
1989                    buf.append('0');
1990                } else {
1991                    out.write('0');
1992                }
1993                minDigits--;
1994                digits--;
1995            }
1996
1997            if (minDigits < digits) {
1998                // Chop off as many trailing zero digits as necessary.
1999                while (minDigits < digits) {
2000                    if (length <= 1 || str.charAt(length - 1) != '0') {
2001                        break;
2002                    }
2003                    digits--;
2004                    length--;
2005                }
2006                if (length < str.length()) {
2007                    if (buf != null) {
2008                        for (int i=0; i<length; i++) {
2009                            buf.append(str.charAt(i));
2010                        }
2011                    } else {
2012                        for (int i=0; i<length; i++) {
2013                            out.write(str.charAt(i));
2014                        }
2015                    }
2016                    return;
2017                }
2018            }
2019
2020            if (buf != null) {
2021                buf.append(str);
2022            } else {
2023                out.write(str);
2024            }
2025        }
2026        
2027        private long[] getFractionData(long fraction, DateTimeField field) {
2028            long rangeMillis = field.getDurationField().getUnitMillis();
2029            long scalar;
2030            int maxDigits = iMaxDigits;
2031            while (true) {
2032                switch (maxDigits) {
2033                default: scalar = 1L; break;
2034                case 1:  scalar = 10L; break;
2035                case 2:  scalar = 100L; break;
2036                case 3:  scalar = 1000L; break;
2037                case 4:  scalar = 10000L; break;
2038                case 5:  scalar = 100000L; break;
2039                case 6:  scalar = 1000000L; break;
2040                case 7:  scalar = 10000000L; break;
2041                case 8:  scalar = 100000000L; break;
2042                case 9:  scalar = 1000000000L; break;
2043                case 10: scalar = 10000000000L; break;
2044                case 11: scalar = 100000000000L; break;
2045                case 12: scalar = 1000000000000L; break;
2046                case 13: scalar = 10000000000000L; break;
2047                case 14: scalar = 100000000000000L; break;
2048                case 15: scalar = 1000000000000000L; break;
2049                case 16: scalar = 10000000000000000L; break;
2050                case 17: scalar = 100000000000000000L; break;
2051                case 18: scalar = 1000000000000000000L; break;
2052                }
2053                if (((rangeMillis * scalar) / scalar) == rangeMillis) {
2054                    break;
2055                }
2056                // Overflowed: scale down.
2057                maxDigits--;
2058            }
2059            
2060            return new long[] {fraction * scalar / rangeMillis, maxDigits};
2061        }
2062
2063        public int estimateParsedLength() {
2064            return iMaxDigits;
2065        }
2066
2067        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2068            DateTimeField field = iFieldType.getField(bucket.getChronology());
2069            
2070            int limit = Math.min(iMaxDigits, text.length() - position);
2071
2072            long value = 0;
2073            long n = field.getDurationField().getUnitMillis() * 10;
2074            int length = 0;
2075            while (length < limit) {
2076                char c = text.charAt(position + length);
2077                if (c < '0' || c > '9') {
2078                    break;
2079                }
2080                length++;
2081                long nn = n / 10;
2082                value += (c - '0') * nn;
2083                n = nn;
2084            }
2085
2086            value /= 10;
2087
2088            if (length == 0) {
2089                return ~position;
2090            }
2091
2092            if (value > Integer.MAX_VALUE) {
2093                return ~position;
2094            }
2095
2096            DateTimeField parseField = new PreciseDateTimeField(
2097                DateTimeFieldType.millisOfSecond(),
2098                MillisDurationField.INSTANCE,
2099                field.getDurationField());
2100
2101            bucket.saveField(parseField, (int) value);
2102
2103            return position + length;
2104        }
2105    }
2106
2107    //-----------------------------------------------------------------------
2108    static class TimeZoneOffset
2109            implements DateTimePrinter, DateTimeParser {
2110
2111        private final String iZeroOffsetPrintText;
2112        private final String iZeroOffsetParseText;
2113        private final boolean iShowSeparators;
2114        private final int iMinFields;
2115        private final int iMaxFields;
2116
2117        TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText,
2118                                boolean showSeparators,
2119                                int minFields, int maxFields)
2120        {
2121            super();
2122            iZeroOffsetPrintText = zeroOffsetPrintText;
2123            iZeroOffsetParseText = zeroOffsetParseText;
2124            iShowSeparators = showSeparators;
2125            if (minFields <= 0 || maxFields < minFields) {
2126                throw new IllegalArgumentException();
2127            }
2128            if (minFields > 4) {
2129                minFields = 4;
2130                maxFields = 4;
2131            }
2132            iMinFields = minFields;
2133            iMaxFields = maxFields;
2134        }
2135            
2136        public int estimatePrintedLength() {
2137            int est = 1 + iMinFields << 1;
2138            if (iShowSeparators) {
2139                est += iMinFields - 1;
2140            }
2141            if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) {
2142                est = iZeroOffsetPrintText.length();
2143            }
2144            return est;
2145        }
2146        
2147        public void printTo(
2148                StringBuffer buf, long instant, Chronology chrono,
2149                int displayOffset, DateTimeZone displayZone, Locale locale) {
2150            if (displayZone == null) {
2151                return;  // no zone
2152            }
2153            if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2154                buf.append(iZeroOffsetPrintText);
2155                return;
2156            }
2157            if (displayOffset >= 0) {
2158                buf.append('+');
2159            } else {
2160                buf.append('-');
2161                displayOffset = -displayOffset;
2162            }
2163
2164            int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2165            FormatUtils.appendPaddedInteger(buf, hours, 2);
2166            if (iMaxFields == 1) {
2167                return;
2168            }
2169            displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2170            if (displayOffset == 0 && iMinFields <= 1) {
2171                return;
2172            }
2173
2174            int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2175            if (iShowSeparators) {
2176                buf.append(':');
2177            }
2178            FormatUtils.appendPaddedInteger(buf, minutes, 2);
2179            if (iMaxFields == 2) {
2180                return;
2181            }
2182            displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2183            if (displayOffset == 0 && iMinFields <= 2) {
2184                return;
2185            }
2186
2187            int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2188            if (iShowSeparators) {
2189                buf.append(':');
2190            }
2191            FormatUtils.appendPaddedInteger(buf, seconds, 2);
2192            if (iMaxFields == 3) {
2193                return;
2194            }
2195            displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2196            if (displayOffset == 0 && iMinFields <= 3) {
2197                return;
2198            }
2199
2200            if (iShowSeparators) {
2201                buf.append('.');
2202            }
2203            FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2204        }
2205        
2206        public void printTo(
2207                Writer out, long instant, Chronology chrono,
2208                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2209            if (displayZone == null) {
2210                return;  // no zone
2211            }
2212            if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2213                out.write(iZeroOffsetPrintText);
2214                return;
2215            }
2216            if (displayOffset >= 0) {
2217                out.write('+');
2218            } else {
2219                out.write('-');
2220                displayOffset = -displayOffset;
2221            }
2222
2223            int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2224            FormatUtils.writePaddedInteger(out, hours, 2);
2225            if (iMaxFields == 1) {
2226                return;
2227            }
2228            displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2229            if (displayOffset == 0 && iMinFields == 1) {
2230                return;
2231            }
2232
2233            int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2234            if (iShowSeparators) {
2235                out.write(':');
2236            }
2237            FormatUtils.writePaddedInteger(out, minutes, 2);
2238            if (iMaxFields == 2) {
2239                return;
2240            }
2241            displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2242            if (displayOffset == 0 && iMinFields == 2) {
2243                return;
2244            }
2245
2246            int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2247            if (iShowSeparators) {
2248                out.write(':');
2249            }
2250            FormatUtils.writePaddedInteger(out, seconds, 2);
2251            if (iMaxFields == 3) {
2252                return;
2253            }
2254            displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2255            if (displayOffset == 0 && iMinFields == 3) {
2256                return;
2257            }
2258
2259            if (iShowSeparators) {
2260                out.write('.');
2261            }
2262            FormatUtils.writePaddedInteger(out, displayOffset, 3);
2263        }
2264
2265        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2266            // no zone info
2267        }
2268
2269        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2270            // no zone info
2271        }
2272
2273        public int estimateParsedLength() {
2274            return estimatePrintedLength();
2275        }
2276
2277        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2278            int limit = text.length() - position;
2279
2280            zeroOffset:
2281            if (iZeroOffsetParseText != null) {
2282                if (iZeroOffsetParseText.length() == 0) {
2283                    // Peek ahead, looking for sign character.
2284                    if (limit > 0) {
2285                        char c = text.charAt(position);
2286                        if (c == '-' || c == '+') {
2287                            break zeroOffset;
2288                        }
2289                    }
2290                    bucket.setOffset(Integer.valueOf(0));
2291                    return position;
2292                }
2293                if (text.regionMatches(true, position, iZeroOffsetParseText, 0, iZeroOffsetParseText.length())) {
2294                    bucket.setOffset(Integer.valueOf(0));
2295                    return position + iZeroOffsetParseText.length();
2296                }
2297            }
2298
2299            // Format to expect is sign character followed by at least one digit.
2300
2301            if (limit <= 1) {
2302                return ~position;
2303            }
2304
2305            boolean negative;
2306            char c = text.charAt(position);
2307            if (c == '-') {
2308                negative = true;
2309            } else if (c == '+') {
2310                negative = false;
2311            } else {
2312                return ~position;
2313            }
2314
2315            limit--;
2316            position++;
2317
2318            // Format following sign is one of:
2319            //
2320            // hh
2321            // hhmm
2322            // hhmmss
2323            // hhmmssSSS
2324            // hh:mm
2325            // hh:mm:ss
2326            // hh:mm:ss.SSS
2327
2328            // First parse hours.
2329
2330            if (digitCount(text, position, 2) < 2) {
2331                // Need two digits for hour.
2332                return ~position;
2333            }
2334
2335            int offset;
2336
2337            int hours = FormatUtils.parseTwoDigits(text, position);
2338            if (hours > 23) {
2339                return ~position;
2340            }
2341            offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2342            limit -= 2;
2343            position += 2;
2344
2345            parse: {
2346                // Need to decide now if separators are expected or parsing
2347                // stops at hour field.
2348
2349                if (limit <= 0) {
2350                    break parse;
2351                }
2352
2353                boolean expectSeparators;
2354                c = text.charAt(position);
2355                if (c == ':') {
2356                    expectSeparators = true;
2357                    limit--;
2358                    position++;
2359                } else if (c >= '0' && c <= '9') {
2360                    expectSeparators = false;
2361                } else {
2362                    break parse;
2363                }
2364
2365                // Proceed to parse minutes.
2366
2367                int count = digitCount(text, position, 2);
2368                if (count == 0 && !expectSeparators) {
2369                    break parse;
2370                } else if (count < 2) {
2371                    // Need two digits for minute.
2372                    return ~position;
2373                }
2374
2375                int minutes = FormatUtils.parseTwoDigits(text, position);
2376                if (minutes > 59) {
2377                    return ~position;
2378                }
2379                offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2380                limit -= 2;
2381                position += 2;
2382
2383                // Proceed to parse seconds.
2384
2385                if (limit <= 0) {
2386                    break parse;
2387                }
2388
2389                if (expectSeparators) {
2390                    if (text.charAt(position) != ':') {
2391                        break parse;
2392                    }
2393                    limit--;
2394                    position++;
2395                }
2396
2397                count = digitCount(text, position, 2);
2398                if (count == 0 && !expectSeparators) {
2399                    break parse;
2400                } else if (count < 2) {
2401                    // Need two digits for second.
2402                    return ~position;
2403                }
2404
2405                int seconds = FormatUtils.parseTwoDigits(text, position);
2406                if (seconds > 59) {
2407                    return ~position;
2408                }
2409                offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2410                limit -= 2;
2411                position += 2;
2412
2413                // Proceed to parse fraction of second.
2414
2415                if (limit <= 0) {
2416                    break parse;
2417                }
2418
2419                if (expectSeparators) {
2420                    if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2421                        break parse;
2422                    }
2423                    limit--;
2424                    position++;
2425                }
2426                
2427                count = digitCount(text, position, 3);
2428                if (count == 0 && !expectSeparators) {
2429                    break parse;
2430                } else if (count < 1) {
2431                    // Need at least one digit for fraction of second.
2432                    return ~position;
2433                }
2434
2435                offset += (text.charAt(position++) - '0') * 100;
2436                if (count > 1) {
2437                    offset += (text.charAt(position++) - '0') * 10;
2438                    if (count > 2) {
2439                        offset += text.charAt(position++) - '0';
2440                    }
2441                }
2442            }
2443
2444            bucket.setOffset(Integer.valueOf(negative ? -offset : offset));
2445            return position;
2446        }
2447
2448        /**
2449         * Returns actual amount of digits to parse, but no more than original
2450         * 'amount' parameter.
2451         */
2452        private int digitCount(String text, int position, int amount) {
2453            int limit = Math.min(text.length() - position, amount);
2454            amount = 0;
2455            for (; limit > 0; limit--) {
2456                char c = text.charAt(position + amount);
2457                if (c < '0' || c > '9') {
2458                    break;
2459                }
2460                amount++;
2461            }
2462            return amount;
2463        }
2464    }
2465
2466    //-----------------------------------------------------------------------
2467    static class TimeZoneName
2468            implements DateTimePrinter, DateTimeParser {
2469
2470        static final int LONG_NAME = 0;
2471        static final int SHORT_NAME = 1;
2472
2473        private final Map<String, DateTimeZone> iParseLookup;
2474        private final int iType;
2475
2476        TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) {
2477            super();
2478            iType = type;
2479            iParseLookup = parseLookup;
2480        }
2481
2482        public int estimatePrintedLength() {
2483            return (iType == SHORT_NAME ? 4 : 20);
2484        }
2485
2486        public void printTo(
2487                StringBuffer buf, long instant, Chronology chrono,
2488                int displayOffset, DateTimeZone displayZone, Locale locale) {
2489            buf.append(print(instant - displayOffset, displayZone, locale));
2490        }
2491
2492        public void printTo(
2493                Writer out, long instant, Chronology chrono,
2494                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2495            out.write(print(instant - displayOffset, displayZone, locale));
2496        }
2497
2498        private String print(long instant, DateTimeZone displayZone, Locale locale) {
2499            if (displayZone == null) {
2500                return "";  // no zone
2501            }
2502            switch (iType) {
2503                case LONG_NAME:
2504                    return displayZone.getName(instant, locale);
2505                case SHORT_NAME:
2506                    return displayZone.getShortName(instant, locale);
2507            }
2508            return "";
2509        }
2510
2511        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2512            // no zone info
2513        }
2514
2515        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2516            // no zone info
2517        }
2518
2519        public int estimateParsedLength() {
2520            return (iType == SHORT_NAME ? 4 : 20);
2521        }
2522
2523        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2524            Map<String, DateTimeZone> parseLookup = iParseLookup;
2525            parseLookup = (parseLookup != null ? parseLookup : DateTimeUtils.getDefaultTimeZoneNames());
2526            String str = text.substring(position);
2527            for (String name : parseLookup.keySet()) {
2528                if (str.startsWith(name)) {
2529                    bucket.setZone(parseLookup.get(name));
2530                    return position + name.length();
2531                }
2532            }
2533            return ~position;
2534        }
2535    }
2536
2537    //-----------------------------------------------------------------------
2538    static enum TimeZoneId
2539            implements DateTimePrinter, DateTimeParser {
2540
2541        INSTANCE;
2542        static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs();
2543        static final int MAX_LENGTH;
2544        static {
2545            int max = 0;
2546            for (String id : ALL_IDS) {
2547                max = Math.max(max, id.length());
2548            }
2549            MAX_LENGTH = max;
2550        }
2551
2552        public int estimatePrintedLength() {
2553            return MAX_LENGTH;
2554        }
2555
2556        public void printTo(
2557                StringBuffer buf, long instant, Chronology chrono,
2558                int displayOffset, DateTimeZone displayZone, Locale locale) {
2559            buf.append(displayZone != null ? displayZone.getID() : "");
2560        }
2561
2562        public void printTo(
2563                Writer out, long instant, Chronology chrono,
2564                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2565            out.write(displayZone != null ? displayZone.getID() : "");
2566        }
2567
2568        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2569            // no zone info
2570        }
2571
2572        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2573            // no zone info
2574        }
2575
2576        public int estimateParsedLength() {
2577            return MAX_LENGTH;
2578        }
2579
2580        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2581            String str = text.substring(position);
2582            String best = null;
2583            for (String id : ALL_IDS) {
2584                if (str.startsWith(id)) {
2585                    if (best == null || id.length() > best.length()) {
2586                        best = id;
2587                    }
2588                }
2589            }
2590            if (best != null) {
2591                bucket.setZone(DateTimeZone.forID(best));
2592                return position + best.length();
2593            }
2594            return ~position;
2595        }
2596    }
2597
2598    //-----------------------------------------------------------------------
2599    static class Composite
2600            implements DateTimePrinter, DateTimeParser {
2601
2602        private final DateTimePrinter[] iPrinters;
2603        private final DateTimeParser[] iParsers;
2604
2605        private final int iPrintedLengthEstimate;
2606        private final int iParsedLengthEstimate;
2607
2608        Composite(List<Object> elementPairs) {
2609            super();
2610
2611            List<Object> printerList = new ArrayList<Object>();
2612            List<Object> parserList = new ArrayList<Object>();
2613
2614            decompose(elementPairs, printerList, parserList);
2615
2616            if (printerList.contains(null) || printerList.isEmpty()) {
2617                iPrinters = null;
2618                iPrintedLengthEstimate = 0;
2619            } else {
2620                int size = printerList.size();
2621                iPrinters = new DateTimePrinter[size];
2622                int printEst = 0;
2623                for (int i=0; i<size; i++) {
2624                    DateTimePrinter printer = (DateTimePrinter) printerList.get(i);
2625                    printEst += printer.estimatePrintedLength();
2626                    iPrinters[i] = printer;
2627                }
2628                iPrintedLengthEstimate = printEst;
2629            }
2630
2631            if (parserList.contains(null) || parserList.isEmpty()) {
2632                iParsers = null;
2633                iParsedLengthEstimate = 0;
2634            } else {
2635                int size = parserList.size();
2636                iParsers = new DateTimeParser[size];
2637                int parseEst = 0;
2638                for (int i=0; i<size; i++) {
2639                    DateTimeParser parser = (DateTimeParser) parserList.get(i);
2640                    parseEst += parser.estimateParsedLength();
2641                    iParsers[i] = parser;
2642                }
2643                iParsedLengthEstimate = parseEst;
2644            }
2645        }
2646
2647        public int estimatePrintedLength() {
2648            return iPrintedLengthEstimate;
2649        }
2650
2651        public void printTo(
2652                StringBuffer buf, long instant, Chronology chrono,
2653                int displayOffset, DateTimeZone displayZone, Locale locale) {
2654            DateTimePrinter[] elements = iPrinters;
2655            if (elements == null) {
2656                throw new UnsupportedOperationException();
2657            }
2658
2659            if (locale == null) {
2660                // Guard against default locale changing concurrently.
2661                locale = Locale.getDefault();
2662            }
2663
2664            int len = elements.length;
2665            for (int i = 0; i < len; i++) {
2666                elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale);
2667            }
2668        }
2669
2670        public void printTo(
2671                Writer out, long instant, Chronology chrono,
2672                int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2673            DateTimePrinter[] elements = iPrinters;
2674            if (elements == null) {
2675                throw new UnsupportedOperationException();
2676            }
2677
2678            if (locale == null) {
2679                // Guard against default locale changing concurrently.
2680                locale = Locale.getDefault();
2681            }
2682
2683            int len = elements.length;
2684            for (int i = 0; i < len; i++) {
2685                elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale);
2686            }
2687        }
2688
2689        public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2690            DateTimePrinter[] elements = iPrinters;
2691            if (elements == null) {
2692                throw new UnsupportedOperationException();
2693            }
2694
2695            if (locale == null) {
2696                // Guard against default locale changing concurrently.
2697                locale = Locale.getDefault();
2698            }
2699
2700            int len = elements.length;
2701            for (int i=0; i<len; i++) {
2702                elements[i].printTo(buf, partial, locale);
2703            }
2704        }
2705
2706        public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2707            DateTimePrinter[] elements = iPrinters;
2708            if (elements == null) {
2709                throw new UnsupportedOperationException();
2710            }
2711
2712            if (locale == null) {
2713                // Guard against default locale changing concurrently.
2714                locale = Locale.getDefault();
2715            }
2716
2717            int len = elements.length;
2718            for (int i=0; i<len; i++) {
2719                elements[i].printTo(out, partial, locale);
2720            }
2721        }
2722
2723        public int estimateParsedLength() {
2724            return iParsedLengthEstimate;
2725        }
2726
2727        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2728            DateTimeParser[] elements = iParsers;
2729            if (elements == null) {
2730                throw new UnsupportedOperationException();
2731            }
2732
2733            int len = elements.length;
2734            for (int i=0; i<len && position >= 0; i++) {
2735                position = elements[i].parseInto(bucket, text, position);
2736            }
2737            return position;
2738        }
2739
2740        boolean isPrinter() {
2741            return iPrinters != null;
2742        }
2743
2744        boolean isParser() {
2745            return iParsers != null;
2746        }
2747
2748        /**
2749         * Processes the element pairs, putting results into the given printer
2750         * and parser lists.
2751         */
2752        private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
2753            int size = elementPairs.size();
2754            for (int i=0; i<size; i+=2) {
2755                Object element = elementPairs.get(i);
2756                if (element instanceof Composite) {
2757                    addArrayToList(printerList, ((Composite)element).iPrinters);
2758                } else {
2759                    printerList.add(element);
2760                }
2761
2762                element = elementPairs.get(i + 1);
2763                if (element instanceof Composite) {
2764                    addArrayToList(parserList, ((Composite)element).iParsers);
2765                } else {
2766                    parserList.add(element);
2767                }
2768            }
2769        }
2770
2771        private void addArrayToList(List<Object> list, Object[] array) {
2772            if (array != null) {
2773                for (int i=0; i<array.length; i++) {
2774                    list.add(array[i]);
2775                }
2776            }
2777        }
2778    }
2779
2780    //-----------------------------------------------------------------------
2781    static class MatchingParser
2782            implements DateTimeParser {
2783
2784        private final DateTimeParser[] iParsers;
2785        private final int iParsedLengthEstimate;
2786
2787        MatchingParser(DateTimeParser[] parsers) {
2788            super();
2789            iParsers = parsers;
2790            int est = 0;
2791            for (int i=parsers.length; --i>=0 ;) {
2792                DateTimeParser parser = parsers[i];
2793                if (parser != null) {
2794                    int len = parser.estimateParsedLength();
2795                    if (len > est) {
2796                        est = len;
2797                    }
2798                }
2799            }
2800            iParsedLengthEstimate = est;
2801        }
2802
2803        public int estimateParsedLength() {
2804            return iParsedLengthEstimate;
2805        }
2806
2807        public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2808            DateTimeParser[] parsers = iParsers;
2809            int length = parsers.length;
2810
2811            final Object originalState = bucket.saveState();
2812            boolean isOptional = false;
2813
2814            int bestValidPos = position;
2815            Object bestValidState = null;
2816
2817            int bestInvalidPos = position;
2818
2819            for (int i=0; i<length; i++) {
2820                DateTimeParser parser = parsers[i];
2821                if (parser == null) {
2822                    // The empty parser wins only if nothing is better.
2823                    if (bestValidPos <= position) {
2824                        return position;
2825                    }
2826                    isOptional = true;
2827                    break;
2828                }
2829                int parsePos = parser.parseInto(bucket, text, position);
2830                if (parsePos >= position) {
2831                    if (parsePos > bestValidPos) {
2832                        if (parsePos >= text.length() ||
2833                            (i + 1) >= length || parsers[i + 1] == null) {
2834
2835                            // Completely parsed text or no more parsers to
2836                            // check. Skip the rest.
2837                            return parsePos;
2838                        }
2839                        bestValidPos = parsePos;
2840                        bestValidState = bucket.saveState();
2841                    }
2842                } else {
2843                    if (parsePos < 0) {
2844                        parsePos = ~parsePos;
2845                        if (parsePos > bestInvalidPos) {
2846                            bestInvalidPos = parsePos;
2847                        }
2848                    }
2849                }
2850                bucket.restoreState(originalState);
2851            }
2852
2853            if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2854                // Restore the state to the best valid parse.
2855                if (bestValidState != null) {
2856                    bucket.restoreState(bestValidState);
2857                }
2858                return bestValidPos;
2859            }
2860
2861            return ~bestInvalidPos;
2862        }
2863    }
2864
2865}