001    /*
002     *  Copyright 2001-2009 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.Collections;
022    import java.util.List;
023    import java.util.Locale;
024    import java.util.TreeSet;
025    
026    import org.joda.time.DateTimeConstants;
027    import org.joda.time.DurationFieldType;
028    import org.joda.time.PeriodType;
029    import org.joda.time.ReadWritablePeriod;
030    import org.joda.time.ReadablePeriod;
031    
032    /**
033     * Factory that creates complex instances of PeriodFormatter via method calls.
034     * <p>
035     * Period formatting is performed by the {@link PeriodFormatter} class.
036     * Three classes provide factory methods to create formatters, and this is one.
037     * The others are {@link PeriodFormat} and {@link ISOPeriodFormat}.
038     * <p>
039     * PeriodFormatterBuilder is used for constructing formatters which are then
040     * used to print or parse. The formatters are built by appending specific fields
041     * or other formatters to an instance of this builder.
042     * <p>
043     * For example, a formatter that prints years and months, like "15 years and 8 months",
044     * can be constructed as follows:
045     * <p>
046     * <pre>
047     * PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
048     *     .printZeroAlways()
049     *     .appendYears()
050     *     .appendSuffix(" year", " years")
051     *     .appendSeparator(" and ")
052     *     .printZeroRarelyLast()
053     *     .appendMonths()
054     *     .appendSuffix(" month", " months")
055     *     .toFormatter();
056     * </pre>
057     * <p>
058     * PeriodFormatterBuilder itself is mutable and not thread-safe, but the
059     * formatters that it builds are thread-safe and immutable.
060     *
061     * @author Brian S O'Neill
062     * @since 1.0
063     * @see PeriodFormat
064     */
065    public class PeriodFormatterBuilder {
066        private static final int PRINT_ZERO_RARELY_FIRST = 1;
067        private static final int PRINT_ZERO_RARELY_LAST = 2;
068        private static final int PRINT_ZERO_IF_SUPPORTED = 3;
069        private static final int PRINT_ZERO_ALWAYS = 4;
070        private static final int PRINT_ZERO_NEVER = 5;
071        
072        private static final int YEARS = 0;
073        private static final int MONTHS = 1;
074        private static final int WEEKS = 2;
075        private static final int DAYS = 3;
076        private static final int HOURS = 4;
077        private static final int MINUTES = 5;
078        private static final int SECONDS = 6;
079        private static final int MILLIS = 7;
080        private static final int SECONDS_MILLIS = 8;
081        private static final int SECONDS_OPTIONAL_MILLIS = 9;
082        private static final int MAX_FIELD = SECONDS_OPTIONAL_MILLIS;
083    
084        private int iMinPrintedDigits;
085        private int iPrintZeroSetting;
086        private int iMaxParsedDigits;
087        private boolean iRejectSignedValues;
088    
089        private PeriodFieldAffix iPrefix;
090    
091        // List of Printers and Parsers used to build a final formatter.
092        private List<Object> iElementPairs;
093        /** Set to true if the formatter is not a printer. */
094        private boolean iNotPrinter;
095        /** Set to true if the formatter is not a parser. */
096        private boolean iNotParser;
097    
098        // Last PeriodFormatter appended of each field type.
099        private FieldFormatter[] iFieldFormatters;
100    
101        public PeriodFormatterBuilder() {
102            clear();
103        }
104    
105        //-----------------------------------------------------------------------
106        /**
107         * Constructs a PeriodFormatter using all the appended elements.
108         * <p>
109         * This is the main method used by applications at the end of the build
110         * process to create a usable formatter.
111         * <p>
112         * Subsequent changes to this builder do not affect the returned formatter.
113         * <p>
114         * The returned formatter may not support both printing and parsing.
115         * The methods {@link PeriodFormatter#isPrinter()} and
116         * {@link PeriodFormatter#isParser()} will help you determine the state
117         * of the formatter.
118         * 
119         * @return the newly created formatter
120         * @throws IllegalStateException if the builder can produce neither a printer nor a parser
121         */
122        public PeriodFormatter toFormatter() {
123            PeriodFormatter formatter = toFormatter(iElementPairs, iNotPrinter, iNotParser);
124            iFieldFormatters = (FieldFormatter[]) iFieldFormatters.clone();
125            return formatter;
126        }
127    
128        /**
129         * Internal method to create a PeriodPrinter instance using all the
130         * appended elements.
131         * <p>
132         * Most applications will not use this method.
133         * If you want a printer in an application, call {@link #toFormatter()}
134         * and just use the printing API.
135         * <p>
136         * Subsequent changes to this builder do not affect the returned printer.
137         * 
138         * @return the newly created printer, null if builder cannot create a printer
139         */
140        public PeriodPrinter toPrinter() {
141            if (iNotPrinter) {
142                return null;
143            }
144            return toFormatter().getPrinter();
145        }
146    
147        /**
148         * Internal method to create a PeriodParser instance using all the
149         * appended elements.
150         * <p>
151         * Most applications will not use this method.
152         * If you want a printer in an application, call {@link #toFormatter()}
153         * and just use the printing API.
154         * <p>
155         * Subsequent changes to this builder do not affect the returned parser.
156         * 
157         * @return the newly created parser, null if builder cannot create a parser
158         */
159        public PeriodParser toParser() {
160            if (iNotParser) {
161                return null;
162            }
163            return toFormatter().getParser();
164        }
165    
166        //-----------------------------------------------------------------------
167        /**
168         * Clears out all the appended elements, allowing this builder to be reused.
169         */
170        public void clear() {
171            iMinPrintedDigits = 1;
172            iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
173            iMaxParsedDigits = 10;
174            iRejectSignedValues = false;
175            iPrefix = null;
176            if (iElementPairs == null) {
177                iElementPairs = new ArrayList<Object>();
178            } else {
179                iElementPairs.clear();
180            }
181            iNotPrinter = false;
182            iNotParser = false;
183            iFieldFormatters = new FieldFormatter[10];
184        }
185    
186        /**
187         * Appends another formatter.
188         *
189         * @return this PeriodFormatterBuilder
190         */
191        public PeriodFormatterBuilder append(PeriodFormatter formatter) {
192            if (formatter == null) {
193                throw new IllegalArgumentException("No formatter supplied");
194            }
195            clearPrefix();
196            append0(formatter.getPrinter(), formatter.getParser());
197            return this;
198        }
199    
200        /**
201         * Appends a printer parser pair.
202         * <p>
203         * Either the printer or the parser may be null, in which case the builder will
204         * be unable to produce a parser or printer repectively.
205         *
206         * @param printer  appends a printer to the builder, null if printing is not supported
207         * @param parser  appends a parser to the builder, null if parsing is not supported
208         * @return this PeriodFormatterBuilder
209         * @throws IllegalArgumentException if both the printer and parser are null
210         */
211        public PeriodFormatterBuilder append(PeriodPrinter printer, PeriodParser parser) {
212            if (printer == null && parser == null) {
213                throw new IllegalArgumentException("No printer or parser supplied");
214            }
215            clearPrefix();
216            append0(printer, parser);
217            return this;
218        }
219    
220        /**
221         * Instructs the printer to emit specific text, and the parser to expect it.
222         * The parser is case-insensitive.
223         *
224         * @return this PeriodFormatterBuilder
225         * @throws IllegalArgumentException if text is null
226         */
227        public PeriodFormatterBuilder appendLiteral(String text) {
228            if (text == null) {
229                throw new IllegalArgumentException("Literal must not be null");
230            }
231            clearPrefix();
232            Literal literal = new Literal(text);
233            append0(literal, literal);
234            return this;
235        }
236    
237        /**
238         * Set the minimum digits printed for the next and following appended
239         * fields. By default, the minimum digits printed is one. If the field value
240         * is zero, it is not printed unless a printZero rule is applied.
241         *
242         * @return this PeriodFormatterBuilder
243         */
244        public PeriodFormatterBuilder minimumPrintedDigits(int minDigits) {
245            iMinPrintedDigits = minDigits;
246            return this;
247        }
248    
249        /**
250         * Set the maximum digits parsed for the next and following appended
251         * fields. By default, the maximum digits parsed is ten.
252         *
253         * @return this PeriodFormatterBuilder
254         */
255        public PeriodFormatterBuilder maximumParsedDigits(int maxDigits) {
256            iMaxParsedDigits = maxDigits;
257            return this;
258        }
259    
260        /**
261         * Reject signed values when parsing the next and following appended fields.
262         *
263         * @return this PeriodFormatterBuilder
264         */
265        public PeriodFormatterBuilder rejectSignedValues(boolean v) {
266            iRejectSignedValues = v;
267            return this;
268        }
269    
270        /**
271         * Never print zero values for the next and following appended fields,
272         * unless no fields would be printed. If no fields are printed, the printer
273         * forces the last "printZeroRarely" field to print a zero.
274         * <p>
275         * This field setting is the default.
276         *
277         * @return this PeriodFormatterBuilder
278         */
279        public PeriodFormatterBuilder printZeroRarelyLast() {
280            iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
281            return this;
282        }
283    
284        /**
285         * Never print zero values for the next and following appended fields,
286         * unless no fields would be printed. If no fields are printed, the printer
287         * forces the first "printZeroRarely" field to print a zero.
288         *
289         * @return this PeriodFormatterBuilder
290         */
291        public PeriodFormatterBuilder printZeroRarelyFirst() {
292            iPrintZeroSetting = PRINT_ZERO_RARELY_FIRST;
293            return this;
294        }
295    
296        /**
297         * Print zero values for the next and following appened fields only if the
298         * period supports it.
299         *
300         * @return this PeriodFormatterBuilder
301         */
302        public PeriodFormatterBuilder printZeroIfSupported() {
303            iPrintZeroSetting = PRINT_ZERO_IF_SUPPORTED;
304            return this;
305        }
306    
307        /**
308         * Always print zero values for the next and following appended fields,
309         * even if the period doesn't support it. The parser requires values for
310         * fields that always print zero.
311         *
312         * @return this PeriodFormatterBuilder
313         */
314        public PeriodFormatterBuilder printZeroAlways() {
315            iPrintZeroSetting = PRINT_ZERO_ALWAYS;
316            return this;
317        }
318    
319        /**
320         * Never print zero values for the next and following appended fields,
321         * unless no fields would be printed. If no fields are printed, the printer
322         * forces the last "printZeroRarely" field to print a zero.
323         * <p>
324         * This field setting is the default.
325         *
326         * @return this PeriodFormatterBuilder
327         */
328        public PeriodFormatterBuilder printZeroNever() {
329            iPrintZeroSetting = PRINT_ZERO_NEVER;
330            return this;
331        }
332    
333        //-----------------------------------------------------------------------
334        /**
335         * Append a field prefix which applies only to the next appended field. If
336         * the field is not printed, neither is the prefix.
337         *
338         * @param text text to print before field only if field is printed
339         * @return this PeriodFormatterBuilder
340         * @see #appendSuffix
341         */
342        public PeriodFormatterBuilder appendPrefix(String text) {
343            if (text == null) {
344                throw new IllegalArgumentException();
345            }
346            return appendPrefix(new SimpleAffix(text));
347        }
348    
349        /**
350         * Append a field prefix which applies only to the next appended field. If
351         * the field is not printed, neither is the prefix.
352         * <p>
353         * During parsing, the singular and plural versions are accepted whether
354         * or not the actual value matches plurality.
355         *
356         * @param singularText text to print if field value is one
357         * @param pluralText text to print if field value is not one
358         * @return this PeriodFormatterBuilder
359         * @see #appendSuffix
360         */
361        public PeriodFormatterBuilder appendPrefix(String singularText,
362                                                     String pluralText) {
363            if (singularText == null || pluralText == null) {
364                throw new IllegalArgumentException();
365            }
366            return appendPrefix(new PluralAffix(singularText, pluralText));
367        }
368    
369        /**
370         * Append a field prefix which applies only to the next appended field. If
371         * the field is not printed, neither is the prefix.
372         *
373         * @param prefix custom prefix
374         * @return this PeriodFormatterBuilder
375         * @see #appendSuffix
376         */
377        private PeriodFormatterBuilder appendPrefix(PeriodFieldAffix prefix) {
378            if (prefix == null) {
379                throw new IllegalArgumentException();
380            }
381            if (iPrefix != null) {
382                prefix = new CompositeAffix(iPrefix, prefix);
383            }
384            iPrefix = prefix;
385            return this;
386        }
387    
388        //-----------------------------------------------------------------------
389        /**
390         * Instruct the printer to emit an integer years field, if supported.
391         * <p>
392         * The number of printed and parsed digits can be controlled using
393         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
394         *
395         * @return this PeriodFormatterBuilder
396         */
397        public PeriodFormatterBuilder appendYears() {
398            appendField(YEARS);
399            return this;
400        }
401    
402        /**
403         * Instruct the printer to emit an integer months field, if supported.
404         * <p>
405         * The number of printed and parsed digits can be controlled using
406         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
407         *
408         * @return this PeriodFormatterBuilder
409         */
410        public PeriodFormatterBuilder appendMonths() {
411            appendField(MONTHS);
412            return this;
413        }
414    
415        /**
416         * Instruct the printer to emit an integer weeks field, if supported.
417         * <p>
418         * The number of printed and parsed digits can be controlled using
419         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
420         *
421         * @return this PeriodFormatterBuilder
422         */
423        public PeriodFormatterBuilder appendWeeks() {
424            appendField(WEEKS);
425            return this;
426        }
427    
428        /**
429         * Instruct the printer to emit an integer days field, if supported.
430         * <p>
431         * The number of printed and parsed digits can be controlled using
432         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
433         *
434         * @return this PeriodFormatterBuilder
435         */
436        public PeriodFormatterBuilder appendDays() {
437            appendField(DAYS);
438            return this;
439        }
440    
441        /**
442         * Instruct the printer to emit an integer hours field, if supported.
443         * <p>
444         * The number of printed and parsed digits can be controlled using
445         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
446         *
447         * @return this PeriodFormatterBuilder
448         */
449        public PeriodFormatterBuilder appendHours() {
450            appendField(HOURS);
451            return this;
452        }
453    
454        /**
455         * Instruct the printer to emit an integer minutes field, if supported.
456         * <p>
457         * The number of printed and parsed digits can be controlled using
458         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
459         *
460         * @return this PeriodFormatterBuilder
461         */
462        public PeriodFormatterBuilder appendMinutes() {
463            appendField(MINUTES);
464            return this;
465        }
466    
467        /**
468         * Instruct the printer to emit an integer seconds field, if supported.
469         * <p>
470         * The number of printed and parsed digits can be controlled using
471         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
472         *
473         * @return this PeriodFormatterBuilder
474         */
475        public PeriodFormatterBuilder appendSeconds() {
476            appendField(SECONDS);
477            return this;
478        }
479    
480        /**
481         * Instruct the printer to emit a combined seconds and millis field, if supported.
482         * The millis will overflow into the seconds if necessary.
483         * The millis are always output.
484         *
485         * @return this PeriodFormatterBuilder
486         */
487        public PeriodFormatterBuilder appendSecondsWithMillis() {
488            appendField(SECONDS_MILLIS);
489            return this;
490        }
491    
492        /**
493         * Instruct the printer to emit a combined seconds and millis field, if supported.
494         * The millis will overflow into the seconds if necessary.
495         * The millis are only output if non-zero.
496         *
497         * @return this PeriodFormatterBuilder
498         */
499        public PeriodFormatterBuilder appendSecondsWithOptionalMillis() {
500            appendField(SECONDS_OPTIONAL_MILLIS);
501            return this;
502        }
503    
504        /**
505         * Instruct the printer to emit an integer millis field, if supported.
506         * <p>
507         * The number of printed and parsed digits can be controlled using
508         * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
509         *
510         * @return this PeriodFormatterBuilder
511         */
512        public PeriodFormatterBuilder appendMillis() {
513            appendField(MILLIS);
514            return this;
515        }
516    
517        /**
518         * Instruct the printer to emit an integer millis field, if supported.
519         * <p>
520         * The number of arsed digits can be controlled using {@link #maximumParsedDigits(int)}.
521         *
522         * @return this PeriodFormatterBuilder
523         */
524        public PeriodFormatterBuilder appendMillis3Digit() {
525            appendField(7, 3);
526            return this;
527        }
528    
529        private void appendField(int type) {
530            appendField(type, iMinPrintedDigits);
531        }
532    
533        private void appendField(int type, int minPrinted) {
534            FieldFormatter field = new FieldFormatter(minPrinted, iPrintZeroSetting,
535                iMaxParsedDigits, iRejectSignedValues, type, iFieldFormatters, iPrefix, null);
536            append0(field, field);
537            iFieldFormatters[type] = field;
538            iPrefix = null;
539        }
540    
541        //-----------------------------------------------------------------------
542        /**
543         * Append a field suffix which applies only to the last appended field. If
544         * the field is not printed, neither is the suffix.
545         *
546         * @param text text to print after field only if field is printed
547         * @return this PeriodFormatterBuilder
548         * @throws IllegalStateException if no field exists to append to
549         * @see #appendPrefix
550         */
551        public PeriodFormatterBuilder appendSuffix(String text) {
552            if (text == null) {
553                throw new IllegalArgumentException();
554            }
555            return appendSuffix(new SimpleAffix(text));
556        }
557    
558        /**
559         * Append a field suffix which applies only to the last appended field. If
560         * the field is not printed, neither is the suffix.
561         * <p>
562         * During parsing, the singular and plural versions are accepted whether or
563         * not the actual value matches plurality.
564         *
565         * @param singularText text to print if field value is one
566         * @param pluralText text to print if field value is not one
567         * @return this PeriodFormatterBuilder
568         * @throws IllegalStateException if no field exists to append to
569         * @see #appendPrefix
570         */
571        public PeriodFormatterBuilder appendSuffix(String singularText,
572                                                   String pluralText) {
573            if (singularText == null || pluralText == null) {
574                throw new IllegalArgumentException();
575            }
576            return appendSuffix(new PluralAffix(singularText, pluralText));
577        }
578    
579        /**
580         * Append a field suffix which applies only to the last appended field. If
581         * the field is not printed, neither is the suffix.
582         *
583         * @param suffix custom suffix
584         * @return this PeriodFormatterBuilder
585         * @throws IllegalStateException if no field exists to append to
586         * @see #appendPrefix
587         */
588        private PeriodFormatterBuilder appendSuffix(PeriodFieldAffix suffix) {
589            final Object originalPrinter;
590            final Object originalParser;
591            if (iElementPairs.size() > 0) {
592                originalPrinter = iElementPairs.get(iElementPairs.size() - 2);
593                originalParser = iElementPairs.get(iElementPairs.size() - 1);
594            } else {
595                originalPrinter = null;
596                originalParser = null;
597            }
598    
599            if (originalPrinter == null || originalParser == null ||
600                    originalPrinter != originalParser ||
601                    !(originalPrinter instanceof FieldFormatter)) {
602                throw new IllegalStateException("No field to apply suffix to");
603            }
604    
605            clearPrefix();
606            FieldFormatter newField = new FieldFormatter((FieldFormatter) originalPrinter, suffix);
607            iElementPairs.set(iElementPairs.size() - 2, newField);
608            iElementPairs.set(iElementPairs.size() - 1, newField);
609            iFieldFormatters[newField.getFieldType()] = newField;
610            
611            return this;
612        }
613    
614        //-----------------------------------------------------------------------
615        /**
616         * Append a separator, which is output if fields are printed both before
617         * and after the separator.
618         * <p>
619         * For example, <code>builder.appendDays().appendSeparator(",").appendHours()</code>
620         * will only output the comma if both the days and hours fields are output.
621         * <p>
622         * The text will be parsed case-insensitively.
623         * <p>
624         * Note: appending a separator discontinues any further work on the latest
625         * appended field.
626         *
627         * @param text  the text to use as a separator
628         * @return this PeriodFormatterBuilder
629         * @throws IllegalStateException if this separator follows a previous one
630         */
631        public PeriodFormatterBuilder appendSeparator(String text) {
632            return appendSeparator(text, text, null, true, true);
633        }
634    
635        /**
636         * Append a separator, which is output only if fields are printed after the separator.
637         * <p>
638         * For example,
639         * <code>builder.appendDays().appendSeparatorIfFieldsAfter(",").appendHours()</code>
640         * will only output the comma if the hours fields is output.
641         * <p>
642         * The text will be parsed case-insensitively.
643         * <p>
644         * Note: appending a separator discontinues any further work on the latest
645         * appended field.
646         *
647         * @param text  the text to use as a separator
648         * @return this PeriodFormatterBuilder
649         * @throws IllegalStateException if this separator follows a previous one
650         */
651        public PeriodFormatterBuilder appendSeparatorIfFieldsAfter(String text) {
652            return appendSeparator(text, text, null, false, true);
653        }
654    
655        /**
656         * Append a separator, which is output only if fields are printed before the separator.
657         * <p>
658         * For example,
659         * <code>builder.appendDays().appendSeparatorIfFieldsBefore(",").appendHours()</code>
660         * will only output the comma if the days fields is output.
661         * <p>
662         * The text will be parsed case-insensitively.
663         * <p>
664         * Note: appending a separator discontinues any further work on the latest
665         * appended field.
666         *
667         * @param text  the text to use as a separator
668         * @return this PeriodFormatterBuilder
669         * @throws IllegalStateException if this separator follows a previous one
670         */
671        public PeriodFormatterBuilder appendSeparatorIfFieldsBefore(String text) {
672            return appendSeparator(text, text, null, true, false);
673        }
674    
675        /**
676         * Append a separator, which is output if fields are printed both before
677         * and after the separator.
678         * <p>
679         * This method changes the separator depending on whether it is the last separator
680         * to be output.
681         * <p>
682         * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
683         * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
684         * and '1' if just one field is output.
685         * <p>
686         * The text will be parsed case-insensitively.
687         * <p>
688         * Note: appending a separator discontinues any further work on the latest
689         * appended field.
690         *
691         * @param text  the text to use as a separator
692         * @param finalText  the text used used if this is the final separator to be printed
693         * @return this PeriodFormatterBuilder
694         * @throws IllegalStateException if this separator follows a previous one
695         */
696        public PeriodFormatterBuilder appendSeparator(String text, String finalText) {
697            return appendSeparator(text, finalText, null, true, true);
698        }
699    
700        /**
701         * Append a separator, which is output if fields are printed both before
702         * and after the separator.
703         * <p>
704         * This method changes the separator depending on whether it is the last separator
705         * to be output.
706         * <p>
707         * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
708         * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
709         * and '1' if just one field is output.
710         * <p>
711         * The text will be parsed case-insensitively.
712         * <p>
713         * Note: appending a separator discontinues any further work on the latest
714         * appended field.
715         *
716         * @param text  the text to use as a separator
717         * @param finalText  the text used used if this is the final separator to be printed
718         * @param variants  set of text values which are also acceptable when parsed
719         * @return this PeriodFormatterBuilder
720         * @throws IllegalStateException if this separator follows a previous one
721         */
722        public PeriodFormatterBuilder appendSeparator(String text, String finalText,
723                                                      String[] variants) {
724            return appendSeparator(text, finalText, variants, true, true);
725        }
726    
727        private PeriodFormatterBuilder appendSeparator(String text, String finalText,
728                                                       String[] variants,
729                                                       boolean useBefore, boolean useAfter) {
730            if (text == null || finalText == null) {
731                throw new IllegalArgumentException();
732            }
733    
734            clearPrefix();
735            
736            // optimise zero formatter case
737            List<Object> pairs = iElementPairs;
738            if (pairs.size() == 0) {
739                if (useAfter && useBefore == false) {
740                    Separator separator = new Separator(
741                            text, finalText, variants,
742                            Literal.EMPTY, Literal.EMPTY, useBefore, useAfter);
743                    append0(separator, separator);
744                }
745                return this;
746            }
747            
748            // find the last separator added
749            int i;
750            Separator lastSeparator = null;
751            for (i=pairs.size(); --i>=0; ) {
752                if (pairs.get(i) instanceof Separator) {
753                    lastSeparator = (Separator) pairs.get(i);
754                    pairs = pairs.subList(i + 1, pairs.size());
755                    break;
756                }
757                i--;  // element pairs
758            }
759            
760            // merge formatters
761            if (lastSeparator != null && pairs.size() == 0) {
762                throw new IllegalStateException("Cannot have two adjacent separators");
763            } else {
764                Object[] comp = createComposite(pairs);
765                pairs.clear();
766                Separator separator = new Separator(
767                        text, finalText, variants,
768                        (PeriodPrinter) comp[0], (PeriodParser) comp[1],
769                        useBefore, useAfter);
770                pairs.add(separator);
771                pairs.add(separator);
772            }
773            
774            return this;
775        }
776    
777        //-----------------------------------------------------------------------
778        private void clearPrefix() throws IllegalStateException {
779            if (iPrefix != null) {
780                throw new IllegalStateException("Prefix not followed by field");
781            }
782            iPrefix = null;
783        }
784    
785        private PeriodFormatterBuilder append0(PeriodPrinter printer, PeriodParser parser) {
786            iElementPairs.add(printer);
787            iElementPairs.add(parser);
788            iNotPrinter |= (printer == null);
789            iNotParser |= (parser == null);
790            return this;
791        }
792    
793        //-----------------------------------------------------------------------
794        private static PeriodFormatter toFormatter(List<Object> elementPairs, boolean notPrinter, boolean notParser) {
795            if (notPrinter && notParser) {
796                throw new IllegalStateException("Builder has created neither a printer nor a parser");
797            }
798            int size = elementPairs.size();
799            if (size >= 2 && elementPairs.get(0) instanceof Separator) {
800                Separator sep = (Separator) elementPairs.get(0);
801                if (sep.iAfterParser == null && sep.iAfterPrinter == null) {
802                    PeriodFormatter f = toFormatter(elementPairs.subList(2, size), notPrinter, notParser);
803                    sep = sep.finish(f.getPrinter(), f.getParser());
804                    return new PeriodFormatter(sep, sep);
805                }
806            }
807            Object[] comp = createComposite(elementPairs);
808            if (notPrinter) {
809                return new PeriodFormatter(null, (PeriodParser) comp[1]);
810            } else if (notParser) {
811                return new PeriodFormatter((PeriodPrinter) comp[0], null);
812            } else {
813                return new PeriodFormatter((PeriodPrinter) comp[0], (PeriodParser) comp[1]);
814            }
815        }
816    
817        private static Object[] createComposite(List<Object> elementPairs) {
818            switch (elementPairs.size()) {
819                case 0:
820                    return new Object[] {Literal.EMPTY, Literal.EMPTY};
821                case 1:
822                    return new Object[] {elementPairs.get(0), elementPairs.get(1)};
823                default:
824                    Composite comp = new Composite(elementPairs);
825                    return new Object[] {comp, comp};
826            }
827        }
828    
829        //-----------------------------------------------------------------------
830        /**
831         * Defines a formatted field's prefix or suffix text.
832         * This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
833         */
834        static interface PeriodFieldAffix {
835            int calculatePrintedLength(int value);
836            
837            void printTo(StringBuffer buf, int value);
838            
839            void printTo(Writer out, int value) throws IOException;
840            
841            /**
842             * @return new position after parsing affix, or ~position of failure
843             */
844            int parse(String periodStr, int position);
845    
846            /**
847             * @return position where affix starts, or original ~position if not found
848             */
849            int scan(String periodStr, int position);
850        }
851    
852        //-----------------------------------------------------------------------
853        /**
854         * Implements an affix where the text does not vary by the amount.
855         */
856        static class SimpleAffix implements PeriodFieldAffix {
857            private final String iText;
858    
859            SimpleAffix(String text) {
860                iText = text;
861            }
862    
863            public int calculatePrintedLength(int value) {
864                return iText.length();
865            }
866    
867            public void printTo(StringBuffer buf, int value) {
868                buf.append(iText);
869            }
870    
871            public void printTo(Writer out, int value) throws IOException {
872                out.write(iText);
873            }
874    
875            public int parse(String periodStr, int position) {
876                String text = iText;
877                int textLength = text.length();
878                if (periodStr.regionMatches(true, position, text, 0, textLength)) {
879                    return position + textLength;
880                }
881                return ~position;
882            }
883    
884            public int scan(String periodStr, final int position) {
885                String text = iText;
886                int textLength = text.length();
887                int sourceLength = periodStr.length();
888                search:
889                for (int pos = position; pos < sourceLength; pos++) {
890                    if (periodStr.regionMatches(true, pos, text, 0, textLength)) {
891                        return pos;
892                    }
893                    // Only allow number characters to be skipped in search of suffix.
894                    switch (periodStr.charAt(pos)) {
895                    case '0': case '1': case '2': case '3': case '4':
896                    case '5': case '6': case '7': case '8': case '9':
897                    case '.': case ',': case '+': case '-':
898                        break;
899                    default:
900                        break search;
901                    }
902                }
903                return ~position;
904            }
905        }
906    
907        //-----------------------------------------------------------------------
908        /**
909         * Implements an affix where the text varies by the amount of the field.
910         * Only singular (1) and plural (not 1) are supported.
911         */
912        static class PluralAffix implements PeriodFieldAffix {
913            private final String iSingularText;
914            private final String iPluralText;
915    
916            PluralAffix(String singularText, String pluralText) {
917                iSingularText = singularText;
918                iPluralText = pluralText;
919            }
920    
921            public int calculatePrintedLength(int value) {
922                return (value == 1 ? iSingularText : iPluralText).length();
923            }
924    
925            public void printTo(StringBuffer buf, int value) {
926                buf.append(value == 1 ? iSingularText : iPluralText);
927            }
928    
929            public void printTo(Writer out, int value) throws IOException {
930                out.write(value == 1 ? iSingularText : iPluralText);
931            }
932    
933            public int parse(String periodStr, int position) {
934                String text1 = iPluralText;
935                String text2 = iSingularText; 
936    
937                if (text1.length() < text2.length()) {
938                    // Swap in order to match longer one first.
939                    String temp = text1;
940                    text1 = text2;
941                    text2 = temp;
942                }
943    
944                if (periodStr.regionMatches
945                    (true, position, text1, 0, text1.length())) {
946                    return position + text1.length();
947                }
948                if (periodStr.regionMatches
949                    (true, position, text2, 0, text2.length())) {
950                    return position + text2.length();
951                }
952    
953                return ~position;
954            }
955    
956            public int scan(String periodStr, final int position) {
957                String text1 = iPluralText;
958                String text2 = iSingularText; 
959    
960                if (text1.length() < text2.length()) {
961                    // Swap in order to match longer one first.
962                    String temp = text1;
963                    text1 = text2;
964                    text2 = temp;
965                }
966    
967                int textLength1 = text1.length();
968                int textLength2 = text2.length();
969    
970                int sourceLength = periodStr.length();
971                for (int pos = position; pos < sourceLength; pos++) {
972                    if (periodStr.regionMatches(true, pos, text1, 0, textLength1)) {
973                        return pos;
974                    }
975                    if (periodStr.regionMatches(true, pos, text2, 0, textLength2)) {
976                        return pos;
977                    }
978                }
979                return ~position;
980            }
981        }
982    
983        //-----------------------------------------------------------------------
984        /**
985         * Builds a composite affix by merging two other affix implementations.
986         */
987        static class CompositeAffix implements PeriodFieldAffix {
988            private final PeriodFieldAffix iLeft;
989            private final PeriodFieldAffix iRight;
990    
991            CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) {
992                iLeft = left;
993                iRight = right;
994            }
995    
996            public int calculatePrintedLength(int value) {
997                return iLeft.calculatePrintedLength(value)
998                    + iRight.calculatePrintedLength(value);
999            }
1000    
1001            public void printTo(StringBuffer buf, int value) {
1002                iLeft.printTo(buf, value);
1003                iRight.printTo(buf, value);
1004            }
1005    
1006            public void printTo(Writer out, int value) throws IOException {
1007                iLeft.printTo(out, value);
1008                iRight.printTo(out, value);
1009            }
1010    
1011            public int parse(String periodStr, int position) {
1012                position = iLeft.parse(periodStr, position);
1013                if (position >= 0) {
1014                    position = iRight.parse(periodStr, position);
1015                }
1016                return position;
1017            }
1018    
1019            public int scan(String periodStr, final int position) {
1020                int pos = iLeft.scan(periodStr, position);
1021                if (pos >= 0) {
1022                    return iRight.scan(periodStr, pos);
1023                }
1024                return ~position;
1025            }
1026        }
1027    
1028        //-----------------------------------------------------------------------
1029        /**
1030         * Formats the numeric value of a field, potentially with prefix/suffix.
1031         */
1032        static class FieldFormatter
1033                implements PeriodPrinter, PeriodParser {
1034            private final int iMinPrintedDigits;
1035            private final int iPrintZeroSetting;
1036            private final int iMaxParsedDigits;
1037            private final boolean iRejectSignedValues;
1038            
1039            /** The index of the field type, 0=year, etc. */
1040            private final int iFieldType;
1041            /**
1042             * The array of the latest formatter added for each type.
1043             * This is shared between all the field formatters in a formatter.
1044             */
1045            private final FieldFormatter[] iFieldFormatters;
1046            
1047            private final PeriodFieldAffix iPrefix;
1048            private final PeriodFieldAffix iSuffix;
1049    
1050            FieldFormatter(int minPrintedDigits, int printZeroSetting,
1051                           int maxParsedDigits, boolean rejectSignedValues,
1052                           int fieldType, FieldFormatter[] fieldFormatters,
1053                           PeriodFieldAffix prefix, PeriodFieldAffix suffix) {
1054                iMinPrintedDigits = minPrintedDigits;
1055                iPrintZeroSetting = printZeroSetting;
1056                iMaxParsedDigits = maxParsedDigits;
1057                iRejectSignedValues = rejectSignedValues;
1058                iFieldType = fieldType;
1059                iFieldFormatters = fieldFormatters;
1060                iPrefix = prefix;
1061                iSuffix = suffix;
1062            }
1063    
1064            FieldFormatter(FieldFormatter field, PeriodFieldAffix suffix) {
1065                iMinPrintedDigits = field.iMinPrintedDigits;
1066                iPrintZeroSetting = field.iPrintZeroSetting;
1067                iMaxParsedDigits = field.iMaxParsedDigits;
1068                iRejectSignedValues = field.iRejectSignedValues;
1069                iFieldType = field.iFieldType;
1070                iFieldFormatters = field.iFieldFormatters;
1071                iPrefix = field.iPrefix;
1072                if (field.iSuffix != null) {
1073                    suffix = new CompositeAffix(field.iSuffix, suffix);
1074                }
1075                iSuffix = suffix;
1076            }
1077    
1078            public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1079                if (stopAt <= 0) {
1080                    return 0;
1081                }
1082                if (iPrintZeroSetting == PRINT_ZERO_ALWAYS || getFieldValue(period) != Long.MAX_VALUE) {
1083                    return 1;
1084                }
1085                return 0;
1086            }
1087    
1088            public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1089                long valueLong = getFieldValue(period);
1090                if (valueLong == Long.MAX_VALUE) {
1091                    return 0;
1092                }
1093    
1094                int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), iMinPrintedDigits);
1095                if (iFieldType >= SECONDS_MILLIS) {
1096                    // valueLong contains the seconds and millis fields
1097                    // the minimum output is 0.000, which is 4 or 5 digits with a negative
1098                    sum = (valueLong < 0 ? Math.max(sum, 5) : Math.max(sum, 4));
1099                    // plus one for the decimal point
1100                    sum++;
1101                    if (iFieldType == SECONDS_OPTIONAL_MILLIS &&
1102                            (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND) == 0) {
1103                        sum -= 4; // remove three digits and decimal point
1104                    }
1105                    // reset valueLong to refer to the seconds part for the prefic/suffix calculation
1106                    valueLong = valueLong / DateTimeConstants.MILLIS_PER_SECOND;
1107                }
1108                int value = (int) valueLong;
1109    
1110                if (iPrefix != null) {
1111                    sum += iPrefix.calculatePrintedLength(value);
1112                }
1113                if (iSuffix != null) {
1114                    sum += iSuffix.calculatePrintedLength(value);
1115                }
1116    
1117                return sum;
1118            }
1119            
1120            public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1121                long valueLong = getFieldValue(period);
1122                if (valueLong == Long.MAX_VALUE) {
1123                    return;
1124                }
1125                int value = (int) valueLong;
1126                if (iFieldType >= SECONDS_MILLIS) {
1127                    value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1128                }
1129    
1130                if (iPrefix != null) {
1131                    iPrefix.printTo(buf, value);
1132                }
1133                int bufLen = buf.length();
1134                int minDigits = iMinPrintedDigits;
1135                if (minDigits <= 1) {
1136                    FormatUtils.appendUnpaddedInteger(buf, value);
1137                } else {
1138                    FormatUtils.appendPaddedInteger(buf, value, minDigits);
1139                }
1140                if (iFieldType >= SECONDS_MILLIS) {
1141                    int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1142                    if (iFieldType == SECONDS_MILLIS || dp > 0) {
1143                        if (valueLong < 0 && valueLong > -DateTimeConstants.MILLIS_PER_SECOND) {
1144                            buf.insert(bufLen, '-');
1145                        }
1146                        buf.append('.');
1147                        FormatUtils.appendPaddedInteger(buf, dp, 3);
1148                    }
1149                }
1150                if (iSuffix != null) {
1151                    iSuffix.printTo(buf, value);
1152                }
1153            }
1154    
1155            public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1156                long valueLong = getFieldValue(period);
1157                if (valueLong == Long.MAX_VALUE) {
1158                    return;
1159                }
1160                int value = (int) valueLong;
1161                if (iFieldType >= SECONDS_MILLIS) {
1162                    value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1163                }
1164    
1165                if (iPrefix != null) {
1166                    iPrefix.printTo(out, value);
1167                }
1168                int minDigits = iMinPrintedDigits;
1169                if (minDigits <= 1) {
1170                    FormatUtils.writeUnpaddedInteger(out, value);
1171                } else {
1172                    FormatUtils.writePaddedInteger(out, value, minDigits);
1173                }
1174                if (iFieldType >= SECONDS_MILLIS) {
1175                    int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1176                    if (iFieldType == SECONDS_MILLIS || dp > 0) {
1177                        out.write('.');
1178                        FormatUtils.writePaddedInteger(out, dp, 3);
1179                    }
1180                }
1181                if (iSuffix != null) {
1182                    iSuffix.printTo(out, value);
1183                }
1184            }
1185    
1186            public int parseInto(
1187                    ReadWritablePeriod period, String text, 
1188                    int position, Locale locale) {
1189    
1190                boolean mustParse = (iPrintZeroSetting == PRINT_ZERO_ALWAYS);
1191    
1192                // Shortcut test.
1193                if (position >= text.length()) {
1194                    return mustParse ? ~position : position;
1195                }
1196    
1197                if (iPrefix != null) {
1198                    position = iPrefix.parse(text, position);
1199                    if (position >= 0) {
1200                        // If prefix is found, then the parse must finish.
1201                        mustParse = true;
1202                    } else {
1203                        // Prefix not found, so bail.
1204                        if (!mustParse) {
1205                            // It's okay because parsing of this field is not
1206                            // required. Don't return an error. Fields down the
1207                            // chain can continue on, trying to parse.
1208                            return ~position;
1209                        }
1210                        return position;
1211                    }
1212                }
1213    
1214                int suffixPos = -1;
1215                if (iSuffix != null && !mustParse) {
1216                    // Pre-scan the suffix, to help determine if this field must be
1217                    // parsed.
1218                    suffixPos = iSuffix.scan(text, position);
1219                    if (suffixPos >= 0) {
1220                        // If suffix is found, then parse must finish.
1221                        mustParse = true;
1222                    } else {
1223                        // Suffix not found, so bail.
1224                        if (!mustParse) {
1225                            // It's okay because parsing of this field is not
1226                            // required. Don't return an error. Fields down the
1227                            // chain can continue on, trying to parse.
1228                            return ~suffixPos;
1229                        }
1230                        return suffixPos;
1231                    }
1232                }
1233    
1234                if (!mustParse && !isSupported(period.getPeriodType(), iFieldType)) {
1235                    // If parsing is not required and the field is not supported,
1236                    // exit gracefully so that another parser can continue on.
1237                    return position;
1238                }
1239    
1240                int limit;
1241                if (suffixPos > 0) {
1242                    limit = Math.min(iMaxParsedDigits, suffixPos - position);
1243                } else {
1244                    limit = Math.min(iMaxParsedDigits, text.length() - position);
1245                }
1246    
1247                // validate input number
1248                int length = 0;
1249                int fractPos = -1;
1250                boolean hasDigits = false;
1251                while (length < limit) {
1252                    char c = text.charAt(position + length);
1253                    // leading sign
1254                    if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) {
1255                        boolean negative = c == '-';
1256    
1257                        // Next character must be a digit.
1258                        if (length + 1 >= limit || 
1259                            (c = text.charAt(position + length + 1)) < '0' || c > '9')
1260                        {
1261                            break;
1262                        }
1263    
1264                        if (negative) {
1265                            length++;
1266                        } else {
1267                            // Skip the '+' for parseInt to succeed.
1268                            position++;
1269                        }
1270                        // Expand the limit to disregard the sign character.
1271                        limit = Math.min(limit + 1, text.length() - position);
1272                        continue;
1273                    }
1274                    // main number
1275                    if (c >= '0' && c <= '9') {
1276                        hasDigits = true;
1277                    } else {
1278                        if ((c == '.' || c == ',')
1279                             && (iFieldType == SECONDS_MILLIS || iFieldType == SECONDS_OPTIONAL_MILLIS)) {
1280                            if (fractPos >= 0) {
1281                                // can't have two decimals
1282                                break;
1283                            }
1284                            fractPos = position + length + 1;
1285                            // Expand the limit to disregard the decimal point.
1286                            limit = Math.min(limit + 1, text.length() - position);
1287                        } else {
1288                            break;
1289                        }
1290                    }
1291                    length++;
1292                }
1293    
1294                if (!hasDigits) {
1295                    return ~position;
1296                }
1297    
1298                if (suffixPos >= 0 && position + length != suffixPos) {
1299                    // If there are additional non-digit characters before the
1300                    // suffix is reached, then assume that the suffix found belongs
1301                    // to a field not yet reached. Return original position so that
1302                    // another parser can continue on.
1303                    return position;
1304                }
1305    
1306                if (iFieldType != SECONDS_MILLIS && iFieldType != SECONDS_OPTIONAL_MILLIS) {
1307                    // Handle common case.
1308                    setFieldValue(period, iFieldType, parseInt(text, position, length));
1309                } else if (fractPos < 0) {
1310                    setFieldValue(period, SECONDS, parseInt(text, position, length));
1311                    setFieldValue(period, MILLIS, 0);
1312                } else {
1313                    int wholeValue = parseInt(text, position, fractPos - position - 1);
1314                    setFieldValue(period, SECONDS, wholeValue);
1315    
1316                    int fractLen = position + length - fractPos;
1317                    int fractValue;
1318                    if (fractLen <= 0) {
1319                        fractValue = 0;
1320                    } else {
1321                        if (fractLen >= 3) {
1322                            fractValue = parseInt(text, fractPos, 3);
1323                        } else {
1324                            fractValue = parseInt(text, fractPos, fractLen);
1325                            if (fractLen == 1) {
1326                                fractValue *= 100;
1327                            } else {
1328                                fractValue *= 10;
1329                            }
1330                        }
1331                        if (wholeValue < 0) {
1332                            fractValue = -fractValue;
1333                        }
1334                    }
1335    
1336                    setFieldValue(period, MILLIS, fractValue);
1337                }
1338                    
1339                position += length;
1340    
1341                if (position >= 0 && iSuffix != null) {
1342                    position = iSuffix.parse(text, position);
1343                }
1344                    
1345                return position;
1346            }
1347    
1348            /**
1349             * @param text text to parse
1350             * @param position position in text
1351             * @param length exact count of characters to parse
1352             * @return parsed int value
1353             */
1354            private int parseInt(String text, int position, int length) {
1355                if (length >= 10) {
1356                    // Since value may exceed max, use stock parser which checks for this.
1357                    return Integer.parseInt(text.substring(position, position + length));
1358                }
1359                if (length <= 0) {
1360                    return 0;
1361                }
1362                int value = text.charAt(position++);
1363                length--;
1364                boolean negative;
1365                if (value == '-') {
1366                    if (--length < 0) {
1367                        return 0;
1368                    }
1369                    negative = true;
1370                    value = text.charAt(position++);
1371                } else {
1372                    negative = false;
1373                }
1374                value -= '0';
1375                while (length-- > 0) {
1376                    value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0';
1377                }
1378                return negative ? -value : value;
1379            }
1380    
1381            /**
1382             * @return Long.MAX_VALUE if nothing to print, otherwise value
1383             */
1384            long getFieldValue(ReadablePeriod period) {
1385                PeriodType type;
1386                if (iPrintZeroSetting == PRINT_ZERO_ALWAYS) {
1387                    type = null; // Don't need to check if supported.
1388                } else {
1389                    type = period.getPeriodType();
1390                }
1391                if (type != null && isSupported(type, iFieldType) == false) {
1392                    return Long.MAX_VALUE;
1393                }
1394    
1395                long value;
1396    
1397                switch (iFieldType) {
1398                default:
1399                    return Long.MAX_VALUE;
1400                case YEARS:
1401                    value = period.get(DurationFieldType.years());
1402                    break;
1403                case MONTHS:
1404                    value = period.get(DurationFieldType.months());
1405                    break;
1406                case WEEKS:
1407                    value = period.get(DurationFieldType.weeks());
1408                    break;
1409                case DAYS:
1410                    value = period.get(DurationFieldType.days());
1411                    break;
1412                case HOURS:
1413                    value = period.get(DurationFieldType.hours());
1414                    break;
1415                case MINUTES:
1416                    value = period.get(DurationFieldType.minutes());
1417                    break;
1418                case SECONDS:
1419                    value = period.get(DurationFieldType.seconds());
1420                    break;
1421                case MILLIS:
1422                    value = period.get(DurationFieldType.millis());
1423                    break;
1424                case SECONDS_MILLIS: // drop through
1425                case SECONDS_OPTIONAL_MILLIS:
1426                    int seconds = period.get(DurationFieldType.seconds());
1427                    int millis = period.get(DurationFieldType.millis());
1428                    value = (seconds * (long) DateTimeConstants.MILLIS_PER_SECOND) + millis;
1429                    break;
1430                }
1431    
1432                // determine if period is zero and this is the last field
1433                if (value == 0) {
1434                    switch (iPrintZeroSetting) {
1435                    case PRINT_ZERO_NEVER:
1436                        return Long.MAX_VALUE;
1437                    case PRINT_ZERO_RARELY_LAST:
1438                        if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1439                            for (int i = iFieldType + 1; i <= MAX_FIELD; i++) {
1440                                if (isSupported(type, i) && iFieldFormatters[i] != null) {
1441                                    return Long.MAX_VALUE;
1442                                }
1443                            }
1444                        } else {
1445                            return Long.MAX_VALUE;
1446                        }
1447                        break;
1448                    case PRINT_ZERO_RARELY_FIRST:
1449                        if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1450                            int i = Math.min(iFieldType, 8);  // line split out for IBM JDK
1451                            i--;                              // see bug 1660490
1452                            for (; i >= 0 && i <= MAX_FIELD; i--) {
1453                                if (isSupported(type, i) && iFieldFormatters[i] != null) {
1454                                    return Long.MAX_VALUE;
1455                                }
1456                            }
1457                        } else {
1458                            return Long.MAX_VALUE;
1459                        }
1460                        break;
1461                    }
1462                }
1463    
1464                return value;
1465            }
1466    
1467            boolean isZero(ReadablePeriod period) {
1468                for (int i = 0, isize = period.size(); i < isize; i++) {
1469                    if (period.getValue(i) != 0) {
1470                        return false;
1471                    }
1472                }
1473                return true;
1474            }
1475    
1476            boolean isSupported(PeriodType type, int field) {
1477                switch (field) {
1478                default:
1479                    return false;
1480                case YEARS:
1481                    return type.isSupported(DurationFieldType.years());
1482                case MONTHS:
1483                    return type.isSupported(DurationFieldType.months());
1484                case WEEKS:
1485                    return type.isSupported(DurationFieldType.weeks());
1486                case DAYS:
1487                    return type.isSupported(DurationFieldType.days());
1488                case HOURS:
1489                    return type.isSupported(DurationFieldType.hours());
1490                case MINUTES:
1491                    return type.isSupported(DurationFieldType.minutes());
1492                case SECONDS:
1493                    return type.isSupported(DurationFieldType.seconds());
1494                case MILLIS:
1495                    return type.isSupported(DurationFieldType.millis());
1496                case SECONDS_MILLIS: // drop through
1497                case SECONDS_OPTIONAL_MILLIS:
1498                    return type.isSupported(DurationFieldType.seconds()) ||
1499                           type.isSupported(DurationFieldType.millis());
1500                }
1501            }
1502    
1503            void setFieldValue(ReadWritablePeriod period, int field, int value) {
1504                switch (field) {
1505                default:
1506                    break;
1507                case YEARS:
1508                    period.setYears(value);
1509                    break;
1510                case MONTHS:
1511                    period.setMonths(value);
1512                    break;
1513                case WEEKS:
1514                    period.setWeeks(value);
1515                    break;
1516                case DAYS:
1517                    period.setDays(value);
1518                    break;
1519                case HOURS:
1520                    period.setHours(value);
1521                    break;
1522                case MINUTES:
1523                    period.setMinutes(value);
1524                    break;
1525                case SECONDS:
1526                    period.setSeconds(value);
1527                    break;
1528                case MILLIS:
1529                    period.setMillis(value);
1530                    break;
1531                }
1532            }
1533    
1534            int getFieldType() {
1535                return iFieldType;
1536            }
1537        }
1538    
1539        //-----------------------------------------------------------------------
1540        /**
1541         * Handles a simple literal piece of text.
1542         */
1543        static class Literal
1544                implements PeriodPrinter, PeriodParser {
1545            static final Literal EMPTY = new Literal("");
1546            private final String iText;
1547    
1548            Literal(String text) {
1549                iText = text;
1550            }
1551    
1552            public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1553                return 0;
1554            }
1555    
1556            public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1557                return iText.length();
1558            }
1559    
1560            public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1561                buf.append(iText);
1562            }
1563    
1564            public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1565                out.write(iText);
1566            }
1567    
1568            public int parseInto(
1569                    ReadWritablePeriod period, String periodStr,
1570                    int position, Locale locale) {
1571                if (periodStr.regionMatches(true, position, iText, 0, iText.length())) {
1572                    return position + iText.length();
1573                }
1574                return ~position;
1575            }
1576        }
1577    
1578        //-----------------------------------------------------------------------
1579        /**
1580         * Handles a separator, that splits the fields into multiple parts.
1581         * For example, the 'T' in the ISO8601 standard.
1582         */
1583        static class Separator
1584                implements PeriodPrinter, PeriodParser {
1585            private final String iText;
1586            private final String iFinalText;
1587            private final String[] iParsedForms;
1588    
1589            private final boolean iUseBefore;
1590            private final boolean iUseAfter;
1591    
1592            private final PeriodPrinter iBeforePrinter;
1593            private volatile PeriodPrinter iAfterPrinter;
1594            private final PeriodParser iBeforeParser;
1595            private volatile PeriodParser iAfterParser;
1596    
1597            Separator(String text, String finalText, String[] variants,
1598                    PeriodPrinter beforePrinter, PeriodParser beforeParser,
1599                    boolean useBefore, boolean useAfter) {
1600                iText = text;
1601                iFinalText = finalText;
1602    
1603                if ((finalText == null || text.equals(finalText)) &&
1604                    (variants == null || variants.length == 0)) {
1605    
1606                    iParsedForms = new String[] {text};
1607                } else {
1608                    // Filter and reverse sort the parsed forms.
1609                    TreeSet<String> parsedSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
1610                    parsedSet.add(text);
1611                    parsedSet.add(finalText);
1612                    if (variants != null) {
1613                        for (int i=variants.length; --i>=0; ) {
1614                            parsedSet.add(variants[i]);
1615                        }
1616                    }
1617                    ArrayList<String> parsedList = new ArrayList<String>(parsedSet);
1618                    Collections.reverse(parsedList);
1619                    iParsedForms = parsedList.toArray(new String[parsedList.size()]);
1620                }
1621    
1622                iBeforePrinter = beforePrinter;
1623                iBeforeParser = beforeParser;
1624                iUseBefore = useBefore;
1625                iUseAfter = useAfter;
1626            }
1627    
1628            public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1629                int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale);
1630                if (sum < stopAt) {
1631                    sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale);
1632                }
1633                return sum;
1634            }
1635    
1636            public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1637                PeriodPrinter before = iBeforePrinter;
1638                PeriodPrinter after = iAfterPrinter;
1639                
1640                int sum = before.calculatePrintedLength(period, locale)
1641                        + after.calculatePrintedLength(period, locale);
1642                
1643                if (iUseBefore) {
1644                    if (before.countFieldsToPrint(period, 1, locale) > 0) {
1645                        if (iUseAfter) {
1646                            int afterCount = after.countFieldsToPrint(period, 2, locale);
1647                            if (afterCount > 0) {
1648                                sum += (afterCount > 1 ? iText : iFinalText).length();
1649                            }
1650                        } else {
1651                            sum += iText.length();
1652                        }
1653                    }
1654                } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1655                    sum += iText.length();
1656                }
1657                
1658                return sum;
1659            }
1660    
1661            public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1662                PeriodPrinter before = iBeforePrinter;
1663                PeriodPrinter after = iAfterPrinter;
1664                
1665                before.printTo(buf, period, locale);
1666                if (iUseBefore) {
1667                    if (before.countFieldsToPrint(period, 1, locale) > 0) {
1668                        if (iUseAfter) {
1669                            int afterCount = after.countFieldsToPrint(period, 2, locale);
1670                            if (afterCount > 0) {
1671                                buf.append(afterCount > 1 ? iText : iFinalText);
1672                            }
1673                        } else {
1674                            buf.append(iText);
1675                        }
1676                    }
1677                } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1678                    buf.append(iText);
1679                }
1680                after.printTo(buf, period, locale);
1681            }
1682    
1683            public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1684                PeriodPrinter before = iBeforePrinter;
1685                PeriodPrinter after = iAfterPrinter;
1686                
1687                before.printTo(out, period, locale);
1688                if (iUseBefore) {
1689                    if (before.countFieldsToPrint(period, 1, locale) > 0) {
1690                        if (iUseAfter) {
1691                            int afterCount = after.countFieldsToPrint(period, 2, locale);
1692                            if (afterCount > 0) {
1693                                out.write(afterCount > 1 ? iText : iFinalText);
1694                            }
1695                        } else {
1696                            out.write(iText);
1697                        }
1698                    }
1699                } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1700                    out.write(iText);
1701                }
1702                after.printTo(out, period, locale);
1703            }
1704    
1705            public int parseInto(
1706                    ReadWritablePeriod period, String periodStr,
1707                    int position, Locale locale) {
1708                int oldPos = position;
1709                position = iBeforeParser.parseInto(period, periodStr, position, locale);
1710    
1711                if (position < 0) {
1712                    return position;
1713                }
1714    
1715                boolean found = false;
1716                int parsedFormLength = -1;
1717                if (position > oldPos) {
1718                    // Consume this separator.
1719                    String[] parsedForms = iParsedForms;
1720                    int length = parsedForms.length;
1721                    for (int i=0; i < length; i++) {
1722                        String parsedForm = parsedForms[i];
1723                        if ((parsedForm == null || parsedForm.length() == 0) ||
1724                            periodStr.regionMatches
1725                            (true, position, parsedForm, 0, parsedForm.length())) {
1726                            
1727                            parsedFormLength = (parsedForm == null ? 0 : parsedForm.length());
1728                            position += parsedFormLength;
1729                            found = true;
1730                            break;
1731                        }
1732                    }
1733                }
1734    
1735                oldPos = position;
1736                position = iAfterParser.parseInto(period, periodStr, position, locale);
1737    
1738                if (position < 0) {
1739                    return position;
1740                }
1741    
1742                if (found && position == oldPos && parsedFormLength > 0) {
1743                    // Separator should not have been supplied.
1744                    return ~oldPos;
1745                }
1746    
1747                if (position > oldPos && !found && !iUseBefore) {
1748                    // Separator was required.
1749                    return ~oldPos;
1750                }
1751    
1752                return position;
1753            }
1754    
1755            Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) {
1756                iAfterPrinter = afterPrinter;
1757                iAfterParser = afterParser;
1758                return this;
1759            }
1760        }
1761    
1762        //-----------------------------------------------------------------------
1763        /**
1764         * Composite implementation that merges other fields to create a full pattern.
1765         */
1766        static class Composite
1767                implements PeriodPrinter, PeriodParser {
1768            
1769            private final PeriodPrinter[] iPrinters;
1770            private final PeriodParser[] iParsers;
1771    
1772            Composite(List<Object> elementPairs) {
1773                List<Object> printerList = new ArrayList<Object>();
1774                List<Object> parserList = new ArrayList<Object>();
1775    
1776                decompose(elementPairs, printerList, parserList);
1777    
1778                if (printerList.size() <= 0) {
1779                    iPrinters = null;
1780                } else {
1781                    iPrinters = printerList.toArray(
1782                            new PeriodPrinter[printerList.size()]);
1783                }
1784    
1785                if (parserList.size() <= 0) {
1786                    iParsers = null;
1787                } else {
1788                    iParsers = parserList.toArray(
1789                            new PeriodParser[parserList.size()]);
1790                }
1791            }
1792    
1793            public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1794                int sum = 0;
1795                PeriodPrinter[] printers = iPrinters;
1796                for (int i=printers.length; sum < stopAt && --i>=0; ) {
1797                    sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale);
1798                }
1799                return sum;
1800            }
1801    
1802            public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1803                int sum = 0;
1804                PeriodPrinter[] printers = iPrinters;
1805                for (int i=printers.length; --i>=0; ) {
1806                    sum += printers[i].calculatePrintedLength(period, locale);
1807                }
1808                return sum;
1809            }
1810    
1811            public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1812                PeriodPrinter[] printers = iPrinters;
1813                int len = printers.length;
1814                for (int i=0; i<len; i++) {
1815                    printers[i].printTo(buf, period, locale);
1816                }
1817            }
1818    
1819            public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1820                PeriodPrinter[] printers = iPrinters;
1821                int len = printers.length;
1822                for (int i=0; i<len; i++) {
1823                    printers[i].printTo(out, period, locale);
1824                }
1825            }
1826    
1827            public int parseInto(
1828                    ReadWritablePeriod period, String periodStr,
1829                    int position, Locale locale) {
1830                PeriodParser[] parsers = iParsers;
1831                if (parsers == null) {
1832                    throw new UnsupportedOperationException();
1833                }
1834    
1835                int len = parsers.length;
1836                for (int i=0; i<len && position >= 0; i++) {
1837                    position = parsers[i].parseInto(period, periodStr, position, locale);
1838                }
1839                return position;
1840            }
1841    
1842            private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
1843                int size = elementPairs.size();
1844                for (int i=0; i<size; i+=2) {
1845                    Object element = elementPairs.get(i);
1846                    if (element instanceof PeriodPrinter) {
1847                        if (element instanceof Composite) {
1848                            addArrayToList(printerList, ((Composite) element).iPrinters);
1849                        } else {
1850                            printerList.add(element);
1851                        }
1852                    }
1853    
1854                    element = elementPairs.get(i + 1);
1855                    if (element instanceof PeriodParser) {
1856                        if (element instanceof Composite) {
1857                            addArrayToList(parserList, ((Composite) element).iParsers);
1858                        } else {
1859                            parserList.add(element);
1860                        }
1861                    }
1862                }
1863            }
1864    
1865            private void addArrayToList(List<Object> list, Object[] array) {
1866                if (array != null) {
1867                    for (int i=0; i<array.length; i++) {
1868                        list.add(array[i]);
1869                    }
1870                }
1871            }
1872        }
1873    
1874    }