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 digits 1098 sum = 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 minDigits = iMinPrintedDigits; 1134 if (minDigits <= 1) { 1135 FormatUtils.appendUnpaddedInteger(buf, value); 1136 } else { 1137 FormatUtils.appendPaddedInteger(buf, value, minDigits); 1138 } 1139 if (iFieldType >= SECONDS_MILLIS) { 1140 int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND); 1141 if (iFieldType == SECONDS_MILLIS || dp > 0) { 1142 buf.append('.'); 1143 FormatUtils.appendPaddedInteger(buf, dp, 3); 1144 } 1145 } 1146 if (iSuffix != null) { 1147 iSuffix.printTo(buf, value); 1148 } 1149 } 1150 1151 public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException { 1152 long valueLong = getFieldValue(period); 1153 if (valueLong == Long.MAX_VALUE) { 1154 return; 1155 } 1156 int value = (int) valueLong; 1157 if (iFieldType >= SECONDS_MILLIS) { 1158 value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND); 1159 } 1160 1161 if (iPrefix != null) { 1162 iPrefix.printTo(out, value); 1163 } 1164 int minDigits = iMinPrintedDigits; 1165 if (minDigits <= 1) { 1166 FormatUtils.writeUnpaddedInteger(out, value); 1167 } else { 1168 FormatUtils.writePaddedInteger(out, value, minDigits); 1169 } 1170 if (iFieldType >= SECONDS_MILLIS) { 1171 int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND); 1172 if (iFieldType == SECONDS_MILLIS || dp > 0) { 1173 out.write('.'); 1174 FormatUtils.writePaddedInteger(out, dp, 3); 1175 } 1176 } 1177 if (iSuffix != null) { 1178 iSuffix.printTo(out, value); 1179 } 1180 } 1181 1182 public int parseInto( 1183 ReadWritablePeriod period, String text, 1184 int position, Locale locale) { 1185 1186 boolean mustParse = (iPrintZeroSetting == PRINT_ZERO_ALWAYS); 1187 1188 // Shortcut test. 1189 if (position >= text.length()) { 1190 return mustParse ? ~position : position; 1191 } 1192 1193 if (iPrefix != null) { 1194 position = iPrefix.parse(text, position); 1195 if (position >= 0) { 1196 // If prefix is found, then the parse must finish. 1197 mustParse = true; 1198 } else { 1199 // Prefix not found, so bail. 1200 if (!mustParse) { 1201 // It's okay because parsing of this field is not 1202 // required. Don't return an error. Fields down the 1203 // chain can continue on, trying to parse. 1204 return ~position; 1205 } 1206 return position; 1207 } 1208 } 1209 1210 int suffixPos = -1; 1211 if (iSuffix != null && !mustParse) { 1212 // Pre-scan the suffix, to help determine if this field must be 1213 // parsed. 1214 suffixPos = iSuffix.scan(text, position); 1215 if (suffixPos >= 0) { 1216 // If suffix is found, then parse must finish. 1217 mustParse = true; 1218 } else { 1219 // Suffix not found, so bail. 1220 if (!mustParse) { 1221 // It's okay because parsing of this field is not 1222 // required. Don't return an error. Fields down the 1223 // chain can continue on, trying to parse. 1224 return ~suffixPos; 1225 } 1226 return suffixPos; 1227 } 1228 } 1229 1230 if (!mustParse && !isSupported(period.getPeriodType(), iFieldType)) { 1231 // If parsing is not required and the field is not supported, 1232 // exit gracefully so that another parser can continue on. 1233 return position; 1234 } 1235 1236 int limit; 1237 if (suffixPos > 0) { 1238 limit = Math.min(iMaxParsedDigits, suffixPos - position); 1239 } else { 1240 limit = Math.min(iMaxParsedDigits, text.length() - position); 1241 } 1242 1243 // validate input number 1244 int length = 0; 1245 int fractPos = -1; 1246 boolean hasDigits = false; 1247 while (length < limit) { 1248 char c = text.charAt(position + length); 1249 // leading sign 1250 if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) { 1251 boolean negative = c == '-'; 1252 1253 // Next character must be a digit. 1254 if (length + 1 >= limit || 1255 (c = text.charAt(position + length + 1)) < '0' || c > '9') 1256 { 1257 break; 1258 } 1259 1260 if (negative) { 1261 length++; 1262 } else { 1263 // Skip the '+' for parseInt to succeed. 1264 position++; 1265 } 1266 // Expand the limit to disregard the sign character. 1267 limit = Math.min(limit + 1, text.length() - position); 1268 continue; 1269 } 1270 // main number 1271 if (c >= '0' && c <= '9') { 1272 hasDigits = true; 1273 } else { 1274 if ((c == '.' || c == ',') 1275 && (iFieldType == SECONDS_MILLIS || iFieldType == SECONDS_OPTIONAL_MILLIS)) { 1276 if (fractPos >= 0) { 1277 // can't have two decimals 1278 break; 1279 } 1280 fractPos = position + length + 1; 1281 // Expand the limit to disregard the decimal point. 1282 limit = Math.min(limit + 1, text.length() - position); 1283 } else { 1284 break; 1285 } 1286 } 1287 length++; 1288 } 1289 1290 if (!hasDigits) { 1291 return ~position; 1292 } 1293 1294 if (suffixPos >= 0 && position + length != suffixPos) { 1295 // If there are additional non-digit characters before the 1296 // suffix is reached, then assume that the suffix found belongs 1297 // to a field not yet reached. Return original position so that 1298 // another parser can continue on. 1299 return position; 1300 } 1301 1302 if (iFieldType != SECONDS_MILLIS && iFieldType != SECONDS_OPTIONAL_MILLIS) { 1303 // Handle common case. 1304 setFieldValue(period, iFieldType, parseInt(text, position, length)); 1305 } else if (fractPos < 0) { 1306 setFieldValue(period, SECONDS, parseInt(text, position, length)); 1307 setFieldValue(period, MILLIS, 0); 1308 } else { 1309 int wholeValue = parseInt(text, position, fractPos - position - 1); 1310 setFieldValue(period, SECONDS, wholeValue); 1311 1312 int fractLen = position + length - fractPos; 1313 int fractValue; 1314 if (fractLen <= 0) { 1315 fractValue = 0; 1316 } else { 1317 if (fractLen >= 3) { 1318 fractValue = parseInt(text, fractPos, 3); 1319 } else { 1320 fractValue = parseInt(text, fractPos, fractLen); 1321 if (fractLen == 1) { 1322 fractValue *= 100; 1323 } else { 1324 fractValue *= 10; 1325 } 1326 } 1327 if (wholeValue < 0) { 1328 fractValue = -fractValue; 1329 } 1330 } 1331 1332 setFieldValue(period, MILLIS, fractValue); 1333 } 1334 1335 position += length; 1336 1337 if (position >= 0 && iSuffix != null) { 1338 position = iSuffix.parse(text, position); 1339 } 1340 1341 return position; 1342 } 1343 1344 /** 1345 * @param text text to parse 1346 * @param position position in text 1347 * @param length exact count of characters to parse 1348 * @return parsed int value 1349 */ 1350 private int parseInt(String text, int position, int length) { 1351 if (length >= 10) { 1352 // Since value may exceed max, use stock parser which checks for this. 1353 return Integer.parseInt(text.substring(position, position + length)); 1354 } 1355 if (length <= 0) { 1356 return 0; 1357 } 1358 int value = text.charAt(position++); 1359 length--; 1360 boolean negative; 1361 if (value == '-') { 1362 if (--length < 0) { 1363 return 0; 1364 } 1365 negative = true; 1366 value = text.charAt(position++); 1367 } else { 1368 negative = false; 1369 } 1370 value -= '0'; 1371 while (length-- > 0) { 1372 value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0'; 1373 } 1374 return negative ? -value : value; 1375 } 1376 1377 /** 1378 * @return Long.MAX_VALUE if nothing to print, otherwise value 1379 */ 1380 long getFieldValue(ReadablePeriod period) { 1381 PeriodType type; 1382 if (iPrintZeroSetting == PRINT_ZERO_ALWAYS) { 1383 type = null; // Don't need to check if supported. 1384 } else { 1385 type = period.getPeriodType(); 1386 } 1387 if (type != null && isSupported(type, iFieldType) == false) { 1388 return Long.MAX_VALUE; 1389 } 1390 1391 long value; 1392 1393 switch (iFieldType) { 1394 default: 1395 return Long.MAX_VALUE; 1396 case YEARS: 1397 value = period.get(DurationFieldType.years()); 1398 break; 1399 case MONTHS: 1400 value = period.get(DurationFieldType.months()); 1401 break; 1402 case WEEKS: 1403 value = period.get(DurationFieldType.weeks()); 1404 break; 1405 case DAYS: 1406 value = period.get(DurationFieldType.days()); 1407 break; 1408 case HOURS: 1409 value = period.get(DurationFieldType.hours()); 1410 break; 1411 case MINUTES: 1412 value = period.get(DurationFieldType.minutes()); 1413 break; 1414 case SECONDS: 1415 value = period.get(DurationFieldType.seconds()); 1416 break; 1417 case MILLIS: 1418 value = period.get(DurationFieldType.millis()); 1419 break; 1420 case SECONDS_MILLIS: // drop through 1421 case SECONDS_OPTIONAL_MILLIS: 1422 int seconds = period.get(DurationFieldType.seconds()); 1423 int millis = period.get(DurationFieldType.millis()); 1424 value = (seconds * (long) DateTimeConstants.MILLIS_PER_SECOND) + millis; 1425 break; 1426 } 1427 1428 // determine if period is zero and this is the last field 1429 if (value == 0) { 1430 switch (iPrintZeroSetting) { 1431 case PRINT_ZERO_NEVER: 1432 return Long.MAX_VALUE; 1433 case PRINT_ZERO_RARELY_LAST: 1434 if (isZero(period) && iFieldFormatters[iFieldType] == this) { 1435 for (int i = iFieldType + 1; i <= MAX_FIELD; i++) { 1436 if (isSupported(type, i) && iFieldFormatters[i] != null) { 1437 return Long.MAX_VALUE; 1438 } 1439 } 1440 } else { 1441 return Long.MAX_VALUE; 1442 } 1443 break; 1444 case PRINT_ZERO_RARELY_FIRST: 1445 if (isZero(period) && iFieldFormatters[iFieldType] == this) { 1446 int i = Math.min(iFieldType, 8); // line split out for IBM JDK 1447 i--; // see bug 1660490 1448 for (; i >= 0 && i <= MAX_FIELD; i--) { 1449 if (isSupported(type, i) && iFieldFormatters[i] != null) { 1450 return Long.MAX_VALUE; 1451 } 1452 } 1453 } else { 1454 return Long.MAX_VALUE; 1455 } 1456 break; 1457 } 1458 } 1459 1460 return value; 1461 } 1462 1463 boolean isZero(ReadablePeriod period) { 1464 for (int i = 0, isize = period.size(); i < isize; i++) { 1465 if (period.getValue(i) != 0) { 1466 return false; 1467 } 1468 } 1469 return true; 1470 } 1471 1472 boolean isSupported(PeriodType type, int field) { 1473 switch (field) { 1474 default: 1475 return false; 1476 case YEARS: 1477 return type.isSupported(DurationFieldType.years()); 1478 case MONTHS: 1479 return type.isSupported(DurationFieldType.months()); 1480 case WEEKS: 1481 return type.isSupported(DurationFieldType.weeks()); 1482 case DAYS: 1483 return type.isSupported(DurationFieldType.days()); 1484 case HOURS: 1485 return type.isSupported(DurationFieldType.hours()); 1486 case MINUTES: 1487 return type.isSupported(DurationFieldType.minutes()); 1488 case SECONDS: 1489 return type.isSupported(DurationFieldType.seconds()); 1490 case MILLIS: 1491 return type.isSupported(DurationFieldType.millis()); 1492 case SECONDS_MILLIS: // drop through 1493 case SECONDS_OPTIONAL_MILLIS: 1494 return type.isSupported(DurationFieldType.seconds()) || 1495 type.isSupported(DurationFieldType.millis()); 1496 } 1497 } 1498 1499 void setFieldValue(ReadWritablePeriod period, int field, int value) { 1500 switch (field) { 1501 default: 1502 break; 1503 case YEARS: 1504 period.setYears(value); 1505 break; 1506 case MONTHS: 1507 period.setMonths(value); 1508 break; 1509 case WEEKS: 1510 period.setWeeks(value); 1511 break; 1512 case DAYS: 1513 period.setDays(value); 1514 break; 1515 case HOURS: 1516 period.setHours(value); 1517 break; 1518 case MINUTES: 1519 period.setMinutes(value); 1520 break; 1521 case SECONDS: 1522 period.setSeconds(value); 1523 break; 1524 case MILLIS: 1525 period.setMillis(value); 1526 break; 1527 } 1528 } 1529 1530 int getFieldType() { 1531 return iFieldType; 1532 } 1533 } 1534 1535 //----------------------------------------------------------------------- 1536 /** 1537 * Handles a simple literal piece of text. 1538 */ 1539 static class Literal 1540 implements PeriodPrinter, PeriodParser { 1541 static final Literal EMPTY = new Literal(""); 1542 private final String iText; 1543 1544 Literal(String text) { 1545 iText = text; 1546 } 1547 1548 public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) { 1549 return 0; 1550 } 1551 1552 public int calculatePrintedLength(ReadablePeriod period, Locale locale) { 1553 return iText.length(); 1554 } 1555 1556 public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) { 1557 buf.append(iText); 1558 } 1559 1560 public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException { 1561 out.write(iText); 1562 } 1563 1564 public int parseInto( 1565 ReadWritablePeriod period, String periodStr, 1566 int position, Locale locale) { 1567 if (periodStr.regionMatches(true, position, iText, 0, iText.length())) { 1568 return position + iText.length(); 1569 } 1570 return ~position; 1571 } 1572 } 1573 1574 //----------------------------------------------------------------------- 1575 /** 1576 * Handles a separator, that splits the fields into multiple parts. 1577 * For example, the 'T' in the ISO8601 standard. 1578 */ 1579 static class Separator 1580 implements PeriodPrinter, PeriodParser { 1581 private final String iText; 1582 private final String iFinalText; 1583 private final String[] iParsedForms; 1584 1585 private final boolean iUseBefore; 1586 private final boolean iUseAfter; 1587 1588 private final PeriodPrinter iBeforePrinter; 1589 private volatile PeriodPrinter iAfterPrinter; 1590 private final PeriodParser iBeforeParser; 1591 private volatile PeriodParser iAfterParser; 1592 1593 Separator(String text, String finalText, String[] variants, 1594 PeriodPrinter beforePrinter, PeriodParser beforeParser, 1595 boolean useBefore, boolean useAfter) { 1596 iText = text; 1597 iFinalText = finalText; 1598 1599 if ((finalText == null || text.equals(finalText)) && 1600 (variants == null || variants.length == 0)) { 1601 1602 iParsedForms = new String[] {text}; 1603 } else { 1604 // Filter and reverse sort the parsed forms. 1605 TreeSet<String> parsedSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); 1606 parsedSet.add(text); 1607 parsedSet.add(finalText); 1608 if (variants != null) { 1609 for (int i=variants.length; --i>=0; ) { 1610 parsedSet.add(variants[i]); 1611 } 1612 } 1613 ArrayList<String> parsedList = new ArrayList<String>(parsedSet); 1614 Collections.reverse(parsedList); 1615 iParsedForms = parsedList.toArray(new String[parsedList.size()]); 1616 } 1617 1618 iBeforePrinter = beforePrinter; 1619 iBeforeParser = beforeParser; 1620 iUseBefore = useBefore; 1621 iUseAfter = useAfter; 1622 } 1623 1624 public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) { 1625 int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale); 1626 if (sum < stopAt) { 1627 sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale); 1628 } 1629 return sum; 1630 } 1631 1632 public int calculatePrintedLength(ReadablePeriod period, Locale locale) { 1633 PeriodPrinter before = iBeforePrinter; 1634 PeriodPrinter after = iAfterPrinter; 1635 1636 int sum = before.calculatePrintedLength(period, locale) 1637 + after.calculatePrintedLength(period, locale); 1638 1639 if (iUseBefore) { 1640 if (before.countFieldsToPrint(period, 1, locale) > 0) { 1641 if (iUseAfter) { 1642 int afterCount = after.countFieldsToPrint(period, 2, locale); 1643 if (afterCount > 0) { 1644 sum += (afterCount > 1 ? iText : iFinalText).length(); 1645 } 1646 } else { 1647 sum += iText.length(); 1648 } 1649 } 1650 } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { 1651 sum += iText.length(); 1652 } 1653 1654 return sum; 1655 } 1656 1657 public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) { 1658 PeriodPrinter before = iBeforePrinter; 1659 PeriodPrinter after = iAfterPrinter; 1660 1661 before.printTo(buf, period, locale); 1662 if (iUseBefore) { 1663 if (before.countFieldsToPrint(period, 1, locale) > 0) { 1664 if (iUseAfter) { 1665 int afterCount = after.countFieldsToPrint(period, 2, locale); 1666 if (afterCount > 0) { 1667 buf.append(afterCount > 1 ? iText : iFinalText); 1668 } 1669 } else { 1670 buf.append(iText); 1671 } 1672 } 1673 } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { 1674 buf.append(iText); 1675 } 1676 after.printTo(buf, period, locale); 1677 } 1678 1679 public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException { 1680 PeriodPrinter before = iBeforePrinter; 1681 PeriodPrinter after = iAfterPrinter; 1682 1683 before.printTo(out, period, locale); 1684 if (iUseBefore) { 1685 if (before.countFieldsToPrint(period, 1, locale) > 0) { 1686 if (iUseAfter) { 1687 int afterCount = after.countFieldsToPrint(period, 2, locale); 1688 if (afterCount > 0) { 1689 out.write(afterCount > 1 ? iText : iFinalText); 1690 } 1691 } else { 1692 out.write(iText); 1693 } 1694 } 1695 } else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) { 1696 out.write(iText); 1697 } 1698 after.printTo(out, period, locale); 1699 } 1700 1701 public int parseInto( 1702 ReadWritablePeriod period, String periodStr, 1703 int position, Locale locale) { 1704 int oldPos = position; 1705 position = iBeforeParser.parseInto(period, periodStr, position, locale); 1706 1707 if (position < 0) { 1708 return position; 1709 } 1710 1711 boolean found = false; 1712 if (position > oldPos) { 1713 // Consume this separator. 1714 String[] parsedForms = iParsedForms; 1715 int length = parsedForms.length; 1716 for (int i=0; i < length; i++) { 1717 String parsedForm = parsedForms[i]; 1718 if ((parsedForm == null || parsedForm.length() == 0) || 1719 periodStr.regionMatches 1720 (true, position, parsedForm, 0, parsedForm.length())) { 1721 1722 position += (parsedForm == null ? 0 : parsedForm.length()); 1723 found = true; 1724 break; 1725 } 1726 } 1727 } 1728 1729 oldPos = position; 1730 position = iAfterParser.parseInto(period, periodStr, position, locale); 1731 1732 if (position < 0) { 1733 return position; 1734 } 1735 1736 if (found && position == oldPos) { 1737 // Separator should not have been supplied. 1738 return ~oldPos; 1739 } 1740 1741 if (position > oldPos && !found && !iUseBefore) { 1742 // Separator was required. 1743 return ~oldPos; 1744 } 1745 1746 return position; 1747 } 1748 1749 Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) { 1750 iAfterPrinter = afterPrinter; 1751 iAfterParser = afterParser; 1752 return this; 1753 } 1754 } 1755 1756 //----------------------------------------------------------------------- 1757 /** 1758 * Composite implementation that merges other fields to create a full pattern. 1759 */ 1760 static class Composite 1761 implements PeriodPrinter, PeriodParser { 1762 1763 private final PeriodPrinter[] iPrinters; 1764 private final PeriodParser[] iParsers; 1765 1766 Composite(List<Object> elementPairs) { 1767 List<Object> printerList = new ArrayList<Object>(); 1768 List<Object> parserList = new ArrayList<Object>(); 1769 1770 decompose(elementPairs, printerList, parserList); 1771 1772 if (printerList.size() <= 0) { 1773 iPrinters = null; 1774 } else { 1775 iPrinters = printerList.toArray( 1776 new PeriodPrinter[printerList.size()]); 1777 } 1778 1779 if (parserList.size() <= 0) { 1780 iParsers = null; 1781 } else { 1782 iParsers = parserList.toArray( 1783 new PeriodParser[parserList.size()]); 1784 } 1785 } 1786 1787 public int countFieldsToPrint(ReadablePeriod period, int stopAt, Locale locale) { 1788 int sum = 0; 1789 PeriodPrinter[] printers = iPrinters; 1790 for (int i=printers.length; sum < stopAt && --i>=0; ) { 1791 sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale); 1792 } 1793 return sum; 1794 } 1795 1796 public int calculatePrintedLength(ReadablePeriod period, Locale locale) { 1797 int sum = 0; 1798 PeriodPrinter[] printers = iPrinters; 1799 for (int i=printers.length; --i>=0; ) { 1800 sum += printers[i].calculatePrintedLength(period, locale); 1801 } 1802 return sum; 1803 } 1804 1805 public void printTo(StringBuffer buf, ReadablePeriod period, Locale locale) { 1806 PeriodPrinter[] printers = iPrinters; 1807 int len = printers.length; 1808 for (int i=0; i<len; i++) { 1809 printers[i].printTo(buf, period, locale); 1810 } 1811 } 1812 1813 public void printTo(Writer out, ReadablePeriod period, Locale locale) throws IOException { 1814 PeriodPrinter[] printers = iPrinters; 1815 int len = printers.length; 1816 for (int i=0; i<len; i++) { 1817 printers[i].printTo(out, period, locale); 1818 } 1819 } 1820 1821 public int parseInto( 1822 ReadWritablePeriod period, String periodStr, 1823 int position, Locale locale) { 1824 PeriodParser[] parsers = iParsers; 1825 if (parsers == null) { 1826 throw new UnsupportedOperationException(); 1827 } 1828 1829 int len = parsers.length; 1830 for (int i=0; i<len && position >= 0; i++) { 1831 position = parsers[i].parseInto(period, periodStr, position, locale); 1832 } 1833 return position; 1834 } 1835 1836 private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) { 1837 int size = elementPairs.size(); 1838 for (int i=0; i<size; i+=2) { 1839 Object element = elementPairs.get(i); 1840 if (element instanceof PeriodPrinter) { 1841 if (element instanceof Composite) { 1842 addArrayToList(printerList, ((Composite) element).iPrinters); 1843 } else { 1844 printerList.add(element); 1845 } 1846 } 1847 1848 element = elementPairs.get(i + 1); 1849 if (element instanceof PeriodParser) { 1850 if (element instanceof Composite) { 1851 addArrayToList(parserList, ((Composite) element).iParsers); 1852 } else { 1853 parserList.add(element); 1854 } 1855 } 1856 } 1857 } 1858 1859 private void addArrayToList(List<Object> list, Object[] array) { 1860 if (array != null) { 1861 for (int i=0; i<array.length; i++) { 1862 list.add(array[i]); 1863 } 1864 } 1865 } 1866 } 1867 1868 }