KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > joda > time > format > PeriodFormatterBuilder


1 /*
2  * Copyright 2001-2006 Stephen Colebourne
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.joda.time.format;
17
18 import java.io.IOException JavaDoc;
19 import java.io.Writer JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Locale JavaDoc;
24 import java.util.TreeSet JavaDoc;
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  * .printZeroRarely()
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
83     private int iMinPrintedDigits;
84     private int iPrintZeroSetting;
85     private int iMaxParsedDigits;
86     private boolean iRejectSignedValues;
87
88     private PeriodFieldAffix iPrefix;
89
90     // List of Printers and Parsers used to build a final formatter.
91
private List JavaDoc iElementPairs;
92     /** Set to true if the formatter is not a printer. */
93     private boolean iNotPrinter;
94     /** Set to true if the formatter is not a parser. */
95     private boolean iNotParser;
96
97     // Last PeriodFormatter appended of each field type.
98
private FieldFormatter[] iFieldFormatters;
99
100     public PeriodFormatterBuilder() {
101         clear();
102     }
103
104     //-----------------------------------------------------------------------
105
/**
106      * Constructs a PeriodFormatter using all the appended elements.
107      * <p>
108      * This is the main method used by applications at the end of the build
109      * process to create a usable formatter.
110      * <p>
111      * Subsequent changes to this builder do not affect the returned formatter.
112      * <p>
113      * The returned formatter may not support both printing and parsing.
114      * The methods {@link PeriodFormatter#isPrinter()} and
115      * {@link PeriodFormatter#isParser()} will help you determine the state
116      * of the formatter.
117      *
118      * @return the newly created formatter
119      * @throws IllegalStateException if the builder can produce neither a printer nor a parser
120      */

121     public PeriodFormatter toFormatter() {
122         PeriodFormatter formatter = toFormatter(iElementPairs, iNotPrinter, iNotParser);
123         iFieldFormatters = (FieldFormatter[]) iFieldFormatters.clone();
124         return formatter;
125     }
126
127     /**
128      * Internal method to create a PeriodPrinter instance using all the
129      * appended elements.
130      * <p>
131      * Most applications will not use this method.
132      * If you want a printer in an application, call {@link #toFormatter()}
133      * and just use the printing API.
134      * <p>
135      * Subsequent changes to this builder do not affect the returned printer.
136      *
137      * @return the newly created printer, null if builder cannot create a printer
138      */

139     public PeriodPrinter toPrinter() {
140         if (iNotPrinter) {
141             return null;
142         }
143         return toFormatter().getPrinter();
144     }
145
146     /**
147      * Internal method to create a PeriodParser instance using all the
148      * appended elements.
149      * <p>
150      * Most applications will not use this method.
151      * If you want a printer in an application, call {@link #toFormatter()}
152      * and just use the printing API.
153      * <p>
154      * Subsequent changes to this builder do not affect the returned parser.
155      *
156      * @return the newly created parser, null if builder cannot create a parser
157      */

158     public PeriodParser toParser() {
159         if (iNotParser) {
160             return null;
161         }
162         return toFormatter().getParser();
163     }
164
165     //-----------------------------------------------------------------------
166
/**
167      * Clears out all the appended elements, allowing this builder to be reused.
168      */

169     public void clear() {
170         iMinPrintedDigits = 1;
171         iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
172         iMaxParsedDigits = 10;
173         iRejectSignedValues = false;
174         iPrefix = null;
175         if (iElementPairs == null) {
176             iElementPairs = new ArrayList JavaDoc();
177         } else {
178             iElementPairs.clear();
179         }
180         iNotPrinter = false;
181         iNotParser = false;
182         iFieldFormatters = new FieldFormatter[10];
183     }
184
185     /**
186      * Appends another formatter.
187      *
188      * @return this PeriodFormatterBuilder
189      */

190     public PeriodFormatterBuilder append(PeriodFormatter formatter) {
191         if (formatter == null) {
192             throw new IllegalArgumentException JavaDoc("No formatter supplied");
193         }
194         clearPrefix();
195         append0(formatter.getPrinter(), formatter.getParser());
196         return this;
197     }
198
199     /**
200      * Appends a printer parser pair.
201      * <p>
202      * Either the printer or the parser may be null, in which case the builder will
203      * be unable to produce a parser or printer repectively.
204      *
205      * @param printer appends a printer to the builder, null if printing is not supported
206      * @param parser appends a parser to the builder, null if parsing is not supported
207      * @return this PeriodFormatterBuilder
208      * @throws IllegalArgumentException if both the printer and parser are null
209      */

210     public PeriodFormatterBuilder append(PeriodPrinter printer, PeriodParser parser) {
211         if (printer == null && parser == null) {
212             throw new IllegalArgumentException JavaDoc("No printer or parser supplied");
213         }
214         clearPrefix();
215         append0(printer, parser);
216         return this;
217     }
218
219     /**
220      * Instructs the printer to emit specific text, and the parser to expect it.
221      * The parser is case-insensitive.
222      *
223      * @return this PeriodFormatterBuilder
224      * @throws IllegalArgumentException if text is null
225      */

226     public PeriodFormatterBuilder appendLiteral(String JavaDoc text) {
227         if (text == null) {
228             throw new IllegalArgumentException JavaDoc("Literal must not be null");
229         }
230         clearPrefix();
231         Literal literal = new Literal(text);
232         append0(literal, literal);
233         return this;
234     }
235
236     /**
237      * Set the minimum digits printed for the next and following appended
238      * fields. By default, the minimum digits printed is one. If the field value
239      * is zero, it is not printed unless a printZero rule is applied.
240      *
241      * @return this PeriodFormatterBuilder
242      */

243     public PeriodFormatterBuilder minimumPrintedDigits(int minDigits) {
244         iMinPrintedDigits = minDigits;
245         return this;
246     }
247
248     /**
249      * Set the maximum digits parsed for the next and following appended
250      * fields. By default, the maximum digits parsed is ten.
251      *
252      * @return this PeriodFormatterBuilder
253      */

254     public PeriodFormatterBuilder maximumParsedDigits(int maxDigits) {
255         iMaxParsedDigits = maxDigits;
256         return this;
257     }
258
259     /**
260      * Reject signed values when parsing the next and following appended fields.
261      *
262      * @return this PeriodFormatterBuilder
263      */

264     public PeriodFormatterBuilder rejectSignedValues(boolean v) {
265         iRejectSignedValues = v;
266         return this;
267     }
268
269     /**
270      * Never print zero values for the next and following appended fields,
271      * unless no fields would be printed. If no fields are printed, the printer
272      * forces the last "printZeroRarely" field to print a zero.
273      * <p>
274      * This field setting is the default.
275      *
276      * @return this PeriodFormatterBuilder
277      */

278     public PeriodFormatterBuilder printZeroRarelyLast() {
279         iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
280         return this;
281     }
282
283     /**
284      * Never print zero values for the next and following appended fields,
285      * unless no fields would be printed. If no fields are printed, the printer
286      * forces the first "printZeroRarely" field to print a zero.
287      *
288      * @return this PeriodFormatterBuilder
289      */

290     public PeriodFormatterBuilder printZeroRarelyFirst() {
291         iPrintZeroSetting = PRINT_ZERO_RARELY_FIRST;
292         return this;
293     }
294
295     /**
296      * Print zero values for the next and following appened fields only if the
297      * period supports it.
298      *
299      * @return this PeriodFormatterBuilder
300      */

