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