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