301     public PeriodFormatterBuilder printZeroIfSupported() {
302         iPrintZeroSetting = PRINT_ZERO_IF_SUPPORTED;
303         return this;
304     }
305
306     /**
307      * Always print zero values for the next and following appended fields,
308      * even if the period doesn't support it. The parser requires values for
309      * fields that always print zero.
310      *
311      * @return this PeriodFormatterBuilder
312      */

313     public PeriodFormatterBuilder printZeroAlways() {
314         iPrintZeroSetting = PRINT_ZERO_ALWAYS;
315         return this;
316     }
317
318     /**
319      * Never print zero values for the next and following appended fields,
320      * unless no fields would be printed. If no fields are printed, the printer
321      * forces the last "printZeroRarely" field to print a zero.
322      * <p>
323      * This field setting is the default.
324      *
325      * @return this PeriodFormatterBuilder
326      */

327     public PeriodFormatterBuilder printZeroNever() {
328         iPrintZeroSetting = PRINT_ZERO_NEVER;
329         return this;
330     }
331
332     //-----------------------------------------------------------------------
333
/**
334      * Append a field prefix which applies only to the next appended field. If
335      * the field is not printed, neither is the prefix.
336      *
337      * @param text text to print before field only if field is printed
338      * @return this PeriodFormatterBuilder
339      * @see #appendSuffix
340      */

341     public PeriodFormatterBuilder appendPrefix(String JavaDoc text) {
342         if (text == null) {
343             throw new IllegalArgumentException JavaDoc();
344         }
345         return appendPrefix(new SimpleAffix(text));
346     }
347
348     /**
349      * Append a field prefix which applies only to the next appended field. If
350      * the field is not printed, neither is the prefix.
351      * <p>
352      * During parsing, the singular and plural versions are accepted whether
353      * or not the actual value matches plurality.
354      *
355      * @param singularText text to print if field value is one
356      * @param pluralText text to print if field value is not one
357      * @return this PeriodFormatterBuilder
358      * @see #appendSuffix
359      */

360     public PeriodFormatterBuilder appendPrefix(String JavaDoc singularText,
361                                                  String JavaDoc pluralText) {
362         if (singularText == null || pluralText == null) {
363             throw new IllegalArgumentException JavaDoc();
364         }
365         return appendPrefix(new PluralAffix(singularText, pluralText));
366     }
367
368     /**
369      * Append a field prefix which applies only to the next appended field. If
370      * the field is not printed, neither is the prefix.
371      *
372      * @param prefix custom prefix
373      * @return this PeriodFormatterBuilder
374      * @see #appendSuffix
375      */

376     private PeriodFormatterBuilder appendPrefix(PeriodFieldAffix prefix) {
377         if (prefix == null) {
378             throw new IllegalArgumentException JavaDoc();
379         }
380         if (iPrefix != null) {
381             prefix = new CompositeAffix(iPrefix, prefix);
382         }
383         iPrefix = prefix;
384         return this;
385     }
386
387     //-----------------------------------------------------------------------
388
/**
389      * Instruct the printer to emit an integer years field, if supported.
390      * <p>
391      * The number of printed and parsed digits can be controlled using
392      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
393      *
394      * @return this PeriodFormatterBuilder
395      */

396     public PeriodFormatterBuilder appendYears() {
397         appendField(YEARS);
398         return this;
399     }
400
401     /**
402      * Instruct the printer to emit an integer months field, if supported.
403      * <p>
404      * The number of printed and parsed digits can be controlled using
405      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
406      *
407      * @return this PeriodFormatterBuilder
408      */

409     public PeriodFormatterBuilder appendMonths() {
410         appendField(MONTHS);
411         return this;
412     }
413
414     /**
415      * Instruct the printer to emit an integer weeks field, if supported.
416      * <p>
417      * The number of printed and parsed digits can be controlled using
418      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
419      *
420      * @return this PeriodFormatterBuilder
421      */

422     public PeriodFormatterBuilder appendWeeks() {
423         appendField(WEEKS);
424         return this;
425     }
426
427     /**
428      * Instruct the printer to emit an integer days field, if supported.
429      * <p>
430      * The number of printed and parsed digits can be controlled using
431      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
432      *
433      * @return this PeriodFormatterBuilder
434      */

435     public PeriodFormatterBuilder appendDays() {
436         appendField(DAYS);
437         return this;
438     }
439
440     /**
441      * Instruct the printer to emit an integer hours field, if supported.
442      * <p>
443      * The number of printed and parsed digits can be controlled using
444      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
445      *
446      * @return this PeriodFormatterBuilder
447      */

448     public PeriodFormatterBuilder appendHours() {
449         appendField(HOURS);
450         return this;
451     }
452
453     /**
454      * Instruct the printer to emit an integer minutes field, if supported.
455      * <p>
456      * The number of printed and parsed digits can be controlled using
457      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
458      *
459      * @return this PeriodFormatterBuilder
460      */

461     public PeriodFormatterBuilder appendMinutes() {
462         appendField(MINUTES);
463         return this;
464     }
465
466     /**
467      * Instruct the printer to emit an integer seconds field, if supported.
468      * <p>
469      * The number of printed and parsed digits can be controlled using
470      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
471      *
472      * @return this PeriodFormatterBuilder
473      */

474     public PeriodFormatterBuilder appendSeconds() {
475         appendField(SECONDS);
476         return this;
477     }
478
479     /**
480      * Instruct the printer to emit a combined seconds and millis field, if supported.
481      * The millis will overflow into the seconds if necessary.
482      * The millis are always output.
483      *
484      * @return this PeriodFormatterBuilder
485      */

486     public PeriodFormatterBuilder appendSecondsWithMillis() {
487         appendField(SECONDS_MILLIS);
488         return this;
489     }
490
491     /**
492      * Instruct the printer to emit a combined seconds and millis field, if supported.
493      * The millis will overflow into the seconds if necessary.
494      * The millis are only output if non-zero.
495      *
496      * @return this PeriodFormatterBuilder
497      */

498     public PeriodFormatterBuilder appendSecondsWithOptionalMillis() {
499         appendField(SECONDS_OPTIONAL_MILLIS);
500         return this;
501     }
502
503     /**
504      * Instruct the printer to emit an integer millis field, if supported.
505      * <p>
506      * The number of printed and parsed digits can be controlled using
507      * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
508      *
509      * @return this PeriodFormatterBuilder
510      */

511     public PeriodFormatterBuilder appendMillis() {
512         appendField(MILLIS);
513         return this;
514     }
515
516     /**
517      * Instruct the printer to emit an integer millis field, if supported.
518      * <p>
519      * The number of arsed digits can be controlled using {@link #maximumParsedDigits(int)}.
520      *
521      * @return this PeriodFormatterBuilder
522      */

523     public PeriodFormatterBuilder appendMillis3Digit() {
524         appendField(7, 3);
525         return this;
526     }
527
528     private void appendField(int type) {
529         appendField(type, iMinPrintedDigits);
530     }
531
532     private void appendField(int type, int minPrinted) {
533         FieldFormatter field = new FieldFormatter(minPrinted, iPrintZeroSetting,
534             iMaxParsedDigits, iRejectSignedValues, type, iFieldFormatters, iPrefix, null);
535         append0(field, field);
536         iFieldFormatters[type] = field;
537         iPrefix = null;
538     }
539
540     //-----------------------------------------------------------------------
541
/**
542      * Append a field suffix which applies only to the last appended field. If
543      * the field is not printed, neither is the suffix.
544      *
545      * @param text text to print after field only if field is printed
546      * @return this PeriodFormatterBuilder
547      * @throws IllegalStateException if no field exists to append to
548      * @see #appendPrefix
549      */

