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