001 /* 002 * Copyright 2001-2006 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 * .printZeroRarely() 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 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(); 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 final PeriodPrinter iBeforePrinter; 1587 private volatile PeriodPrinter iAfterPrinter; 1588 private final PeriodParser iBeforeParser; 1589 private volatile 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 }