550     public PeriodFormatterBuilder appendSuffix(String JavaDoc text) {
551         if (text == null) {
552             throw new IllegalArgumentException JavaDoc();
553         }
554         return appendSuffix(new SimpleAffix(text));
555     }
556
557     /**
558      * Append a field suffix which applies only to the last appended field. If
559      * the field is not printed, neither is the suffix.
560      * <p>
561      * During parsing, the singular and plural versions are accepted whether or
562      * not the actual value matches plurality.
563      *
564      * @param singularText text to print if field value is one
565      * @param pluralText text to print if field value is not one
566      * @return this PeriodFormatterBuilder
567      * @throws IllegalStateException if no field exists to append to
568      * @see #appendPrefix
569      */

570     public PeriodFormatterBuilder appendSuffix(String JavaDoc singularText,
571                                                String JavaDoc pluralText) {
572         if (singularText == null || pluralText == null) {
573             throw new IllegalArgumentException JavaDoc();
574         }
575         return appendSuffix(new PluralAffix(singularText, pluralText));
576     }
577
578     /**
579      * Append a field suffix which applies only to the last appended field. If
580      * the field is not printed, neither is the suffix.
581      *
582      * @param suffix custom suffix
583      * @return this PeriodFormatterBuilder
584      * @throws IllegalStateException if no field exists to append to
585      * @see #appendPrefix
586      */

587     private PeriodFormatterBuilder appendSuffix(PeriodFieldAffix suffix) {
588         final Object JavaDoc originalPrinter;
589         final Object JavaDoc originalParser;
590         if (iElementPairs.size() > 0) {
591             originalPrinter = iElementPairs.get(iElementPairs.size() - 2);
592             originalParser = iElementPairs.get(iElementPairs.size() - 1);
593         } else {
594             originalPrinter = null;
595             originalParser = null;
596         }
597
598         if (originalPrinter == null || originalParser == null ||
599                 originalPrinter != originalParser ||
600                 !(originalPrinter instanceof FieldFormatter)) {
601             throw new IllegalStateException JavaDoc("No field to apply suffix to");
602         }
603
604         clearPrefix();
605         FieldFormatter newField = new FieldFormatter((FieldFormatter) originalPrinter, suffix);
606         iElementPairs.set(iElementPairs.size() - 2, newField);
607         iElementPairs.set(iElementPairs.size() - 1, newField);
608         iFieldFormatters[newField.getFieldType()] = newField;
609         
610         return this;
611     }
612
613     //-----------------------------------------------------------------------
614
/**
615      * Append a separator, which is output if fields are printed both before
616      * and after the separator.
617      * <p>
618      * For example, <code>builder.appendDays().appendSeparator(",").appendHours()</code>
619      * will only output the comma if both the days and hours fields are output.
620      * <p>
621      * The text will be parsed case-insensitively.
622      * <p>
623      * Note: appending a separator discontinues any further work on the latest
624      * appended field.
625      *
626      * @param text the text to use as a separator
627      * @return this PeriodFormatterBuilder
628      * @throws IllegalStateException if this separator follows a previous one
629      */

630     public PeriodFormatterBuilder appendSeparator(String JavaDoc text) {
631         return appendSeparator(text, text, null, true, true);
632     }
633
634     /**
635      * Append a separator, which is output only if fields are printed after the separator.
636      * <p>
637      * For example,
638      * <code>builder.appendDays().appendSeparatorIfFieldsAfter(",").appendHours()</code>
639      * will only output the comma if the hours fields is output.
640      * <p>
641      * The text will be parsed case-insensitively.
642      * <p>
643      * Note: appending a separator discontinues any further work on the latest
644      * appended field.
645      *
646      * @param text the text to use as a separator
647      * @return this PeriodFormatterBuilder
648      * @throws IllegalStateException if this separator follows a previous one
649      */

650     public PeriodFormatterBuilder appendSeparatorIfFieldsAfter(String JavaDoc text) {
651         return appendSeparator(text, text, null, false, true);
652     }
653
654     /**
655      * Append a separator, which is output only if fields are printed before the separator.
656      * <p>
657      * For example,
658      * <code>builder.appendDays().appendSeparatorIfFieldsBefore(",").appendHours()</code>
659      * will only output the comma if the days fields is output.
660      * <p>
661      * The text will be parsed case-insensitively.
662      * <p>
663      * Note: appending a separator discontinues any further work on the latest
664      * appended field.
665      *
666      * @param text the text to use as a separator
667      * @return this PeriodFormatterBuilder
668      * @throws IllegalStateException if this separator follows a previous one
669      */

670     public PeriodFormatterBuilder appendSeparatorIfFieldsBefore(String JavaDoc text) {
671         return appendSeparator(text, text, null, true, false);
672     }
673
674     /**
675      * Append a separator, which is output if fields are printed both before
676      * and after the separator.
677      * <p>
678      * This method changes the separator depending on whether it is the last separator
679      * to be output.
680      * <p>
681      * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
682      * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
683      * and '1' if just one field is output.
684      * <p>
685      * The text will be parsed case-insensitively.
686      * <p>
687      * Note: appending a separator discontinues any further work on the latest
688      * appended field.
689      *
690      * @param text the text to use as a separator
691      * @param finalText the text used used if this is the final separator to be printed
692      * @return this PeriodFormatterBuilder
693      * @throws IllegalStateException if this separator follows a previous one
694      */

695     public PeriodFormatterBuilder appendSeparator(String JavaDoc text, String JavaDoc finalText) {
696         return appendSeparator(text, finalText, null, true, true);
697     }
698
699     /**
700      * Append a separator, which is output if fields are printed both before
701      * and after the separator.
702      * <p>
703      * This method changes the separator depending on whether it is the last separator
704      * to be output.
705      * <p>
706      * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
707      * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
708      * and '1' if just one field is output.
709      * <p>
710      * The text will be parsed case-insensitively.
711      * <p>
712      * Note: appending a separator discontinues any further work on the latest
713      * appended field.
714      *
715      * @param text the text to use as a separator
716      * @param finalText the text used used if this is the final separator to be printed
717      * @param variants set of text values which are also acceptable when parsed
718      * @return this PeriodFormatterBuilder
719      * @throws IllegalStateException if this separator follows a previous one
720      */

