View Javadoc

1   /*
2    *  Copyright 2001-2009 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.format;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.TreeSet;
25  
26  import org.joda.time.DateTimeConstants;
27  import org.joda.time.DurationFieldType;
28  import org.joda.time.PeriodType;
29  import org.joda.time.ReadWritablePeriod;
30  import org.joda.time.ReadablePeriod;
31  
32  /**
33   * Factory that creates complex instances of PeriodFormatter via method calls.
34   * <p>
35   * Period formatting is performed by the {@link PeriodFormatter} class.
36   * Three classes provide factory methods to create formatters, and this is one.
37   * The others are {@link PeriodFormat} and {@link ISOPeriodFormat}.
38   * <p>
39   * PeriodFormatterBuilder is used for constructing formatters which are then
40   * used to print or parse. The formatters are built by appending specific fields
41   * or other formatters to an instance of this builder.
42   * <p>
43   * For example, a formatter that prints years and months, like "15 years and 8 months",
44   * can be constructed as follows:
45   * <p>
46   * <pre>
47   * PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
48   *     .printZeroAlways()
49   *     .appendYears()
50   *     .appendSuffix(" year", " years")
51   *     .appendSeparator(" and ")
52   *     .printZeroRarelyLast()
53   *     .appendMonths()
54   *     .appendSuffix(" month", " months")
55   *     .toFormatter();
56   * </pre>
57   * <p>
58   * PeriodFormatterBuilder itself is mutable and not thread-safe, but the
59   * formatters that it builds are thread-safe and immutable.
60   *
61   * @author Brian S O'Neill
62   * @since 1.0
63   * @see PeriodFormat
64   */
65  public class PeriodFormatterBuilder {
66      private static final int PRINT_ZERO_RARELY_FIRST = 1;
67      private static final int PRINT_ZERO_RARELY_LAST = 2;
68      private static final int PRINT_ZERO_IF_SUPPORTED = 3;
69      private static final int PRINT_ZERO_ALWAYS = 4;
70      private static final int PRINT_ZERO_NEVER = 5;
71      
72      private static final int YEARS = 0;
73      private static final int MONTHS = 1;
74      private static final int WEEKS = 2;
75      private static final int DAYS = 3;
76      private static final int HOURS = 4;
77      private static final int MINUTES = 5;
78      private static final int SECONDS = 6;
79      private static final int MILLIS = 7;
80      private static final int SECONDS_MILLIS = 8;
81      private static final int SECONDS_OPTIONAL_MILLIS = 9;
82      private static final int MAX_FIELD = SECONDS_OPTIONAL_MILLIS;
83  
84      private int iMinPrintedDigits;
85      private int iPrintZeroSetting;
86      private int iMaxParsedDigits;
87      private boolean iRejectSignedValues;
88  
89      private PeriodFieldAffix iPrefix;
90  
91      // List of Printers and Parsers used to build a final formatter.
92      private List<Object> iElementPairs;
93      /** Set to true if the formatter is not a printer. */
94      private boolean iNotPrinter;
95      /** Set to true if the formatter is not a parser. */
96      private boolean iNotParser;
97  
98      // Last PeriodFormatter appended of each field type.
99      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 }