EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.format]

COVERAGE SUMMARY FOR SOURCE FILE [PeriodFormatterBuilder.java]

nameclass, %method, %block, %line, %
PeriodFormatterBuilder.java88%  (7/8)91%  (89/98)87%  (2404/2750)87%  (568.2/655)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class PeriodFormatterBuilder$CompositeAffix0%   (0/1)0%   (0/6)0%   (0/75)0%   (0/19)
PeriodFormatterBuilder$CompositeAffix (PeriodFormatterBuilder$PeriodFieldAffi... 0%   (0/1)0%   (0/9)0%   (0/4)
calculatePrintedLength (int): int 0%   (0/1)0%   (0/10)0%   (0/1)
parse (String, int): int 0%   (0/1)0%   (0/16)0%   (0/4)
printTo (StringBuffer, int): void 0%   (0/1)0%   (0/11)0%   (0/3)
printTo (Writer, int): void 0%   (0/1)0%   (0/11)0%   (0/3)
scan (String, int): int 0%   (0/1)0%   (0/18)0%   (0/4)
     
class PeriodFormatterBuilder$PluralAffix100% (1/1)83%  (5/6)71%  (105/148)66%  (23/35)
printTo (Writer, int): void 0%   (0/1)0%   (0/11)0%   (0/2)
parse (String, int): int 100% (1/1)51%  (25/49)45%  (5/11)
scan (String, int): int 100% (1/1)86%  (49/57)73%  (11/15)
PeriodFormatterBuilder$PluralAffix (String, String): void 100% (1/1)100% (9/9)100% (4/4)
calculatePrintedLength (int): int 100% (1/1)100% (10/10)100% (1/1)
printTo (StringBuffer, int): void 100% (1/1)100% (12/12)100% (2/2)
     
class PeriodFormatterBuilder100% (1/1)96%  (43/45)89%  (664/746)91%  (157/173)
maximumParsedDigits (int): PeriodFormatterBuilder 0%   (0/1)0%   (0/5)0%   (0/2)
rejectSignedValues (boolean): PeriodFormatterBuilder 0%   (0/1)0%   (0/5)0%   (0/2)
appendPrefix (PeriodFormatterBuilder$PeriodFieldAffix): PeriodFormatterBuilder 100% (1/1)48%  (10/21)67%  (4/6)
clearPrefix (): void 100% (1/1)58%  (7/12)75%  (3/4)
createComposite (List): Object [] 100% (1/1)67%  (30/45)80%  (4/5)
appendSeparator (String, String, String [], boolean, boolean): PeriodFormatte... 100% (1/1)72%  (78/109)75%  (18/24)
append (PeriodFormatter): PeriodFormatterBuilder 100% (1/1)72%  (13/18)80%  (4/5)
appendLiteral (String): PeriodFormatterBuilder 100% (1/1)76%  (16/21)83%  (5/6)
PeriodFormatterBuilder (): void 100% (1/1)100% (5/5)100% (3/3)
append (PeriodPrinter, PeriodParser): PeriodFormatterBuilder 100% (1/1)100% (18/18)100% (5/5)
append0 (PeriodPrinter, PeriodParser): PeriodFormatterBuilder 100% (1/1)100% (32/32)100% (5/5)
appendDays (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendField (int): void 100% (1/1)100% (6/6)100% (2/2)
appendField (int, int): void 100% (1/1)100% (31/31)100% (5/5)
appendHours (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendMillis (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendMillis3Digit (): PeriodFormatterBuilder 100% (1/1)100% (6/6)100% (2/2)
appendMinutes (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendMonths (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendPrefix (String): PeriodFormatterBuilder 100% (1/1)100% (13/13)100% (3/3)
appendPrefix (String, String): PeriodFormatterBuilder 100% (1/1)100% (16/16)100% (3/3)
appendSeconds (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendSecondsWithMillis (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendSecondsWithOptionalMillis (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendSeparator (String): PeriodFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendSeparator (String, String): PeriodFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendSeparator (String, String, String []): PeriodFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendSeparatorIfFieldsAfter (String): PeriodFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendSeparatorIfFieldsBefore (String): PeriodFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendSuffix (PeriodFormatterBuilder$PeriodFieldAffix): PeriodFormatterBuilder 100% (1/1)100% (79/79)100% (13/13)
appendSuffix (String): PeriodFormatterBuilder 100% (1/1)100% (13/13)100% (3/3)
appendSuffix (String, String): PeriodFormatterBuilder 100% (1/1)100% (16/16)100% (3/3)
appendWeeks (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
appendYears (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
clear (): void 100% (1/1)100% (38/38)100% (12/12)
minimumPrintedDigits (int): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
printZeroAlways (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
printZeroIfSupported (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
printZeroNever (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
printZeroRarelyFirst (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
printZeroRarelyLast (): PeriodFormatterBuilder 100% (1/1)100% (5/5)100% (2/2)
toFormatter (): PeriodFormatter 100% (1/1)100% (16/16)100% (3/3)
toFormatter (List, boolean, boolean): PeriodFormatter 100% (1/1)100% (83/83)100% (14/14)
toParser (): PeriodParser 100% (1/1)100% (9/9)100% (3/3)
toPrinter (): PeriodPrinter 100% (1/1)100% (9/9)100% (3/3)
     
class PeriodFormatterBuilder$Separator100% (1/1)100% (7/7)90%  (376/416)92%  (82.8/90)
printTo (Writer, ReadablePeriod, Locale): void 100% (1/1)50%  (33/66)57%  (8/14)
parseInto (ReadWritablePeriod, String, int, Locale): int 100% (1/1)96%  (91/95)96%  (22/23)
PeriodFormatterBuilder$Separator (String, String, String [], PeriodPrinter, P... 100% (1/1)96%  (81/84)99%  (18.8/19)
calculatePrintedLength (ReadablePeriod, Locale): int 100% (1/1)100% (73/73)100% (13/13)
countFieldsToPrint (ReadablePeriod, int, Locale): int 100% (1/1)100% (21/21)100% (4/4)
finish (PeriodPrinter, PeriodParser): PeriodFormatterBuilder$Separator 100% (1/1)100% (8/8)100% (3/3)
printTo (StringBuffer, ReadablePeriod, Locale): void 100% (1/1)100% (69/69)100% (14/14)
     
class PeriodFormatterBuilder$FieldFormatter100% (1/1)100% (13/13)91%  (904/994)88%  (218.4/247)
PeriodFormatterBuilder$FieldFormatter (PeriodFormatterBuilder$FieldFormatter,... 100% (1/1)84%  (37/44)92%  (11/12)
parseInto (ReadWritablePeriod, String, int, Locale): int 100% (1/1)84%  (286/340)76%  (54.4/72)
parseInt (String, int, int): int 100% (1/1)86%  (59/69)88%  (14/16)
printTo (Writer, ReadablePeriod, Locale): void 100% (1/1)87%  (65/75)85%  (17/20)
countFieldsToPrint (ReadablePeriod, int, Locale): int 100% (1/1)89%  (16/18)80%  (4/5)
isSupported (PeriodType, int): boolean 100% (1/1)96%  (46/48)91%  (10/11)
setFieldValue (ReadWritablePeriod, int, int): void 100% (1/1)97%  (34/35)94%  (17/18)
getFieldValue (ReadablePeriod): long 100% (1/1)98%  (171/175)95%  (40/42)
PeriodFormatterBuilder$FieldFormatter (int, int, int, boolean, int, PeriodFor... 100% (1/1)100% (27/27)100% (10/10)
calculatePrintedLength (ReadablePeriod, Locale): int 100% (1/1)100% (66/66)100% (16/16)
getFieldType (): int 100% (1/1)100% (3/3)100% (1/1)
isZero (ReadablePeriod): boolean 100% (1/1)100% (18/18)100% (4/4)
printTo (StringBuffer, ReadablePeriod, Locale): void 100% (1/1)100% (76/76)100% (20/20)
     
class PeriodFormatterBuilder$SimpleAffix100% (1/1)100% (6/6)95%  (75/79)96%  (22/23)
parse (String, int): int 100% (1/1)82%  (18/22)80%  (4/5)
PeriodFormatterBuilder$SimpleAffix (String): void 100% (1/1)100% (6/6)100% (3/3)
calculatePrintedLength (int): int 100% (1/1)100% (4/4)100% (1/1)
printTo (StringBuffer, int): void 100% (1/1)100% (6/6)100% (2/2)
printTo (Writer, int): void 100% (1/1)100% (5/5)100% (2/2)
scan (String, int): int 100% (1/1)100% (36/36)100% (10/10)
     
class PeriodFormatterBuilder$Composite100% (1/1)100% (8/8)95%  (230/242)95%  (52/55)
PeriodFormatterBuilder$Composite (List): void 100% (1/1)83%  (38/46)82%  (9/11)
parseInto (ReadWritablePeriod, String, int, Locale): int 100% (1/1)88%  (28/32)86%  (6/7)
addArrayToList (List, Object []): void 100% (1/1)100% (17/17)100% (4/4)
calculatePrintedLength (ReadablePeriod, Locale): int 100% (1/1)100% (23/23)100% (5/5)
countFieldsToPrint (ReadablePeriod, int, Locale): int 100% (1/1)100% (27/27)100% (5/5)
decompose (List, List, List): void 100% (1/1)100% (55/55)100% (13/13)
printTo (StringBuffer, ReadablePeriod, Locale): void 100% (1/1)100% (21/21)100% (5/5)
printTo (Writer, ReadablePeriod, Locale): void 100% (1/1)100% (21/21)100% (5/5)
     
class PeriodFormatterBuilder$Literal100% (1/1)100% (7/7)100% (50/50)100% (13/13)
<static initializer> 100% (1/1)100% (6/6)100% (1/1)
PeriodFormatterBuilder$Literal (String): void 100% (1/1)100% (6/6)100% (3/3)
calculatePrintedLength (ReadablePeriod, Locale): int 100% (1/1)100% (4/4)100% (1/1)
countFieldsToPrint (ReadablePeriod, int, Locale): int 100% (1/1)100% (2/2)100% (1/1)
parseInto (ReadWritablePeriod, String, int, Locale): int 100% (1/1)100% (21/21)100% (3/3)
printTo (StringBuffer, ReadablePeriod, Locale): void 100% (1/1)100% (6/6)100% (2/2)
printTo (Writer, ReadablePeriod, Locale): void 100% (1/1)100% (5/5)100% (2/2)

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 */
16package org.joda.time.format;
17 
18import java.io.IOException;
19import java.io.Writer;
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.List;
23import java.util.Locale;
24import java.util.TreeSet;
25 
26import org.joda.time.DateTimeConstants;
27import org.joda.time.DurationFieldType;
28import org.joda.time.PeriodType;
29import org.joda.time.ReadWritablePeriod;
30import 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 */
65public 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 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();
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 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 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            PeriodFormatter f = toFormatter(elementPairs.subList(2, size), notPrinter, notParser);
802            sep = sep.finish(f.getPrinter(), f.getParser());
803            return new PeriodFormatter(sep, sep);
804        }
805        Object[] comp = createComposite(elementPairs);
806        if (notPrinter) {
807            return new PeriodFormatter(null, (PeriodParser) comp[1]);
808        } else if (notParser) {
809            return new PeriodFormatter((PeriodPrinter) comp[0], null);
810        } else {
811            return new PeriodFormatter((PeriodPrinter) comp[0], (PeriodParser) comp[1]);
812        }
813    }
814 
815    private static Object[] createComposite(List elementPairs) {
816        switch (elementPairs.size()) {
817            case 0:
818                return new Object[] {Literal.EMPTY, Literal.EMPTY};
819            case 1:
820                return new Object[] {elementPairs.get(0), elementPairs.get(1)};
821            default:
822                Composite comp = new Composite(elementPairs);
823                return new Object[] {comp, comp};
824        }
825    }
826 
827    //-----------------------------------------------------------------------
828    /**
829     * Defines a formatted field's prefix or suffix text.
830     * This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
831     */
832    static interface PeriodFieldAffix {
833        int calculatePrintedLength(int value);
834        
835        void printTo(StringBuffer buf, int value);
836        
837        void printTo(Writer out, int value) throws IOException;
838        
839        /**
840         * @return new position after parsing affix, or ~position of failure
841         */
842        int parse(String periodStr, int position);
843 
844        /**
845         * @return position where affix starts, or original ~position if not found
846         */
847        int scan(String periodStr, int position);
848    }
849 
850    //-----------------------------------------------------------------------
851    /**
852     * Implements an affix where the text does not vary by the amount.
853     */
854    static class SimpleAffix implements PeriodFieldAffix {
855        private final String iText;
856 
857        SimpleAffix(String text) {
858            iText = text;
859        }
860 
861        public int calculatePrintedLength(int value) {
862            return iText.length();
863        }
864 
865        public void printTo(StringBuffer buf, int value) {
866            buf.append(iText);
867        }
868 
869        public void printTo(Writer out, int value) throws IOException {
870            out.write(iText);
871        }
872 
873        public int parse(String periodStr, int position) {
874            String text = iText;
875            int textLength = text.length();
876            if (periodStr.regionMatches(true, position, text, 0, textLength)) {
877                return position + textLength;
878            }
879            return ~position;
880        }
881 
882        public int scan(String periodStr, final int position) {
883            String text = iText;
884            int textLength = text.length();
885            int sourceLength = periodStr.length();
886            search:
887            for (int pos = position; pos < sourceLength; pos++) {
888                if (periodStr.regionMatches(true, pos, text, 0, textLength)) {
889                    return pos;
890                }
891                // Only allow number characters to be skipped in search of suffix.
892                switch (periodStr.charAt(pos)) {
893                case '0': case '1': case '2': case '3': case '4':
894                case '5': case '6': case '7': case '8': case '9':
895                case '.': case ',': case '+': case '-':
896                    break;
897                default:
898                    break search;
899                }
900            }
901            return ~position;
902        }
903    }
904 
905    //-----------------------------------------------------------------------
906    /**
907     * Implements an affix where the text varies by the amount of the field.
908     * Only singular (1) and plural (not 1) are supported.
909     */
910    static class PluralAffix implements PeriodFieldAffix {
911        private final String iSingularText;
912        private final String iPluralText;
913 
914        PluralAffix(String singularText, String pluralText) {
915            iSingularText = singularText;
916            iPluralText = pluralText;
917        }
918 
919        public int calculatePrintedLength(int value) {
920            return (value == 1 ? iSingularText : iPluralText).length();
921        }
922 
923        public void printTo(StringBuffer buf, int value) {
924            buf.append(value == 1 ? iSingularText : iPluralText);
925        }
926 
927        public void printTo(Writer out, int value) throws IOException {
928            out.write(value == 1 ? iSingularText : iPluralText);
929        }
930 
931        public int parse(String periodStr, int position) {
932            String text1 = iPluralText;
933            String text2 = iSingularText; 
934 
935            if (text1.length() < text2.length()) {
936                // Swap in order to match longer one first.
937                String temp = text1;
938                text1 = text2;
939                text2 = temp;
940            }
941 
942            if (periodStr.regionMatches
943                (true, position, text1, 0, text1.length())) {
944                return position + text1.length();
945            }
946            if (periodStr.regionMatches
947                (true, position, text2, 0, text2.length())) {
948                return position + text2.length();
949            }
950 
951            return ~position;
952        }
953 
954        public int scan(String periodStr, final int position) {
955            String text1 = iPluralText;
956            String text2 = iSingularText; 
957 
958            if (text1.length() < text2.length()) {
959                // Swap in order to match longer one first.
960                String temp = text1;
961                text1 = text2;
962                text2 = temp;
963            }
964 
965            int textLength1 = text1.length();
966            int textLength2 = text2.length();
967 
968            int sourceLength = periodStr.length();
969            for (int pos = position; pos < sourceLength; pos++) {
970                if (periodStr.regionMatches(true, pos, text1, 0, textLength1)) {
971                    return pos;
972                }
973                if (periodStr.regionMatches(true, pos, text2, 0, textLength2)) {
974                    return pos;
975                }
976            }
977            return ~position;
978        }
979    }
980 
981    //-----------------------------------------------------------------------
982    /**
983     * Builds a composite affix by merging two other affix implementations.
984     */
985    static class CompositeAffix implements PeriodFieldAffix {
986        private final PeriodFieldAffix iLeft;
987        private final PeriodFieldAffix iRight;
988 
989        CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) {
990            iLeft = left;
991            iRight = right;
992        }
993 
994        public int calculatePrintedLength(int value) {
995            return iLeft.calculatePrintedLength(value)
996                + iRight.calculatePrintedLength(value);
997        }
998 
999        public void printTo(StringBuffer buf, int value) {
1000            iLeft.printTo(buf, value);
1001            iRight.printTo(buf, value);
1002        }
1003 
1004        public void printTo(Writer out, int value) throws IOException {
1005            iLeft.printTo(out, value);
1006            iRight.printTo(out, value);
1007        }
1008 
1009        public int parse(String periodStr, int position) {
1010            position = iLeft.parse(periodStr, position);
1011            if (position >= 0) {
1012                position = iRight.parse(periodStr, position);
1013            }
1014            return position;
1015        }
1016 
1017        public int scan(String periodStr, final int position) {
1018            int pos = iLeft.scan(periodStr, position);
1019            if (pos >= 0) {
1020                return iRight.scan(periodStr, pos);
1021            }
1022            return ~position;
1023        }
1024    }
1025 
1026    //-----------------------------------------------------------------------
1027    /**
1028     * Formats the numeric value of a field, potentially with prefix/suffix.
1029     */
1030    static class FieldFormatter
1031            implements PeriodPrinter, PeriodParser {
1032        private final int iMinPrintedDigits;
1033        private final int iPrintZeroSetting;
1034        private final int iMaxParsedDigits;
1035        private final boolean iRejectSignedValues;
1036        
1037        /** The index of the field type, 0=year, etc. */
1038        private final int iFieldType;
1039        /**
1040         * The array of the latest formatter added for each type.
1041         * This is shared between all the field formatters in a formatter.
1042         */
1043        private final FieldFormatter[] iFieldFormatters;
1044        
1045        private final PeriodFieldAffix iPrefix;
1046        private final PeriodFieldAffix iSuffix;
1047 
1048        FieldFormatter(int minPrintedDigits, int printZeroSetting,
1049                       int maxParsedDigits, boolean rejectSignedValues,
1050                       int fieldType, FieldFormatter[] fieldFormatters,
1051                       PeriodFieldAffix prefix, PeriodFieldAffix suffix) {
1052            iMinPrintedDigits = minPrintedDigits;
1053            iPrintZeroSetting = printZeroSetting;
1054            iMaxParsedDigits = maxParsedDigits;
1055            iRejectSignedValues = rejectSignedValues;
1056            iFieldType = fieldType;
1057            iFieldFormatters = fieldFormatters;
1058            iPrefix = prefix;
1059            iSuffix = suffix;
1060        }
1061 
1062        FieldFormatter(FieldFormatter field, PeriodFieldAffix suffix) {
1063            iMinPrintedDigits = field.iMinPrintedDigits;
1064            iPrintZeroSetting = field.iPrintZeroSetting;
1065            iMaxParsedDigits = field.iMaxParsedDigits;
1066            iRejectSignedValues = field.iRejectSignedValues;
1067            iFieldType = field.iFieldType;
1068            iFieldFormatters = field.iFieldFormatters;
1069            iPrefix = field.iPrefix;
1070            if (field.iSuffix != null) {
1071                suffix = new CompositeAffix(field.iSuffix, suffix);
1072            }
1073            iSuffix = suffix;
1074        }
1075 
1076        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1077            if (stopAt <= 0) {
1078                return 0;
1079            }
1080            if (iPrintZeroSetting == PRINT_ZERO_ALWAYS || getFieldValue(period) != Long.MAX_VALUE) {
1081                return 1;
1082            }
1083            return 0;
1084        }
1085 
1086        public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1087            long valueLong = getFieldValue(period);
1088            if (valueLong == Long.MAX_VALUE) {
1089                return 0;
1090            }
1091 
1092            int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), iMinPrintedDigits);
1093            if (iFieldType >= SECONDS_MILLIS) {
1094                // valueLong contains the seconds and millis fields
1095                // the minimum output is 0.000, which is 4 digits
1096                sum = Math.max(sum, 4);
1097                // plus one for the decimal point
1098                sum++;
1099                if (iFieldType == SECONDS_OPTIONAL_MILLIS &&
1100                        (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND) == 0) {
1101                    sum -= 4; // remove three digits and decimal point
1102                }
1103                // reset valueLong to refer to the seconds part for the prefic/suffix calculation
1104                valueLong = valueLong / DateTimeConstants.MILLIS_PER_SECOND;
1105            }
1106            int value = (int) valueLong;
1107 
1108            if (iPrefix != null) {
1109                sum += iPrefix.calculatePrintedLength(value);
1110            }
1111            if (iSuffix != null) {
1112                sum += iSuffix.calculatePrintedLength(value);
1113            }
1114 
1115            return sum;
1116        }
1117        
1118        public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1119            long valueLong = getFieldValue(period);
1120            if (valueLong == Long.MAX_VALUE) {
1121                return;
1122            }
1123            int value = (int) valueLong;
1124            if (iFieldType >= SECONDS_MILLIS) {
1125                value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1126            }
1127 
1128            if (iPrefix != null) {
1129                iPrefix.printTo(buf, value);
1130            }
1131            int minDigits = iMinPrintedDigits;
1132            if (minDigits <= 1) {
1133                FormatUtils.appendUnpaddedInteger(buf, value);
1134            } else {
1135                FormatUtils.appendPaddedInteger(buf, value, minDigits);
1136            }
1137            if (iFieldType >= SECONDS_MILLIS) {
1138                int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1139                if (iFieldType == SECONDS_MILLIS || dp > 0) {
1140                    buf.append('.');
1141                    FormatUtils.appendPaddedInteger(buf, dp, 3);
1142                }
1143            }
1144            if (iSuffix != null) {
1145                iSuffix.printTo(buf, value);
1146            }
1147        }
1148 
1149        public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1150            long valueLong = getFieldValue(period);
1151            if (valueLong == Long.MAX_VALUE) {
1152                return;
1153            }
1154            int value = (int) valueLong;
1155            if (iFieldType >= SECONDS_MILLIS) {
1156                value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1157            }
1158 
1159            if (iPrefix != null) {
1160                iPrefix.printTo(out, value);
1161            }
1162            int minDigits = iMinPrintedDigits;
1163            if (minDigits <= 1) {
1164                FormatUtils.writeUnpaddedInteger(out, value);
1165            } else {
1166                FormatUtils.writePaddedInteger(out, value, minDigits);
1167            }
1168            if (iFieldType >= SECONDS_MILLIS) {
1169                int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1170                if (iFieldType == SECONDS_MILLIS || dp > 0) {
1171                    out.write('.');
1172                    FormatUtils.writePaddedInteger(out, dp, 3);
1173                }
1174            }
1175            if (iSuffix != null) {
1176                iSuffix.printTo(out, value);
1177            }
1178        }
1179 
1180        public int parseInto(
1181                ReadWritablePeriod period, String text, 
1182                int position, Locale locale) {
1183 
1184            boolean mustParse = (iPrintZeroSetting == PRINT_ZERO_ALWAYS);
1185 
1186            // Shortcut test.
1187            if (position >= text.length()) {
1188                return mustParse ? ~position : position;
1189            }
1190 
1191            if (iPrefix != null) {
1192                position = iPrefix.parse(text, position);
1193                if (position >= 0) {
1194                    // If prefix is found, then the parse must finish.
1195                    mustParse = true;
1196                } else {
1197                    // Prefix not found, so bail.
1198                    if (!mustParse) {
1199                        // It's okay because parsing of this field is not
1200                        // required. Don't return an error. Fields down the
1201                        // chain can continue on, trying to parse.
1202                        return ~position;
1203                    }
1204                    return position;
1205                }
1206            }
1207 
1208            int suffixPos = -1;
1209            if (iSuffix != null && !mustParse) {
1210                // Pre-scan the suffix, to help determine if this field must be
1211                // parsed.
1212                suffixPos = iSuffix.scan(text, position);
1213                if (suffixPos >= 0) {
1214                    // If suffix is found, then parse must finish.
1215                    mustParse = true;
1216                } else {
1217                    // Suffix not found, so bail.
1218                    if (!mustParse) {
1219                        // It's okay because parsing of this field is not
1220                        // required. Don't return an error. Fields down the
1221                        // chain can continue on, trying to parse.
1222                        return ~suffixPos;
1223                    }
1224                    return suffixPos;
1225                }
1226            }
1227 
1228            if (!mustParse && !isSupported(period.getPeriodType(), iFieldType)) {
1229                // If parsing is not required and the field is not supported,
1230                // exit gracefully so that another parser can continue on.
1231                return position;
1232            }
1233 
1234            int limit;
1235            if (suffixPos > 0) {
1236                limit = Math.min(iMaxParsedDigits, suffixPos - position);
1237            } else {
1238                limit = Math.min(iMaxParsedDigits, text.length() - position);
1239            }
1240 
1241            // validate input number
1242            int length = 0;
1243            int fractPos = -1;
1244            boolean hasDigits = false;
1245            while (length < limit) {
1246                char c = text.charAt(position + length);
1247                // leading sign
1248                if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) {
1249                    boolean negative = c == '-';
1250 
1251                    // Next character must be a digit.
1252                    if (length + 1 >= limit || 
1253                        (c = text.charAt(position + length + 1)) < '0' || c > '9')
1254                    {
1255                        break;
1256                    }
1257 
1258                    if (negative) {
1259                        length++;
1260                    } else {
1261                        // Skip the '+' for parseInt to succeed.
1262                        position++;
1263                    }
1264                    // Expand the limit to disregard the sign character.
1265                    limit = Math.min(limit + 1, text.length() - position);
1266                    continue;
1267                }
1268                // main number
1269                if (c >= '0' && c <= '9') {
1270                    hasDigits = true;
1271                } else {
1272                    if ((c == '.' || c == ',')
1273                         && (iFieldType == SECONDS_MILLIS || iFieldType == SECONDS_OPTIONAL_MILLIS)) {
1274                        if (fractPos >= 0) {
1275                            // can't have two decimals
1276                            break;
1277                        }
1278                        fractPos = position + length + 1;
1279                        // Expand the limit to disregard the decimal point.
1280                        limit = Math.min(limit + 1, text.length() - position);
1281                    } else {
1282                        break;
1283                    }
1284                }
1285                length++;
1286            }
1287 
1288            if (!hasDigits) {
1289                return ~position;
1290            }
1291 
1292            if (suffixPos >= 0 && position + length != suffixPos) {
1293                // If there are additional non-digit characters before the
1294                // suffix is reached, then assume that the suffix found belongs
1295                // to a field not yet reached. Return original position so that
1296                // another parser can continue on.
1297                return position;
1298            }
1299 
1300            if (iFieldType != SECONDS_MILLIS && iFieldType != SECONDS_OPTIONAL_MILLIS) {
1301                // Handle common case.
1302                setFieldValue(period, iFieldType, parseInt(text, position, length));
1303            } else if (fractPos < 0) {
1304                setFieldValue(period, SECONDS, parseInt(text, position, length));
1305                setFieldValue(period, MILLIS, 0);
1306            } else {
1307                int wholeValue = parseInt(text, position, fractPos - position - 1);
1308                setFieldValue(period, SECONDS, wholeValue);
1309 
1310                int fractLen = position + length - fractPos;
1311                int fractValue;
1312                if (fractLen <= 0) {
1313                    fractValue = 0;
1314                } else {
1315                    if (fractLen >= 3) {
1316                        fractValue = parseInt(text, fractPos, 3);
1317                    } else {
1318                        fractValue = parseInt(text, fractPos, fractLen);
1319                        if (fractLen == 1) {
1320                            fractValue *= 100;
1321                        } else {
1322                            fractValue *= 10;
1323                        }
1324                    }
1325                    if (wholeValue < 0) {
1326                        fractValue = -fractValue;
1327                    }
1328                }
1329 
1330                setFieldValue(period, MILLIS, fractValue);
1331            }
1332                
1333            position += length;
1334 
1335            if (position >= 0 && iSuffix != null) {
1336                position = iSuffix.parse(text, position);
1337            }
1338                
1339            return position;
1340        }
1341 
1342        /**
1343         * @param text text to parse
1344         * @param position position in text
1345         * @param length exact count of characters to parse
1346         * @return parsed int value
1347         */
1348        private int parseInt(String text, int position, int length) {
1349            if (length >= 10) {
1350                // Since value may exceed max, use stock parser which checks for this.
1351                return Integer.parseInt(text.substring(position, position + length));
1352            }
1353            if (length <= 0) {
1354                return 0;
1355            }
1356            int value = text.charAt(position++);
1357            length--;
1358            boolean negative;
1359            if (value == '-') {
1360                if (--length < 0) {
1361                    return 0;
1362                }
1363                negative = true;
1364                value = text.charAt(position++);
1365            } else {
1366                negative = false;
1367            }
1368            value -= '0';
1369            while (length-- > 0) {
1370                value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0';
1371            }
1372            return negative ? -value : value;
1373        }
1374 
1375        /**
1376         * @return Long.MAX_VALUE if nothing to print, otherwise value
1377         */
1378        long getFieldValue(ReadablePeriod period) {
1379            PeriodType type;
1380            if (iPrintZeroSetting == PRINT_ZERO_ALWAYS) {
1381                type = null; // Don't need to check if supported.
1382            } else {
1383                type = period.getPeriodType();
1384            }
1385            if (type != null && isSupported(type, iFieldType) == false) {
1386                return Long.MAX_VALUE;
1387            }
1388 
1389            long value;
1390 
1391            switch (iFieldType) {
1392            default:
1393                return Long.MAX_VALUE;
1394            case YEARS:
1395                value = period.get(DurationFieldType.years());
1396                break;
1397            case MONTHS:
1398                value = period.get(DurationFieldType.months());
1399                break;
1400            case WEEKS:
1401                value = period.get(DurationFieldType.weeks());
1402                break;
1403            case DAYS:
1404                value = period.get(DurationFieldType.days());
1405                break;
1406            case HOURS:
1407                value = period.get(DurationFieldType.hours());
1408                break;
1409            case MINUTES:
1410                value = period.get(DurationFieldType.minutes());
1411                break;
1412            case SECONDS:
1413                value = period.get(DurationFieldType.seconds());
1414                break;
1415            case MILLIS:
1416                value = period.get(DurationFieldType.millis());
1417                break;
1418            case SECONDS_MILLIS: // drop through
1419            case SECONDS_OPTIONAL_MILLIS:
1420                int seconds = period.get(DurationFieldType.seconds());
1421                int millis = period.get(DurationFieldType.millis());
1422                value = (seconds * (long) DateTimeConstants.MILLIS_PER_SECOND) + millis;
1423                break;
1424            }
1425 
1426            // determine if period is zero and this is the last field
1427            if (value == 0) {
1428                switch (iPrintZeroSetting) {
1429                case PRINT_ZERO_NEVER:
1430                    return Long.MAX_VALUE;
1431                case PRINT_ZERO_RARELY_LAST:
1432                    if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1433                        for (int i = iFieldType + 1; i <= MAX_FIELD; i++) {
1434                            if (isSupported(type, i) && iFieldFormatters[i] != null) {
1435                                return Long.MAX_VALUE;
1436                            }
1437                        }
1438                    } else {
1439                        return Long.MAX_VALUE;
1440                    }
1441                    break;
1442                case PRINT_ZERO_RARELY_FIRST:
1443                    if (isZero(period) && iFieldFormatters[iFieldType] == this) {
1444                        int i = Math.min(iFieldType, 8);  // line split out for IBM JDK
1445                        i--;                              // see bug 1660490
1446                        for (; i >= 0 && i <= MAX_FIELD; i--) {
1447                            if (isSupported(type, i) && iFieldFormatters[i] != null) {
1448                                return Long.MAX_VALUE;
1449                            }
1450                        }
1451                    } else {
1452                        return Long.MAX_VALUE;
1453                    }
1454                    break;
1455                }
1456            }
1457 
1458            return value;
1459        }
1460 
1461        boolean isZero(ReadablePeriod period) {
1462            for (int i = 0, isize = period.size(); i < isize; i++) {
1463                if (period.getValue(i) != 0) {
1464                    return false;
1465                }
1466            }
1467            return true;
1468        }
1469 
1470        boolean isSupported(PeriodType type, int field) {
1471            switch (field) {
1472            default:
1473                return false;
1474            case YEARS:
1475                return type.isSupported(DurationFieldType.years());
1476            case MONTHS:
1477                return type.isSupported(DurationFieldType.months());
1478            case WEEKS:
1479                return type.isSupported(DurationFieldType.weeks());
1480            case DAYS:
1481                return type.isSupported(DurationFieldType.days());
1482            case HOURS:
1483                return type.isSupported(DurationFieldType.hours());
1484            case MINUTES:
1485                return type.isSupported(DurationFieldType.minutes());
1486            case SECONDS:
1487                return type.isSupported(DurationFieldType.seconds());
1488            case MILLIS:
1489                return type.isSupported(DurationFieldType.millis());
1490            case SECONDS_MILLIS: // drop through
1491            case SECONDS_OPTIONAL_MILLIS:
1492                return type.isSupported(DurationFieldType.seconds()) ||
1493                       type.isSupported(DurationFieldType.millis());
1494            }
1495        }
1496 
1497        void setFieldValue(ReadWritablePeriod period, int field, int value) {
1498            switch (field) {
1499            default:
1500                break;
1501            case YEARS:
1502                period.setYears(value);
1503                break;
1504            case MONTHS:
1505                period.setMonths(value);
1506                break;
1507            case WEEKS:
1508                period.setWeeks(value);
1509                break;
1510            case DAYS:
1511                period.setDays(value);
1512                break;
1513            case HOURS:
1514                period.setHours(value);
1515                break;
1516            case MINUTES:
1517                period.setMinutes(value);
1518                break;
1519            case SECONDS:
1520                period.setSeconds(value);
1521                break;
1522            case MILLIS:
1523                period.setMillis(value);
1524                break;
1525            }
1526        }
1527 
1528        int getFieldType() {
1529            return iFieldType;
1530        }
1531    }
1532 
1533    //-----------------------------------------------------------------------
1534    /**
1535     * Handles a simple literal piece of text.
1536     */
1537    static class Literal
1538            implements PeriodPrinter, PeriodParser {
1539        static final Literal EMPTY = new Literal("");
1540        private final String iText;
1541 
1542        Literal(String text) {
1543            iText = text;
1544        }
1545 
1546        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1547            return 0;
1548        }
1549 
1550        public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1551            return iText.length();
1552        }
1553 
1554        public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1555            buf.append(iText);
1556        }
1557 
1558        public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1559            out.write(iText);
1560        }
1561 
1562        public int parseInto(
1563                ReadWritablePeriod period, String periodStr,
1564                int position, Locale locale) {
1565            if (periodStr.regionMatches(true, position, iText, 0, iText.length())) {
1566                return position + iText.length();
1567            }
1568            return ~position;
1569        }
1570    }
1571 
1572    //-----------------------------------------------------------------------
1573    /**
1574     * Handles a separator, that splits the fields into multiple parts.
1575     * For example, the 'T' in the ISO8601 standard.
1576     */
1577    static class Separator
1578            implements PeriodPrinter, PeriodParser {
1579        private final String iText;
1580        private final String iFinalText;
1581        private final String[] iParsedForms;
1582 
1583        private final boolean iUseBefore;
1584        private final boolean iUseAfter;
1585 
1586        private PeriodPrinter iBeforePrinter;
1587        private PeriodPrinter iAfterPrinter;
1588        private PeriodParser iBeforeParser;
1589        private PeriodParser iAfterParser;
1590 
1591        Separator(String text, String finalText, String[] variants,
1592                PeriodPrinter beforePrinter, PeriodParser beforeParser,
1593                boolean useBefore, boolean useAfter) {
1594            iText = text;
1595            iFinalText = finalText;
1596 
1597            if ((finalText == null || text.equals(finalText)) &&
1598                (variants == null || variants.length == 0)) {
1599 
1600                iParsedForms = new String[] {text};
1601            } else {
1602                // Filter and reverse sort the parsed forms.
1603                TreeSet parsedSet = new TreeSet(String.CASE_INSENSITIVE_ORDER);
1604                parsedSet.add(text);
1605                parsedSet.add(finalText);
1606                if (variants != null) {
1607                    for (int i=variants.length; --i>=0; ) {
1608                        parsedSet.add(variants[i]);
1609                    }
1610                }
1611                ArrayList parsedList = new ArrayList(parsedSet);
1612                Collections.reverse(parsedList);
1613                iParsedForms = (String[]) parsedList.toArray(new String[parsedList.size()]);
1614            }
1615 
1616            iBeforePrinter = beforePrinter;
1617            iBeforeParser = beforeParser;
1618            iUseBefore = useBefore;
1619            iUseAfter = useAfter;
1620        }
1621 
1622        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1623            int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale);
1624            if (sum < stopAt) {
1625                sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale);
1626            }
1627            return sum;
1628        }
1629 
1630        public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1631            PeriodPrinter before = iBeforePrinter;
1632            PeriodPrinter after = iAfterPrinter;
1633            
1634            int sum = before.calculatePrintedLength(period, locale)
1635                    + after.calculatePrintedLength(period, locale);
1636            
1637            if (iUseBefore) {
1638                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1639                    if (iUseAfter) {
1640                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1641                        if (afterCount > 0) {
1642                            sum += (afterCount > 1 ? iText : iFinalText).length();
1643                        }
1644                    } else {
1645                        sum += iText.length();
1646                    }
1647                }
1648            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1649                sum += iText.length();
1650            }
1651            
1652            return sum;
1653        }
1654 
1655        public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1656            PeriodPrinter before = iBeforePrinter;
1657            PeriodPrinter after = iAfterPrinter;
1658            
1659            before.printTo(buf, period, locale);
1660            if (iUseBefore) {
1661                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1662                    if (iUseAfter) {
1663                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1664                        if (afterCount > 0) {
1665                            buf.append(afterCount > 1 ? iText : iFinalText);
1666                        }
1667                    } else {
1668                        buf.append(iText);
1669                    }
1670                }
1671            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1672                buf.append(iText);
1673            }
1674            after.printTo(buf, period, locale);
1675        }
1676 
1677        public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1678            PeriodPrinter before = iBeforePrinter;
1679            PeriodPrinter after = iAfterPrinter;
1680            
1681            before.printTo(out, period, locale);
1682            if (iUseBefore) {
1683                if (before.countFieldsToPrint(period, 1, locale) > 0) {
1684                    if (iUseAfter) {
1685                        int afterCount = after.countFieldsToPrint(period, 2, locale);
1686                        if (afterCount > 0) {
1687                            out.write(afterCount > 1 ? iText : iFinalText);
1688                        }
1689                    } else {
1690                        out.write(iText);
1691                    }
1692                }
1693            } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
1694                out.write(iText);
1695            }
1696            after.printTo(out, period, locale);
1697        }
1698 
1699        public int parseInto(
1700                ReadWritablePeriod period, String periodStr,
1701                int position, Locale locale) {
1702            int oldPos = position;
1703            position = iBeforeParser.parseInto(period, periodStr, position, locale);
1704 
1705            if (position < 0) {
1706                return position;
1707            }
1708 
1709            boolean found = false;
1710            if (position > oldPos) {
1711                // Consume this separator.
1712                String[] parsedForms = iParsedForms;
1713                int length = parsedForms.length;
1714                for (int i=0; i < length; i++) {
1715                    String parsedForm = parsedForms[i];
1716                    if ((parsedForm == null || parsedForm.length() == 0) ||
1717                        periodStr.regionMatches
1718                        (true, position, parsedForm, 0, parsedForm.length())) {
1719                        
1720                        position += parsedForm.length();
1721                        found = true;
1722                        break;
1723                    }
1724                }
1725            }
1726 
1727            oldPos = position;
1728            position = iAfterParser.parseInto(period, periodStr, position, locale);
1729 
1730            if (position < 0) {
1731                return position;
1732            }
1733 
1734            if (found && position == oldPos) {
1735                // Separator should not have been supplied.
1736                return ~oldPos;
1737            }
1738 
1739            if (position > oldPos && !found && !iUseBefore) {
1740                // Separator was required.
1741                return ~oldPos;
1742            }
1743 
1744            return position;
1745        }
1746 
1747        Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) {
1748            iAfterPrinter = afterPrinter;
1749            iAfterParser = afterParser;
1750            return this;
1751        }
1752    }
1753 
1754    //-----------------------------------------------------------------------
1755    /**
1756     * Composite implementation that merges other fields to create a full pattern.
1757     */
1758    static class Composite
1759            implements PeriodPrinter, PeriodParser {
1760        
1761        private final PeriodPrinter[] iPrinters;
1762        private final PeriodParser[] iParsers;
1763 
1764        Composite(List elementPairs) {
1765            List printerList = new ArrayList();
1766            List parserList = new ArrayList();
1767 
1768            decompose(elementPairs, printerList, parserList);
1769 
1770            if (printerList.size() <= 0) {
1771                iPrinters = null;
1772            } else {
1773                iPrinters = (PeriodPrinter[]) printerList.toArray(
1774                        new PeriodPrinter[printerList.size()]);
1775            }
1776 
1777            if (parserList.size() <= 0) {
1778                iParsers = null;
1779            } else {
1780                iParsers = (PeriodParser[]) parserList.toArray(
1781                        new PeriodParser[parserList.size()]);
1782            }
1783        }
1784 
1785        public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) {
1786            int sum = 0;
1787            PeriodPrinter[] printers = iPrinters;
1788            for (int i=printers.length; sum < stopAt && --i>=0; ) {
1789                sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale);
1790            }
1791            return sum;
1792        }
1793 
1794        public int calculatePrintedLength(ReadablePeriod period, Locale locale) {
1795            int sum = 0;
1796            PeriodPrinter[] printers = iPrinters;
1797            for (int i=printers.length; --i>=0; ) {
1798                sum += printers[i].calculatePrintedLength(period, locale);
1799            }
1800            return sum;
1801        }
1802 
1803        public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) {
1804            PeriodPrinter[] printers = iPrinters;
1805            int len = printers.length;
1806            for (int i=0; i<len; i++) {
1807                printers[i].printTo(buf, period, locale);
1808            }
1809        }
1810 
1811        public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException {
1812            PeriodPrinter[] printers = iPrinters;
1813            int len = printers.length;
1814            for (int i=0; i<len; i++) {
1815                printers[i].printTo(out, period, locale);
1816            }
1817        }
1818 
1819        public int parseInto(
1820                ReadWritablePeriod period, String periodStr,
1821                int position, Locale locale) {
1822            PeriodParser[] parsers = iParsers;
1823            if (parsers == null) {
1824                throw new UnsupportedOperationException();
1825            }
1826 
1827            int len = parsers.length;
1828            for (int i=0; i<len && position >= 0; i++) {
1829                position = parsers[i].parseInto(period, periodStr, position, locale);
1830            }
1831            return position;
1832        }
1833 
1834        private void decompose(List elementPairs, List printerList, List parserList) {
1835            int size = elementPairs.size();
1836            for (int i=0; i<size; i+=2) {
1837                Object element = elementPairs.get(i);
1838                if (element instanceof PeriodPrinter) {
1839                    if (element instanceof Composite) {
1840                        addArrayToList(printerList, ((Composite) element).iPrinters);
1841                    } else {
1842                        printerList.add(element);
1843                    }
1844                }
1845 
1846                element = elementPairs.get(i + 1);
1847                if (element instanceof PeriodParser) {
1848                    if (element instanceof Composite) {
1849                        addArrayToList(parserList, ((Composite) element).iParsers);
1850                    } else {
1851                        parserList.add(element);
1852                    }
1853                }
1854            }
1855        }
1856 
1857        private void addArrayToList(List list, Object[] array) {
1858            if (array != null) {
1859                for (int i=0; i<array.length; i++) {
1860                    list.add(array[i]);
1861                }
1862            }
1863        }
1864    }
1865 
1866}

[all classes][org.joda.time.format]
EMMA 2.0.5312 (C) Vladimir Roubtsov