721     public PeriodFormatterBuilder appendSeparator(String JavaDoc text, String JavaDoc finalText,
722                                                   String JavaDoc[] variants) {
723         return appendSeparator(text, finalText, variants, true, true);
724     }
725
726     private PeriodFormatterBuilder appendSeparator(String JavaDoc text, String JavaDoc finalText,
727                                                    String JavaDoc[] variants,
728                                                    boolean useBefore, boolean useAfter) {
729         if (text == null || finalText == null) {
730             throw new IllegalArgumentException JavaDoc();
731         }
732
733         clearPrefix();
734         
735         // optimise zero formatter case
736
List JavaDoc pairs = iElementPairs;
737         if (pairs.size() == 0) {
738             if (useAfter && useBefore == false) {
739                 Separator separator = new Separator(
740                         text, finalText, variants,
741                         Literal.EMPTY, Literal.EMPTY, useBefore, useAfter);
742                 append0(separator, separator);
743             }
744             return this;
745         }
746         
747         // find the last separator added
748
int i;
749         Separator lastSeparator = null;
750         for (i=pairs.size(); --i>=0; ) {
751             if (pairs.get(i) instanceof Separator) {
752                 lastSeparator = (Separator) pairs.get(i);
753                 pairs = pairs.subList(i + 1, pairs.size());
754                 break;
755             }
756             i--; // element pairs
757
}
758         
759         // merge formatters
760
if (lastSeparator != null && pairs.size() == 0) {
761             throw new IllegalStateException JavaDoc("Cannot have two adjacent separators");
762         } else {
763             Object JavaDoc[] comp = createComposite(pairs);
764             pairs.clear();
765             Separator separator = new Separator(
766                     text, finalText, variants,
767                     (PeriodPrinter) comp[0], (PeriodParser) comp[1],
768                     useBefore, useAfter);
769             pairs.add(separator);
770             pairs.add(separator);
771         }
772         
773         return this;
774     }
775
776     //-----------------------------------------------------------------------
777
private void clearPrefix() throws IllegalStateException JavaDoc {
778         if (iPrefix != null) {
779             throw new IllegalStateException JavaDoc("Prefix not followed by field");
780         }
781         iPrefix = null;
782     }
783
784     private PeriodFormatterBuilder append0(PeriodPrinter printer, PeriodParser parser) {
785         iElementPairs.add(printer);
786         iElementPairs.add(parser);
787         iNotPrinter |= (printer == null);
788         iNotParser |= (parser == null);
789         return this;
790     }
791
792     //-----------------------------------------------------------------------
793
private static PeriodFormatter toFormatter(List JavaDoc elementPairs, boolean notPrinter, boolean notParser) {
794         if (notPrinter && notParser) {
795             throw new IllegalStateException JavaDoc("Builder has created neither a printer nor a parser");
796         }
797         int size = elementPairs.size();
798         if (size >= 2 && elementPairs.get(0) instanceof Separator) {
799             Separator sep = (Separator) elementPairs.get(0);
800             PeriodFormatter f = toFormatter(elementPairs.subList(2, size), notPrinter, notParser);
801             sep = sep.finish(f.getPrinter(), f.getParser());
802             return new PeriodFormatter(sep, sep);
803         }
804         Object JavaDoc[] comp = createComposite(elementPairs);
805         if (notPrinter) {
806             return new PeriodFormatter(null, (PeriodParser) comp[1]);
807         } else if (notParser) {
808             return new PeriodFormatter((PeriodPrinter) comp[0], null);
809         } else {
810             return new PeriodFormatter((PeriodPrinter) comp[0], (PeriodParser) comp[1]);
811         }
812     }
813
814     private static Object JavaDoc[] createComposite(List JavaDoc elementPairs) {
815         switch (elementPairs.size()) {
816             case 0:
817                 return new Object JavaDoc[] {Literal.EMPTY, Literal.EMPTY};
818             case 1:
819                 return new Object JavaDoc[] {elementPairs.get(0), elementPairs.get(1)};
820             default:
821                 Composite comp = new Composite(elementPairs);
822                 return new Object JavaDoc[] {comp, comp};
823         }
824     }
825
826     //-----------------------------------------------------------------------
827
/**
828      * Defines a formatted field's prefix or suffix text.
829      * This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
830      */

831     static interface PeriodFieldAffix {
832         int calculatePrintedLength(int value);
833         
834         void printTo(StringBuffer JavaDoc buf, int value);
835         
836         void printTo(Writer JavaDoc out, int value) throws IOException JavaDoc;
837         
838         /**
839          * @return new position after parsing affix, or ~position of failure
840          */

841         int parse(String JavaDoc periodStr, int position);
842
843         /**
844          * @return position where affix starts, or original ~position if not found
845          */

846         int scan(String JavaDoc periodStr, int position);
847     }
848
849     //-----------------------------------------------------------------------
850
/**
851      * Implements an affix where the text does not vary by the amount.
852      */

853     static class SimpleAffix implements PeriodFieldAffix {
854         private final String JavaDoc iText;
855
856         SimpleAffix(String JavaDoc text) {
857             iText = text;
858         }
859
860         public int calculatePrintedLength(int value) {
861             return iText.length();
862         }
863
864         public void printTo(StringBuffer JavaDoc buf, int value) {
865             buf.append(iText);
866         }
867
868         public void printTo(Writer JavaDoc out, int value) throws IOException JavaDoc {
869             out.write(iText);
870         }
871
872         public int parse(String JavaDoc periodStr, int position) {
873             String JavaDoc text = iText;
874             int textLength = text.length();
875             if (periodStr.regionMatches(true, position, text, 0, textLength)) {
876                 return position + textLength;
877             }
878             return ~position;
879         }
880
881         public int scan(String JavaDoc periodStr, final int position) {
882             String JavaDoc text = iText;
883             int textLength = text.length();
884             int sourceLength = periodStr.length();
885             search:
886             for (int pos = position; pos < sourceLength; pos++) {
887                 if (periodStr.regionMatches(true, pos, text, 0, textLength)) {
888                     return pos;
889                 }
890                 // Only allow number characters to be skipped in search of suffix.
891
switch (periodStr.charAt(pos)) {
892                 case '0': case '1': case '2': case '3': case '4':
893                 case '5': case '6': case '7': case '8': case '9':
894                 case '.': case ',': case '+': case '-':
895                     break;
896                 default:
897                     break search;
898                 }
899             }
900             return ~position;
901         }
902     }
903
904     //-----------------------------------------------------------------------
905
/**
906      * Implements an affix where the text varies by the amount of the field.
907      * Only singular (1) and plural (not 1) are supported.
908      */

909     static class PluralAffix implements PeriodFieldAffix {
910         private final String JavaDoc iSingularText;
911         private final String JavaDoc iPluralText;
912
913         PluralAffix(String JavaDoc singularText, String JavaDoc pluralText) {
914             iSingularText = singularText;
915             iPluralText = pluralText;
916         }
917
918         public int calculatePrintedLength(int value) {
919             return (value == 1 ? iSingularText : iPluralText).length();
920         }
921
922         public void printTo(StringBuffer JavaDoc buf, int value) {
923             buf.append(value == 1 ? iSingularText : iPluralText);
924         }
925
926         public void printTo(Writer JavaDoc out, int value) throws IOException JavaDoc {
927             out.write(value == 1 ? iSingularText : iPluralText);
928         }
929
930         public int parse(String JavaDoc periodStr, int position) {
931             String JavaDoc text1 = iPluralText;
932             String JavaDoc text2 = iSingularText;
933
934             if (text1.length() < text2.length()) {
935                 // Swap in order to match longer one first.
936
String JavaDoc temp = text1;
937                 text1 = text2;
938                 text2 = temp;
939             }
940
941             if (periodStr.regionMatches
942                 (true, position, text1, 0, text1.length())) {
943                 return position + text1.length();
944             }
945             if (periodStr.regionMatches
946                 (true, position, text2, 0, text2.length())) {
947                 return position + text2.length();
948             }
949
950             return ~position;
951         }
952
953         public int scan(String JavaDoc periodStr, final int position) {
954             String JavaDoc text1 = iPluralText;
955             String JavaDoc text2 = iSingularText;
956
957             if (text1.length() < text2.length()) {
958                 // Swap in order to match longer one first.
959
String JavaDoc temp = text1;
960                 text1 = text2;
961                 text2 = temp;
962             }
963
964             int textLength1 = text1.length();
965             int textLength2 = text2.length();
966
967             int sourceLength = periodStr.length();
968             for (int pos = position; pos < sourceLength; pos++) {
969                 if (periodStr.regionMatches(true, pos, text1, 0, textLength1)) {
970                     return pos;
971                 }
972                 if (periodStr.regionMatches(true, pos, text2, 0, textLength2)) {
973                     return pos;
974                 }
975             }
976             return ~position;
977         }
978     }
979
980     //-----------------------------------------------------------------------
981
/**
982      * Builds a composite affix by merging two other affix implementations.
983      */

984     static class CompositeAffix implements PeriodFieldAffix {
985         private final PeriodFieldAffix iLeft;
986         private final PeriodFieldAffix iRight;
987
988         CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) {
989             iLeft = left;
990             iRight = right;
991         }
992
993         public int calculatePrintedLength(int value) {
994             return iLeft.calculatePrintedLength(value)
995                 + iRight.calculatePrintedLength(value);
996         }
997
998         public void printTo(StringBuffer JavaDoc buf, int value) {
999             iLeft.printTo(buf, value);
1000            iRight.printTo(buf, value);
1001        }
1002
1003        public void printTo(Writer JavaDoc out, int value) throws IOException JavaDoc {
1004            iLeft.printTo(out, value);
1005            iRight.printTo(out, value);
1006        }
1007
1008        public int parse(String JavaDoc periodStr, int position) {
1009            position = iLeft.parse(periodStr, position);
1010            if (position >= 0) {
1011                position = iRight.parse(periodStr, position);
1012            }
1013            return position;
1014        }
1015
1016        public int scan(String JavaDoc periodStr, final int position) {
1017            int pos = iLeft.scan(periodStr, position);
1018            if (pos >= 0) {
1019                return iRight.scan(periodStr, pos);
1020            }
1021            return ~position;
1022        }
1023    }
1024
1025    //-----------------------------------------------------------------------
1026
/**
1027     * Formats the numeric value of a field, potentially with prefix/suffix.
1028     */

1029    static class FieldFormatter
1030            implements PeriodPrinter, PeriodParser {
1031        private final int iMinPrintedDigits;
1032        private final int iPrintZeroSetting;
1033        private final int iMaxParsedDigits;
1034        private final boolean iRejectSignedValues;
1035        
1036        /** The index of the field type, 0=year, etc. */
1037        private final int iFieldType;
1038        /**
1039         * The array of the latest formatter added for each type.
1040         * This is shared between all the field formatters in a formatter.
1041         */

1042        private final FieldFormatter[] iFieldFormatters;
1043        
1044        private final PeriodFieldAffix iPrefix;
1045        private final PeriodFieldAffix iSuffix;
1046
1047        FieldFormatter(int minPrintedDigits, int printZeroSetting,
1048                       int maxParsedDigits, boolean rejectSignedValues,
1049                       int fieldType, FieldFormatter[] fieldFormatters,
1050                       PeriodFieldAffix prefix, PeriodFieldAffix suffix) {
1051            iMinPrintedDigits = minPrintedDigits;
1052            iPrintZeroSetting = printZeroSetting;
1053            iMaxParsedDigits = maxParsedDigits;
1054            iRejectSignedValues = rejectSignedValues;
1055            iFieldType = fieldType;
1056            iFieldFormatters = fieldFormatters;
1057            iPrefix = prefix;
1058            iSuffix = suffix;
1059        }
1060
1061        FieldFormatter(FieldFormatter field, PeriodFieldAffix suffix) {
1062            iMinPrintedDigits = field.iMinPrintedDigits;
1063            iPrintZeroSetting = field.iPrintZeroSetting;
1064            iMaxParsedDigits = field.iMaxParsedDigits;
1065            iRejectSignedValues = field.iRejectSignedValues;
1066            iFieldType = field.iFieldType;
1067            iFieldFormatters = field.iFieldFormatters;
1068            iPrefix = field.iPrefix;
1069            if (field.iSuffix != null) {
1070                suffix = new CompositeAffix(field.iSuffix, suffix);
1071            }
1072            iSuffix = suffix;
1073        }
1074
1075        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale JavaDoc locale) {
1076            if (stopAt <= 0) {
1077                return 0;
1078            }
1079            if (iPrintZeroSetting == PRINT_ZERO_ALWAYS || getFieldValue(period) != Long.MAX_VALUE) {
1080                return 1;
1081            }
1082            return 0;
1083        }
1084
1085        public int calculatePrintedLength(ReadablePeriod period, Locale JavaDoc locale) {
1086            long valueLong = getFieldValue(period);
1087            if (valueLong == Long.MAX_VALUE) {
1088                return 0;
1089            }
1090
1091            int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), iMinPrintedDigits);
1092            if (iFieldType >= SECONDS_MILLIS) {
1093                sum++; // decimal point
1094
if (iFieldType == SECONDS_OPTIONAL_MILLIS &&
1095                    (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND) == 0) {
1096                    sum -= 4; // remove three digits and decimal point
1097
}
1098                valueLong = valueLong / DateTimeConstants.MILLIS_PER_SECOND;
1099            }
1100            int value = (int) valueLong;
1101
1102            if (iPrefix != null) {
1103                sum += iPrefix.calculatePrintedLength(value);
1104            }
1105            if (iSuffix != null) {
1106                sum += iSuffix.calculatePrintedLength(value);
1107            }
1108
1109            return sum;
1110        }
1111        
1112        public void printTo(StringBuffer JavaDoc buf, ReadablePeriod period, Locale JavaDoc locale) {
1113            long valueLong = getFieldValue(period);
1114            if (valueLong == Long.MAX_VALUE) {
1115                return;
1116            }
1117            int value = (int) valueLong;
1118            if (iFieldType >= SECONDS_MILLIS) {
1119                value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1120            }
1121
1122            if (iPrefix != null) {
1123                iPrefix.printTo(buf, value);
1124            }
1125            int minDigits = iMinPrintedDigits;
1126            if (minDigits <= 1) {
1127                FormatUtils.appendUnpaddedInteger(buf, value);
1128            } else {
1129                FormatUtils.appendPaddedInteger(buf, value, minDigits);
1130            }
1131            if (iFieldType >= SECONDS_MILLIS) {
1132                int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1133                if (iFieldType == SECONDS_MILLIS || dp > 0) {
1134                    buf.append('.');
1135                    FormatUtils.appendPaddedInteger(buf, dp, 3);
1136                }
1137            }
1138            if (iSuffix != null) {
1139                iSuffix.printTo(buf, value);
1140            }
1141        }
1142
1143        public void printTo(Writer JavaDoc out, ReadablePeriod period, Locale JavaDoc locale) throws IOException JavaDoc {
1144            long valueLong = getFieldValue(period);
1145            if (valueLong == Long.MAX_VALUE) {
1146                return;
1147            }
1148            int value = (int) valueLong;
1149            if (iFieldType >= SECONDS_MILLIS) {
1150                value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1151            }
1152
1153            if (iPrefix != null) {
1154                iPrefix.printTo(out, value);
1155            }
1156            int minDigits = iMinPrintedDigits;
1157            if (minDigits <= 1) {
1158                FormatUtils.writeUnpaddedInteger(out, value);
1159            } else {
1160                FormatUtils.writePaddedInteger(out, value, minDigits);
1161            }
1162            if (iFieldType >= SECONDS_MILLIS) {
1163                int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1164                if (iFieldType == SECONDS_MILLIS || dp > 0) {
1165                    out.write('.');
1166                    FormatUtils.writePaddedInteger(out, dp, 3);
1167                }
1168            }
1169            if (iSuffix != null) {
1170                iSuffix.printTo(out, value);
1171            }
1172        }
1173
1174        public int parseInto(
1175                ReadWritablePeriod period, String JavaDoc text,
1176                int position, Locale JavaDoc locale) {
1177
1178            boolean mustParse = (iPrintZeroSetting == PRINT_ZERO_ALWAYS);
1179
1180            // Shortcut test.
1181
if (position >= text.length()) {
1182                return mustParse ? ~position : position;
1183            }
1184
1185            if (iPrefix != null) {
1186                position = iPrefix.parse(text, position);
1187                if (position >= 0) {
1188                    // If prefix is found, then the parse must finish.
1189
mustParse = true;
1190                } else {
1191                    // Prefix not found, so bail.
1192
if (!mustParse) {
1193                        // It's okay because parsing of this field is not
1194
// required. Don't return an error. Fields down the
1195
// chain can continue on, trying to parse.
1196
return ~position;
1197                    }
1198                    return position;
1199                }
1200            }
1201
1202            int suffixPos = -1;
1203            if (iSuffix != null && !mustParse) {
1204                // Pre-scan the suffix, to help determine if this field must be
1205
// parsed.
1206
suffixPos = iSuffix.scan(text, position);
1207                if (suffixPos >= 0) {
1208                    // If suffix is found, then parse must finish.
1209
mustParse = true;
1210                } else {
1211                    // Suffix not found, so bail.
1212
if (!mustParse) {
1213                        // It's okay because parsing of this field is not
1214
// required. Don't return an error. Fields down the
1215
// chain can continue on, trying to parse.
1216
return ~suffixPos;
1217                    }
1218                    return suffixPos;
1219                }
1220            }
1221
1222            if (!mustParse && !isSupported(period.getPeriodType(), iFieldType)) {
1223                // If parsing is not required and the field is not supported,
1224
// exit gracefully so that another parser can continue on.
1225
return position;
1226            }
1227
1228            int limit;
1229            if (suffixPos > 0) {
1230                limit = Math.min(iMaxParsedDigits, suffixPos - position);
1231            } else {
1232                limit = Math.min(iMaxParsedDigits, text.length() - position);
1233            }
1234
1235            // validate input number
1236
int length = 0;
1237            int fractPos = -1;
1238            boolean hasDigits = false;
1239            while (length < limit) {
1240                char c = text.charAt(position + length);
1241                // leading sign
1242
if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) {
1243                    boolean negative = c == '-';
1244
1245                    // Next character must be a digit.
1246
if (length + 1 >= limit ||
1247                        (c = text.charAt(position + length + 1)) < '0' || c > '9')
1248                    {
1249                        break;
1250                    }
1251
1252                    if (negative) {
1253                        length++;
1254                    } else {
1255                        // Skip the '+' for parseInt to succeed.
1256
position++;
1257                    }
1258                    // Expand the limit to disregard the sign character.
1259
limit = Math.min(limit + 1, text.length() - position);
1260                    continue;
1261                }
1262                // main number
1263
if (c >= '0' && c <= '9') {
1264                    hasDigits = true;
1265                } else {
1266                    if ((c == '.' || c == ',')
1267                         && (iFieldType == SECONDS_MILLIS || iFieldType == SECONDS_OPTIONAL_MILLIS)) {
1268                        if (fractPos >= 0) {
1269                            // can't have two decimals
1270
break;
1271                        }
1272                        fractPos = position + length + 1;
1273                        // Expand the limit to disregard the decimal point.
1274
limit = Math.min(limit + 1, text.length() - position);
1275                    } else {
1276                        break;
1277                    }
1278                }
1279                length++;
1280            }
1281
1282            if (!hasDigits) {
1283                return ~position;
1284            }
1285
1286            if (suffixPos >= 0 && position + length != suffixPos) {
1287                // If there are additional non-digit characters before the
1288
// suffix is reached, then assume that the suffix found belongs
1289
// to a field not yet reached. Return original position so that
1290
// another parser can continue on.
1291
return position;
1292            }
1293
1294            if (iFieldType != SECONDS_MILLIS && iFieldType != SECONDS_OPTIONAL_MILLIS) {
1295                // Handle common case.
1296
setFieldValue(period, iFieldType, parseInt(text, position, length));
1297            } else if (fractPos < 0) {
1298                setFieldValue(period, SECONDS, parseInt(text, position, length));
1299                setFieldValue(period, MILLIS, 0);
1300            } else {
1301                int wholeValue = parseInt(text, position, fractPos - position - 1);
1302                setFieldValue(period, SECONDS, wholeValue);
1303
1304                int fractLen = position + length - fractPos;
1305                int fractValue;
1306                if (fractLen <= 0) {
1307                    fractValue = 0;
1308                } else {
1309                    if (fractLen >= 3) {
1310                        fractValue = parseInt(text, fractPos, 3);
1311                    } else {
1312                        fractValue = parseInt(text, fractPos, fractLen);
1313                        if (fractLen == 1) {
1314                            fractValue *= 100;
1315                        } else {
1316                            fractValue *= 10;
1317                        }
1318                    }
1319                    if (wholeValue < 0) {
1320                        fractValue = -fractValue;
1321                    }
1322                }
1323
1324                setFieldValue(period, MILLIS, fractValue);
1325            }
1326                
1327            position += length;
1328
1329            if (position >= 0 && iSuffix != null) {
1330                position = iSuffix.parse(text, position);
1331            }
1332                
1333            return position;
1334        }
1335
1336        /**
1337         * @param text text to parse
1338         * @param position position in text
1339         * @param length exact count of characters to parse
1340         * @return parsed int value
1341         */

1342        private int parseInt(String JavaDoc text, int position, int length) {
1343            if (length >= 10) {
1344                // Since value may exceed max, use stock parser which checks for this.
1345
return Integer.parseInt(text.substring(position, position + length));
1346            }
1347            if (length <= 0) {
1348                return 0;
1349            }
1350            int value = text.charAt(position++);
1351            length--;
1352            boolean negative;
1353            if (value == '-') {
1354                if (--length < 0) {
1355                    return 0;
1356                }
1357                negative = true;
1358                value = text.charAt(position++);
1359            } else {
1360                negative = false;
1361            }
1362            value -= '0';
1363            while (length-- > 0) {
1364                value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0';
1365            }
1366            return negative ? -value : value;
1367        }
1368
1369        /**
1370         * @return Long.MAX_VALUE if nothing to print, otherwise value
1371         */

1372        long getFieldValue(ReadablePeriod period) {
1373            PeriodType type;
1374            if (iPrintZeroSetting == PRINT_ZERO_ALWAYS) {
1375                type = null; // Don't need to check if supported.
1376
} else {
1377                type = period.getPeriodType();
1378            }
1379            if (type != null && isSupported(type, iFieldType) == false) {
1380                return Long.MAX_VALUE;
1381            }
1382
1383            long value;
1384
1385            switch (iFieldType) {
1386            default:
1387                return Long.MAX_VALUE;
1388            case YEARS:
1389                value = period.get(DurationFieldType.years());
1390                break;
1391            case MONTHS:
1392                value = period.get(DurationFieldType.months());
1393                break;
1394            case WEEKS:
1395                value = period.get(DurationFieldType.weeks());
1396                break;
1397            case DAYS:
1398                value = period.get(DurationFieldType.days());
1399                break;
1400            case HOURS:
1401                value = period.get(DurationFieldType.hours());
1402                break;
1403            case MINUTES:
1404                value = period.get(DurationFieldType.minutes());
1405                break;
1406            case SECONDS:
1407                value = period.get(DurationFieldType.seconds());
1408                break;
1409            case MILLIS:
1410                value = period.get(DurationFieldType.millis());
1411                break;
1412            case SECONDS_MILLIS: // drop through
1413
case SECONDS_OPTIONAL_MILLIS:
1414                int seconds = period.get(DurationFieldType.seconds());
1415                int millis = period.get(DurationFieldType.millis());
1416                value = (seconds * (long) DateTimeConstants.MILLIS_PER_SECOND) + millis;
1417                break;
1418            }
1419
1420            // determine if period is zero and this is the last field
1421
if (value == 0) {
1422                switch (iPrintZeroSetting) {
1423                case PRINT_ZERO_NEVER:
1424                    return Long.MAX_VALUE;
1425                case PRINT_ZERO_RARELY_LAST:
1426                    if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1427                        for (int i = iFieldType + 1; i < 10; i++) {
1428                            if (isSupported(type, i) && iFieldFormatters[i] != null) {
1429                                return Long.MAX_VALUE;
1430                            }
1431                        }
1432                    } else {
1433                        return Long.MAX_VALUE;
1434                    }
1435                    break;
1436                case PRINT_ZERO_RARELY_FIRST:
1437                    if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1438                        for (int i = Math.min(iFieldType, 8) - 1; i >= 0; i++) {
1439                            if (isSupported(type, i) && iFieldFormatters[i] != null) {
1440                                return Long.MAX_VALUE;
1441                            }
1442                        }
1443                    } else {
1444                        return Long.MAX_VALUE;
1445                    }
1446                    break;
1447                }
1448            }
1449
1450            return value;
1451        }
1452
1453        boolean isZero(ReadablePeriod period) {
1454            for (int i = 0, isize = period.size(); i < isize; i++) {
1455                if (period.getValue(i) != 0) {
1456                    return false;
1457                }
1458            }
1459            return true;
1460        }
1461
1462        boolean isSupported(PeriodType type, int field) {
1463            switch (field) {
1464            default:
1465                return false;
1466            case YEARS:
1467                return type.isSupported(DurationFieldType.years());
1468            case MONTHS:
1469                return type.isSupported(DurationFieldType.months());
1470            case WEEKS:
1471                return type.isSupported(DurationFieldType.weeks());
1472            case DAYS:
1473                return type.isSupported(DurationFieldType.days());
1474            case HOURS:
1475                return type.isSupported(DurationFieldType.hours());
1476            case MINUTES:
1477                return type.isSupported(DurationFieldType.minutes());
1478            case SECONDS:
1479                return type.isSupported(DurationFieldType.seconds());
1480            case MILLIS:
1481                return type.isSupported(DurationFieldType.millis());
1482            case SECONDS_MILLIS: // drop through
1483
case SECONDS_OPTIONAL_MILLIS:
1484                return type.isSupported(DurationFieldType.seconds()) ||
1485                       type.isSupported(DurationFieldType.millis());
1486            }
1487        }
1488
1489        void setFieldValue(ReadWritablePeriod period, int field, int value) {
1490            switch (field) {
1491            default:
1492                break;
1493            case YEARS:
1494                period.setYears(value);
1495                break;
1496            case MONTHS:
1497                period.setMonths(value);
1498                break;
1499            case WEEKS:
1500                period.setWeeks(value);
1501                break;
1502            case DAYS:
1503                period.setDays(value);
1504                break;
1505            case HOURS:
1506                period.setHours(value);
1507                break;
1508            case MINUTES:
1509                period.setMinutes(value);
1510                break;
1511            case SECONDS:
1512                period.setSeconds(value);
1513                break;
1514            case MILLIS:
1515                period.setMillis(value);
1516                break;
1517            }
1518        }
1519
1520        int getFieldType() {
1521            return iFieldType;
1522        }
1523    }
1524
1525    //-----------------------------------------------------------------------
1526
/**
1527     * Handles a simple literal piece of text.
1528     */

1529    static class Literal
1530            implements PeriodPrinter, PeriodParser {
1531        static final Literal EMPTY = new Literal("");
1532        private final String JavaDoc iText;
1533
1534        Literal(String JavaDoc text) {
1535            iText = text;
1536        }
1537
1538        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale JavaDoc locale) {
1539            return 0;
1540        }
1541
1542        public int calculatePrintedLength(ReadablePeriod period, Locale JavaDoc locale) {
1543            return iText.length();
1544        }
1545
1546        public void printTo(StringBuffer JavaDoc buf, ReadablePeriod period, Locale JavaDoc locale) {
1547            buf.append(iText);
1548        }
1549
1550        public void printTo(Writer JavaDoc out, ReadablePeriod period, Locale JavaDoc locale) throws IOException JavaDoc {
1551            out.write(iText);
1552        }
1553
1554        public int parseInto(
1555                ReadWritablePeriod period, String JavaDoc periodStr,
1556                int position, Locale JavaDoc locale) {
1557            if (periodStr.regionMatches(true, position, iText, 0, iText.length())) {
1558                return position + iText.length();
1559            }
1560            return ~position;
1561        }
1562    }
1563
1564    //-----------------------------------------------------------------------
1565
/**
1566     * Handles a separator, that splits the fields into multiple parts.
1567     * For example, the 'T' in the ISO8601 standard.
1568     */

1569    static class Separator
1570            implements PeriodPrinter, PeriodParser {
1571        private final String JavaDoc iText;
1572        private final String JavaDoc iFinalText;
1573        private final String JavaDoc[] iParsedForms;
1574
1575        private final boolean iUseBefore;
1576        private final boolean iUseAfter;
1577
1578        private PeriodPrinter iBeforePrinter;
1579        private PeriodPrinter iAfterPrinter;
1580        private PeriodParser iBeforeParser;
1581        private PeriodParser iAfterParser;
1582
1583        Separator(String JavaDoc text, String JavaDoc finalText, String JavaDoc[] variants,
1584                PeriodPrinter beforePrinter, PeriodParser beforeParser,
1585                boolean useBefore, boolean useAfter) {
1586            iText = text;
1587            iFinalText = finalText;
1588
1589            if ((finalText == null || text.equals(finalText)) &&
1590                (variants == null || variants.length == 0)) {
1591
1592                iParsedForms = new String JavaDoc[] {text};
1593            } else {
1594                // Filter and reverse sort the parsed forms.
1595
TreeSet JavaDoc parsedSet = new TreeSet JavaDoc(String.CASE_INSENSITIVE_ORDER);
1596                parsedSet.add(text);
1597                parsedSet.add(finalText);
1598                if (variants != null) {
1599                    for (int i=variants.length; --i>=0; ) {
1600                        parsedSet.add(variants[i]);
1601                    }
1602                }
1603                ArrayList JavaDoc parsedList = new ArrayList JavaDoc(parsedSet);
1604                Collections.reverse(parsedList);
1605                iParsedForms = (String JavaDoc[]) parsedList.toArray(new String JavaDoc[parsedList.size()]);
1606            }
1607
1608            iBeforePrinter = beforePrinter;
1609            iBeforeParser = beforeParser;
1610            iUseBefore = useBefore;
1611            iUseAfter = useAfter;
1612        }
1613
1614        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale JavaDoc locale) {
1615            int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale);
1616            if (sum < stopAt) {
1617                sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale);
1618            }
1619            return sum;
1620        }
1621
1622        public int calculatePrintedLength(ReadablePeriod period, Locale JavaDoc locale) {
1623            PeriodPrinter before = iBeforePrinter;
1624            PeriodPrinter after = iAfterPrinter;
1625            
1626            int sum = before.calculatePrintedLength(period, locale)
1627                    + after.calculatePrintedLength(period, locale);
1628            
1629            if (iUseBefore) {
1630                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1631                    if (iUseAfter) {
1632                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1633                        if (afterCount > 0) {
1634                            sum += (afterCount > 1 ? iText : iFinalText).length();
1635                        }
1636                    } else {
1637                        sum += iText.length();
1638                    }
1639                }
1640            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1641                sum += iText.length();
1642            }
1643            
1644            return sum;
1645        }
1646
1647        public void printTo(StringBuffer JavaDoc buf, ReadablePeriod period, Locale JavaDoc locale) {
1648            PeriodPrinter before = iBeforePrinter;
1649            PeriodPrinter after = iAfterPrinter;
1650            
1651            before.printTo(buf, period, locale);
1652            if (iUseBefore) {
1653                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1654                    if (iUseAfter) {
1655                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1656                        if (afterCount > 0) {
1657                            buf.append(afterCount > 1 ? iText : iFinalText);
1658                        }
1659                    } else {
1660                        buf.append(iText);
1661                    }
1662                }
1663            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1664                buf.append(iText);
1665            }
1666            after.printTo(buf, period, locale);
1667        }
1668
1669        public void printTo(Writer JavaDoc out, ReadablePeriod period, Locale JavaDoc locale) throws IOException JavaDoc {
1670            PeriodPrinter before = iBeforePrinter;
1671            PeriodPrinter after = iAfterPrinter;
1672            
1673            before.printTo(out, period, locale);
1674            if (iUseBefore) {
1675                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1676                    if (iUseAfter) {
1677                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1678                        if (afterCount > 0) {
1679                            out.write(afterCount > 1 ? iText : iFinalText);
1680                        }
1681                    } else {
1682                        out.write(iText);
1683                    }
1684                }
1685            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1686                out.write(iText);
1687            }
1688            after.printTo(out, period, locale);
1689        }
1690
1691        public int parseInto(
1692                ReadWritablePeriod period, String JavaDoc periodStr,
1693                int position, Locale JavaDoc locale) {
1694            int oldPos = position;
1695            position = iBeforeParser.parseInto(period, periodStr, position, locale);
1696
1697            if (position < 0) {
1698                return position;
1699            }
1700
1701            boolean found = false;
1702            if (position > oldPos) {
1703                // Consume this separator.
1704
String JavaDoc[] parsedForms = iParsedForms;
1705                int length = parsedForms.length;
1706                for (int i=0; i < length; i++) {
1707                    String JavaDoc parsedForm = parsedForms[i];
1708                    if ((parsedForm == null || parsedForm.length() == 0) ||
1709                        periodStr.regionMatches
1710                        (true, position, parsedForm, 0, parsedForm.length())) {
1711                        
1712                        position += parsedForm.length();
1713                        found = true;
1714                        break;
1715                    }
1716                }
1717            }
1718
1719            oldPos = position;
1720            position = iAfterParser.parseInto(period, periodStr, position, locale);
1721
1722            if (position < 0) {
1723                return position;
1724            }
1725
1726            if (found && position == oldPos) {
1727                // Separator should not have been supplied.
1728
return ~oldPos;
1729            }
1730
1731            if (position > oldPos && !found && !iUseBefore) {
1732                // Separator was required.
1733
return ~oldPos;
1734            }
1735
1736            return position;
1737        }
1738
1739        Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) {
1740            iAfterPrinter = afterPrinter;
1741            iAfterParser = afterParser;
1742            return this;
1743        }
1744    }
1745
1746    //-----------------------------------------------------------------------
1747
/**
1748     * Composite implementation that merges other fields to create a full pattern.
1749     */

1750    static class Composite
1751            implements PeriodPrinter, PeriodParser {
1752        
1753        private final PeriodPrinter[] iPrinters;
1754        private final PeriodParser[] iParsers;
1755
1756        Composite(List JavaDoc elementPairs) {
1757            List JavaDoc printerList = new ArrayList JavaDoc();
1758            List JavaDoc parserList = new ArrayList JavaDoc();
1759
1760            decompose(elementPairs, printerList, parserList);
1761
1762            if (printerList.size() <= 0) {
1763                iPrinters = null;
1764            } else {
1765                iPrinters = (PeriodPrinter[]) printerList.toArray(
1766                        new PeriodPrinter[printerList.size()]);
1767            }
1768
1769            if (parserList.size() <= 0) {
1770                iParsers = null;
1771            } else {
1772                iParsers = (PeriodParser[]) parserList.toArray(
1773                        new PeriodParser[parserList.size()]);
1774            }
1775        }
1776
1777        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale JavaDoc locale) {
1778            int sum = 0;
1779            PeriodPrinter[] printers = iPrinters;
1780            for (int i=printers.length; sum < stopAt && --i>=0; ) {
1781                sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale);
1782            }
1783            return sum;
1784        }
1785
1786        public int calculatePrintedLength(ReadablePeriod period, Locale JavaDoc locale) {
1787            int sum = 0;
1788            PeriodPrinter[] printers = iPrinters;
1789            for (int i=printers.length; --i>=0; ) {
1790                sum += printers[i].calculatePrintedLength(period, locale);
1791            }
1792            return sum;
1793        }
1794
1795        public void printTo(StringBuffer JavaDoc buf, ReadablePeriod period, Locale JavaDoc locale) {
1796            PeriodPrinter[] printers = iPrinters;
1797            int len = printers.length;
1798            for (int i=0; i<len; i++) {
1799                printers[i].printTo(buf, period, locale);
1800            }
1801        }
1802
1803        public void printTo(Writer JavaDoc out, ReadablePeriod period, Locale JavaDoc locale) throws IOException JavaDoc {
1804            PeriodPrinter[] printers = iPrinters;
1805            int len = printers.length;
1806            for (int i=0; i<len; i++) {
1807                printers[i].printTo(out, period, locale);
1808            }
1809        }
1810
1811        public int parseInto(
1812                ReadWritablePeriod period, String JavaDoc periodStr,
1813                int position, Locale JavaDoc locale) {
1814            PeriodParser[] parsers = iParsers;
1815            if (parsers == null) {
1816                throw new UnsupportedOperationException JavaDoc();
1817            }
1818
1819            int len = parsers.length;
1820            for (int i=0; i<len && position >= 0; i++) {
1821                position = parsers[i].parseInto(period, periodStr, position, locale);
1822            }
1823            return position;
1824        }
1825
1826        private void decompose(List JavaDoc elementPairs, List JavaDoc printerList, List JavaDoc parserList) {
1827            int size = elementPairs.size();
1828            for (int i=0; i<size; i+=2) {
1829                Object JavaDoc element = elementPairs.get(i);
1830                if (element instanceof PeriodPrinter) {
1831                    if (element instanceof Composite) {
1832                        addArrayToList(printerList, ((Composite) element).iPrinters);
1833                    } else {
1834                        printerList.add(element);
1835                    }
1836                }
1837
1838                element = elementPairs.get(i + 1);
1839                if (element instanceof PeriodParser) {
1840                    if (element instanceof Composite) {
1841                        addArrayToList(parserList, ((Composite) element).iParsers);
1842                    } else {
1843                        parserList.add(element);
1844                    }
1845                }
1846            }
1847        }
1848
1849        private void addArrayToList(List JavaDoc list, Object JavaDoc[] array) {
1850            if (array != null) {
1851                for (int i=0; i<array.length; i++) {
1852                    list.add(array[i]);
1853                }
1854            }
1855        }
1856    }
1857
1858}
1859
Popular Tags