0001: /*
0002: * Copyright 2001-2006 Stephen Colebourne
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.joda.time.format;
0017:
0018: import java.io.IOException;
0019: import java.io.Writer;
0020: import java.util.ArrayList;
0021: import java.util.Collections;
0022: import java.util.List;
0023: import java.util.Locale;
0024: import java.util.TreeSet;
0025:
0026: import org.joda.time.DateTimeConstants;
0027: import org.joda.time.DurationFieldType;
0028: import org.joda.time.PeriodType;
0029: import org.joda.time.ReadWritablePeriod;
0030: import org.joda.time.ReadablePeriod;
0031:
0032: /**
0033: * Factory that creates complex instances of PeriodFormatter via method calls.
0034: * <p>
0035: * Period formatting is performed by the {@link PeriodFormatter} class.
0036: * Three classes provide factory methods to create formatters, and this is one.
0037: * The others are {@link PeriodFormat} and {@link ISOPeriodFormat}.
0038: * <p>
0039: * PeriodFormatterBuilder is used for constructing formatters which are then
0040: * used to print or parse. The formatters are built by appending specific fields
0041: * or other formatters to an instance of this builder.
0042: * <p>
0043: * For example, a formatter that prints years and months, like "15 years and 8 months",
0044: * can be constructed as follows:
0045: * <p>
0046: * <pre>
0047: * PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
0048: * .printZeroAlways()
0049: * .appendYears()
0050: * .appendSuffix(" year", " years")
0051: * .appendSeparator(" and ")
0052: * .printZeroRarely()
0053: * .appendMonths()
0054: * .appendSuffix(" month", " months")
0055: * .toFormatter();
0056: * </pre>
0057: * <p>
0058: * PeriodFormatterBuilder itself is mutable and not thread-safe, but the
0059: * formatters that it builds are thread-safe and immutable.
0060: *
0061: * @author Brian S O'Neill
0062: * @since 1.0
0063: * @see PeriodFormat
0064: */
0065: public class PeriodFormatterBuilder {
0066: private static final int PRINT_ZERO_RARELY_FIRST = 1;
0067: private static final int PRINT_ZERO_RARELY_LAST = 2;
0068: private static final int PRINT_ZERO_IF_SUPPORTED = 3;
0069: private static final int PRINT_ZERO_ALWAYS = 4;
0070: private static final int PRINT_ZERO_NEVER = 5;
0071:
0072: private static final int YEARS = 0;
0073: private static final int MONTHS = 1;
0074: private static final int WEEKS = 2;
0075: private static final int DAYS = 3;
0076: private static final int HOURS = 4;
0077: private static final int MINUTES = 5;
0078: private static final int SECONDS = 6;
0079: private static final int MILLIS = 7;
0080: private static final int SECONDS_MILLIS = 8;
0081: private static final int SECONDS_OPTIONAL_MILLIS = 9;
0082: private static final int MAX_FIELD = SECONDS_OPTIONAL_MILLIS;
0083:
0084: private int iMinPrintedDigits;
0085: private int iPrintZeroSetting;
0086: private int iMaxParsedDigits;
0087: private boolean iRejectSignedValues;
0088:
0089: private PeriodFieldAffix iPrefix;
0090:
0091: // List of Printers and Parsers used to build a final formatter.
0092: private List iElementPairs;
0093: /** Set to true if the formatter is not a printer. */
0094: private boolean iNotPrinter;
0095: /** Set to true if the formatter is not a parser. */
0096: private boolean iNotParser;
0097:
0098: // Last PeriodFormatter appended of each field type.
0099: private FieldFormatter[] iFieldFormatters;
0100:
0101: public PeriodFormatterBuilder() {
0102: clear();
0103: }
0104:
0105: //-----------------------------------------------------------------------
0106: /**
0107: * Constructs a PeriodFormatter using all the appended elements.
0108: * <p>
0109: * This is the main method used by applications at the end of the build
0110: * process to create a usable formatter.
0111: * <p>
0112: * Subsequent changes to this builder do not affect the returned formatter.
0113: * <p>
0114: * The returned formatter may not support both printing and parsing.
0115: * The methods {@link PeriodFormatter#isPrinter()} and
0116: * {@link PeriodFormatter#isParser()} will help you determine the state
0117: * of the formatter.
0118: *
0119: * @return the newly created formatter
0120: * @throws IllegalStateException if the builder can produce neither a printer nor a parser
0121: */
0122: public PeriodFormatter toFormatter() {
0123: PeriodFormatter formatter = toFormatter(iElementPairs,
0124: iNotPrinter, iNotParser);
0125: iFieldFormatters = (FieldFormatter[]) iFieldFormatters.clone();
0126: return formatter;
0127: }
0128:
0129: /**
0130: * Internal method to create a PeriodPrinter instance using all the
0131: * appended elements.
0132: * <p>
0133: * Most applications will not use this method.
0134: * If you want a printer in an application, call {@link #toFormatter()}
0135: * and just use the printing API.
0136: * <p>
0137: * Subsequent changes to this builder do not affect the returned printer.
0138: *
0139: * @return the newly created printer, null if builder cannot create a printer
0140: */
0141: public PeriodPrinter toPrinter() {
0142: if (iNotPrinter) {
0143: return null;
0144: }
0145: return toFormatter().getPrinter();
0146: }
0147:
0148: /**
0149: * Internal method to create a PeriodParser instance using all the
0150: * appended elements.
0151: * <p>
0152: * Most applications will not use this method.
0153: * If you want a printer in an application, call {@link #toFormatter()}
0154: * and just use the printing API.
0155: * <p>
0156: * Subsequent changes to this builder do not affect the returned parser.
0157: *
0158: * @return the newly created parser, null if builder cannot create a parser
0159: */
0160: public PeriodParser toParser() {
0161: if (iNotParser) {
0162: return null;
0163: }
0164: return toFormatter().getParser();
0165: }
0166:
0167: //-----------------------------------------------------------------------
0168: /**
0169: * Clears out all the appended elements, allowing this builder to be reused.
0170: */
0171: public void clear() {
0172: iMinPrintedDigits = 1;
0173: iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
0174: iMaxParsedDigits = 10;
0175: iRejectSignedValues = false;
0176: iPrefix = null;
0177: if (iElementPairs == null) {
0178: iElementPairs = new ArrayList();
0179: } else {
0180: iElementPairs.clear();
0181: }
0182: iNotPrinter = false;
0183: iNotParser = false;
0184: iFieldFormatters = new FieldFormatter[10];
0185: }
0186:
0187: /**
0188: * Appends another formatter.
0189: *
0190: * @return this PeriodFormatterBuilder
0191: */
0192: public PeriodFormatterBuilder append(PeriodFormatter formatter) {
0193: if (formatter == null) {
0194: throw new IllegalArgumentException("No formatter supplied");
0195: }
0196: clearPrefix();
0197: append0(formatter.getPrinter(), formatter.getParser());
0198: return this ;
0199: }
0200:
0201: /**
0202: * Appends a printer parser pair.
0203: * <p>
0204: * Either the printer or the parser may be null, in which case the builder will
0205: * be unable to produce a parser or printer repectively.
0206: *
0207: * @param printer appends a printer to the builder, null if printing is not supported
0208: * @param parser appends a parser to the builder, null if parsing is not supported
0209: * @return this PeriodFormatterBuilder
0210: * @throws IllegalArgumentException if both the printer and parser are null
0211: */
0212: public PeriodFormatterBuilder append(PeriodPrinter printer,
0213: PeriodParser parser) {
0214: if (printer == null && parser == null) {
0215: throw new IllegalArgumentException(
0216: "No printer or parser supplied");
0217: }
0218: clearPrefix();
0219: append0(printer, parser);
0220: return this ;
0221: }
0222:
0223: /**
0224: * Instructs the printer to emit specific text, and the parser to expect it.
0225: * The parser is case-insensitive.
0226: *
0227: * @return this PeriodFormatterBuilder
0228: * @throws IllegalArgumentException if text is null
0229: */
0230: public PeriodFormatterBuilder appendLiteral(String text) {
0231: if (text == null) {
0232: throw new IllegalArgumentException(
0233: "Literal must not be null");
0234: }
0235: clearPrefix();
0236: Literal literal = new Literal(text);
0237: append0(literal, literal);
0238: return this ;
0239: }
0240:
0241: /**
0242: * Set the minimum digits printed for the next and following appended
0243: * fields. By default, the minimum digits printed is one. If the field value
0244: * is zero, it is not printed unless a printZero rule is applied.
0245: *
0246: * @return this PeriodFormatterBuilder
0247: */
0248: public PeriodFormatterBuilder minimumPrintedDigits(int minDigits) {
0249: iMinPrintedDigits = minDigits;
0250: return this ;
0251: }
0252:
0253: /**
0254: * Set the maximum digits parsed for the next and following appended
0255: * fields. By default, the maximum digits parsed is ten.
0256: *
0257: * @return this PeriodFormatterBuilder
0258: */
0259: public PeriodFormatterBuilder maximumParsedDigits(int maxDigits) {
0260: iMaxParsedDigits = maxDigits;
0261: return this ;
0262: }
0263:
0264: /**
0265: * Reject signed values when parsing the next and following appended fields.
0266: *
0267: * @return this PeriodFormatterBuilder
0268: */
0269: public PeriodFormatterBuilder rejectSignedValues(boolean v) {
0270: iRejectSignedValues = v;
0271: return this ;
0272: }
0273:
0274: /**
0275: * Never print zero values for the next and following appended fields,
0276: * unless no fields would be printed. If no fields are printed, the printer
0277: * forces the last "printZeroRarely" field to print a zero.
0278: * <p>
0279: * This field setting is the default.
0280: *
0281: * @return this PeriodFormatterBuilder
0282: */
0283: public PeriodFormatterBuilder printZeroRarelyLast() {
0284: iPrintZeroSetting = PRINT_ZERO_RARELY_LAST;
0285: return this ;
0286: }
0287:
0288: /**
0289: * Never print zero values for the next and following appended fields,
0290: * unless no fields would be printed. If no fields are printed, the printer
0291: * forces the first "printZeroRarely" field to print a zero.
0292: *
0293: * @return this PeriodFormatterBuilder
0294: */
0295: public PeriodFormatterBuilder printZeroRarelyFirst() {
0296: iPrintZeroSetting = PRINT_ZERO_RARELY_FIRST;
0297: return this ;
0298: }
0299:
0300: /**
0301: * Print zero values for the next and following appened fields only if the
0302: * period supports it.
0303: *
0304: * @return this PeriodFormatterBuilder
0305: */
0306: public PeriodFormatterBuilder printZeroIfSupported() {
0307: iPrintZeroSetting = PRINT_ZERO_IF_SUPPORTED;
0308: return this ;
0309: }
0310:
0311: /**
0312: * Always print zero values for the next and following appended fields,
0313: * even if the period doesn't support it. The parser requires values for
0314: * fields that always print zero.
0315: *
0316: * @return this PeriodFormatterBuilder
0317: */
0318: public PeriodFormatterBuilder printZeroAlways() {
0319: iPrintZeroSetting = PRINT_ZERO_ALWAYS;
0320: return this ;
0321: }
0322:
0323: /**
0324: * Never print zero values for the next and following appended fields,
0325: * unless no fields would be printed. If no fields are printed, the printer
0326: * forces the last "printZeroRarely" field to print a zero.
0327: * <p>
0328: * This field setting is the default.
0329: *
0330: * @return this PeriodFormatterBuilder
0331: */
0332: public PeriodFormatterBuilder printZeroNever() {
0333: iPrintZeroSetting = PRINT_ZERO_NEVER;
0334: return this ;
0335: }
0336:
0337: //-----------------------------------------------------------------------
0338: /**
0339: * Append a field prefix which applies only to the next appended field. If
0340: * the field is not printed, neither is the prefix.
0341: *
0342: * @param text text to print before field only if field is printed
0343: * @return this PeriodFormatterBuilder
0344: * @see #appendSuffix
0345: */
0346: public PeriodFormatterBuilder appendPrefix(String text) {
0347: if (text == null) {
0348: throw new IllegalArgumentException();
0349: }
0350: return appendPrefix(new SimpleAffix(text));
0351: }
0352:
0353: /**
0354: * Append a field prefix which applies only to the next appended field. If
0355: * the field is not printed, neither is the prefix.
0356: * <p>
0357: * During parsing, the singular and plural versions are accepted whether
0358: * or not the actual value matches plurality.
0359: *
0360: * @param singularText text to print if field value is one
0361: * @param pluralText text to print if field value is not one
0362: * @return this PeriodFormatterBuilder
0363: * @see #appendSuffix
0364: */
0365: public PeriodFormatterBuilder appendPrefix(String singularText,
0366: String pluralText) {
0367: if (singularText == null || pluralText == null) {
0368: throw new IllegalArgumentException();
0369: }
0370: return appendPrefix(new PluralAffix(singularText, pluralText));
0371: }
0372:
0373: /**
0374: * Append a field prefix which applies only to the next appended field. If
0375: * the field is not printed, neither is the prefix.
0376: *
0377: * @param prefix custom prefix
0378: * @return this PeriodFormatterBuilder
0379: * @see #appendSuffix
0380: */
0381: private PeriodFormatterBuilder appendPrefix(PeriodFieldAffix prefix) {
0382: if (prefix == null) {
0383: throw new IllegalArgumentException();
0384: }
0385: if (iPrefix != null) {
0386: prefix = new CompositeAffix(iPrefix, prefix);
0387: }
0388: iPrefix = prefix;
0389: return this ;
0390: }
0391:
0392: //-----------------------------------------------------------------------
0393: /**
0394: * Instruct the printer to emit an integer years field, if supported.
0395: * <p>
0396: * The number of printed and parsed digits can be controlled using
0397: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0398: *
0399: * @return this PeriodFormatterBuilder
0400: */
0401: public PeriodFormatterBuilder appendYears() {
0402: appendField(YEARS);
0403: return this ;
0404: }
0405:
0406: /**
0407: * Instruct the printer to emit an integer months field, if supported.
0408: * <p>
0409: * The number of printed and parsed digits can be controlled using
0410: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0411: *
0412: * @return this PeriodFormatterBuilder
0413: */
0414: public PeriodFormatterBuilder appendMonths() {
0415: appendField(MONTHS);
0416: return this ;
0417: }
0418:
0419: /**
0420: * Instruct the printer to emit an integer weeks field, if supported.
0421: * <p>
0422: * The number of printed and parsed digits can be controlled using
0423: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0424: *
0425: * @return this PeriodFormatterBuilder
0426: */
0427: public PeriodFormatterBuilder appendWeeks() {
0428: appendField(WEEKS);
0429: return this ;
0430: }
0431:
0432: /**
0433: * Instruct the printer to emit an integer days field, if supported.
0434: * <p>
0435: * The number of printed and parsed digits can be controlled using
0436: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0437: *
0438: * @return this PeriodFormatterBuilder
0439: */
0440: public PeriodFormatterBuilder appendDays() {
0441: appendField(DAYS);
0442: return this ;
0443: }
0444:
0445: /**
0446: * Instruct the printer to emit an integer hours field, if supported.
0447: * <p>
0448: * The number of printed and parsed digits can be controlled using
0449: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0450: *
0451: * @return this PeriodFormatterBuilder
0452: */
0453: public PeriodFormatterBuilder appendHours() {
0454: appendField(HOURS);
0455: return this ;
0456: }
0457:
0458: /**
0459: * Instruct the printer to emit an integer minutes field, if supported.
0460: * <p>
0461: * The number of printed and parsed digits can be controlled using
0462: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0463: *
0464: * @return this PeriodFormatterBuilder
0465: */
0466: public PeriodFormatterBuilder appendMinutes() {
0467: appendField(MINUTES);
0468: return this ;
0469: }
0470:
0471: /**
0472: * Instruct the printer to emit an integer seconds field, if supported.
0473: * <p>
0474: * The number of printed and parsed digits can be controlled using
0475: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0476: *
0477: * @return this PeriodFormatterBuilder
0478: */
0479: public PeriodFormatterBuilder appendSeconds() {
0480: appendField(SECONDS);
0481: return this ;
0482: }
0483:
0484: /**
0485: * Instruct the printer to emit a combined seconds and millis field, if supported.
0486: * The millis will overflow into the seconds if necessary.
0487: * The millis are always output.
0488: *
0489: * @return this PeriodFormatterBuilder
0490: */
0491: public PeriodFormatterBuilder appendSecondsWithMillis() {
0492: appendField(SECONDS_MILLIS);
0493: return this ;
0494: }
0495:
0496: /**
0497: * Instruct the printer to emit a combined seconds and millis field, if supported.
0498: * The millis will overflow into the seconds if necessary.
0499: * The millis are only output if non-zero.
0500: *
0501: * @return this PeriodFormatterBuilder
0502: */
0503: public PeriodFormatterBuilder appendSecondsWithOptionalMillis() {
0504: appendField(SECONDS_OPTIONAL_MILLIS);
0505: return this ;
0506: }
0507:
0508: /**
0509: * Instruct the printer to emit an integer millis field, if supported.
0510: * <p>
0511: * The number of printed and parsed digits can be controlled using
0512: * {@link #minimumPrintedDigits(int)} and {@link #maximumParsedDigits(int)}.
0513: *
0514: * @return this PeriodFormatterBuilder
0515: */
0516: public PeriodFormatterBuilder appendMillis() {
0517: appendField(MILLIS);
0518: return this ;
0519: }
0520:
0521: /**
0522: * Instruct the printer to emit an integer millis field, if supported.
0523: * <p>
0524: * The number of arsed digits can be controlled using {@link #maximumParsedDigits(int)}.
0525: *
0526: * @return this PeriodFormatterBuilder
0527: */
0528: public PeriodFormatterBuilder appendMillis3Digit() {
0529: appendField(7, 3);
0530: return this ;
0531: }
0532:
0533: private void appendField(int type) {
0534: appendField(type, iMinPrintedDigits);
0535: }
0536:
0537: private void appendField(int type, int minPrinted) {
0538: FieldFormatter field = new FieldFormatter(minPrinted,
0539: iPrintZeroSetting, iMaxParsedDigits,
0540: iRejectSignedValues, type, iFieldFormatters, iPrefix,
0541: null);
0542: append0(field, field);
0543: iFieldFormatters[type] = field;
0544: iPrefix = null;
0545: }
0546:
0547: //-----------------------------------------------------------------------
0548: /**
0549: * Append a field suffix which applies only to the last appended field. If
0550: * the field is not printed, neither is the suffix.
0551: *
0552: * @param text text to print after field only if field is printed
0553: * @return this PeriodFormatterBuilder
0554: * @throws IllegalStateException if no field exists to append to
0555: * @see #appendPrefix
0556: */
0557: public PeriodFormatterBuilder appendSuffix(String text) {
0558: if (text == null) {
0559: throw new IllegalArgumentException();
0560: }
0561: return appendSuffix(new SimpleAffix(text));
0562: }
0563:
0564: /**
0565: * Append a field suffix which applies only to the last appended field. If
0566: * the field is not printed, neither is the suffix.
0567: * <p>
0568: * During parsing, the singular and plural versions are accepted whether or
0569: * not the actual value matches plurality.
0570: *
0571: * @param singularText text to print if field value is one
0572: * @param pluralText text to print if field value is not one
0573: * @return this PeriodFormatterBuilder
0574: * @throws IllegalStateException if no field exists to append to
0575: * @see #appendPrefix
0576: */
0577: public PeriodFormatterBuilder appendSuffix(String singularText,
0578: String pluralText) {
0579: if (singularText == null || pluralText == null) {
0580: throw new IllegalArgumentException();
0581: }
0582: return appendSuffix(new PluralAffix(singularText, pluralText));
0583: }
0584:
0585: /**
0586: * Append a field suffix which applies only to the last appended field. If
0587: * the field is not printed, neither is the suffix.
0588: *
0589: * @param suffix custom suffix
0590: * @return this PeriodFormatterBuilder
0591: * @throws IllegalStateException if no field exists to append to
0592: * @see #appendPrefix
0593: */
0594: private PeriodFormatterBuilder appendSuffix(PeriodFieldAffix suffix) {
0595: final Object originalPrinter;
0596: final Object originalParser;
0597: if (iElementPairs.size() > 0) {
0598: originalPrinter = iElementPairs
0599: .get(iElementPairs.size() - 2);
0600: originalParser = iElementPairs
0601: .get(iElementPairs.size() - 1);
0602: } else {
0603: originalPrinter = null;
0604: originalParser = null;
0605: }
0606:
0607: if (originalPrinter == null || originalParser == null
0608: || originalPrinter != originalParser
0609: || !(originalPrinter instanceof FieldFormatter)) {
0610: throw new IllegalStateException(
0611: "No field to apply suffix to");
0612: }
0613:
0614: clearPrefix();
0615: FieldFormatter newField = new FieldFormatter(
0616: (FieldFormatter) originalPrinter, suffix);
0617: iElementPairs.set(iElementPairs.size() - 2, newField);
0618: iElementPairs.set(iElementPairs.size() - 1, newField);
0619: iFieldFormatters[newField.getFieldType()] = newField;
0620:
0621: return this ;
0622: }
0623:
0624: //-----------------------------------------------------------------------
0625: /**
0626: * Append a separator, which is output if fields are printed both before
0627: * and after the separator.
0628: * <p>
0629: * For example, <code>builder.appendDays().appendSeparator(",").appendHours()</code>
0630: * will only output the comma if both the days and hours fields are output.
0631: * <p>
0632: * The text will be parsed case-insensitively.
0633: * <p>
0634: * Note: appending a separator discontinues any further work on the latest
0635: * appended field.
0636: *
0637: * @param text the text to use as a separator
0638: * @return this PeriodFormatterBuilder
0639: * @throws IllegalStateException if this separator follows a previous one
0640: */
0641: public PeriodFormatterBuilder appendSeparator(String text) {
0642: return appendSeparator(text, text, null, true, true);
0643: }
0644:
0645: /**
0646: * Append a separator, which is output only if fields are printed after the separator.
0647: * <p>
0648: * For example,
0649: * <code>builder.appendDays().appendSeparatorIfFieldsAfter(",").appendHours()</code>
0650: * will only output the comma if the hours fields is output.
0651: * <p>
0652: * The text will be parsed case-insensitively.
0653: * <p>
0654: * Note: appending a separator discontinues any further work on the latest
0655: * appended field.
0656: *
0657: * @param text the text to use as a separator
0658: * @return this PeriodFormatterBuilder
0659: * @throws IllegalStateException if this separator follows a previous one
0660: */
0661: public PeriodFormatterBuilder appendSeparatorIfFieldsAfter(
0662: String text) {
0663: return appendSeparator(text, text, null, false, true);
0664: }
0665:
0666: /**
0667: * Append a separator, which is output only if fields are printed before the separator.
0668: * <p>
0669: * For example,
0670: * <code>builder.appendDays().appendSeparatorIfFieldsBefore(",").appendHours()</code>
0671: * will only output the comma if the days fields is output.
0672: * <p>
0673: * The text will be parsed case-insensitively.
0674: * <p>
0675: * Note: appending a separator discontinues any further work on the latest
0676: * appended field.
0677: *
0678: * @param text the text to use as a separator
0679: * @return this PeriodFormatterBuilder
0680: * @throws IllegalStateException if this separator follows a previous one
0681: */
0682: public PeriodFormatterBuilder appendSeparatorIfFieldsBefore(
0683: String text) {
0684: return appendSeparator(text, text, null, true, false);
0685: }
0686:
0687: /**
0688: * Append a separator, which is output if fields are printed both before
0689: * and after the separator.
0690: * <p>
0691: * This method changes the separator depending on whether it is the last separator
0692: * to be output.
0693: * <p>
0694: * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
0695: * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
0696: * and '1' if just one field is output.
0697: * <p>
0698: * The text will be parsed case-insensitively.
0699: * <p>
0700: * Note: appending a separator discontinues any further work on the latest
0701: * appended field.
0702: *
0703: * @param text the text to use as a separator
0704: * @param finalText the text used used if this is the final separator to be printed
0705: * @return this PeriodFormatterBuilder
0706: * @throws IllegalStateException if this separator follows a previous one
0707: */
0708: public PeriodFormatterBuilder appendSeparator(String text,
0709: String finalText) {
0710: return appendSeparator(text, finalText, null, true, true);
0711: }
0712:
0713: /**
0714: * Append a separator, which is output if fields are printed both before
0715: * and after the separator.
0716: * <p>
0717: * This method changes the separator depending on whether it is the last separator
0718: * to be output.
0719: * <p>
0720: * For example, <code>builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes()</code>
0721: * will output '1,2&3' if all three fields are output, '1&2' if two fields are output
0722: * and '1' if just one field is output.
0723: * <p>
0724: * The text will be parsed case-insensitively.
0725: * <p>
0726: * Note: appending a separator discontinues any further work on the latest
0727: * appended field.
0728: *
0729: * @param text the text to use as a separator
0730: * @param finalText the text used used if this is the final separator to be printed
0731: * @param variants set of text values which are also acceptable when parsed
0732: * @return this PeriodFormatterBuilder
0733: * @throws IllegalStateException if this separator follows a previous one
0734: */
0735: public PeriodFormatterBuilder appendSeparator(String text,
0736: String finalText, String[] variants) {
0737: return appendSeparator(text, finalText, variants, true, true);
0738: }
0739:
0740: private PeriodFormatterBuilder appendSeparator(String text,
0741: String finalText, String[] variants, boolean useBefore,
0742: boolean useAfter) {
0743: if (text == null || finalText == null) {
0744: throw new IllegalArgumentException();
0745: }
0746:
0747: clearPrefix();
0748:
0749: // optimise zero formatter case
0750: List pairs = iElementPairs;
0751: if (pairs.size() == 0) {
0752: if (useAfter && useBefore == false) {
0753: Separator separator = new Separator(text, finalText,
0754: variants, Literal.EMPTY, Literal.EMPTY,
0755: useBefore, useAfter);
0756: append0(separator, separator);
0757: }
0758: return this ;
0759: }
0760:
0761: // find the last separator added
0762: int i;
0763: Separator lastSeparator = null;
0764: for (i = pairs.size(); --i >= 0;) {
0765: if (pairs.get(i) instanceof Separator) {
0766: lastSeparator = (Separator) pairs.get(i);
0767: pairs = pairs.subList(i + 1, pairs.size());
0768: break;
0769: }
0770: i--; // element pairs
0771: }
0772:
0773: // merge formatters
0774: if (lastSeparator != null && pairs.size() == 0) {
0775: throw new IllegalStateException(
0776: "Cannot have two adjacent separators");
0777: } else {
0778: Object[] comp = createComposite(pairs);
0779: pairs.clear();
0780: Separator separator = new Separator(text, finalText,
0781: variants, (PeriodPrinter) comp[0],
0782: (PeriodParser) comp[1], useBefore, useAfter);
0783: pairs.add(separator);
0784: pairs.add(separator);
0785: }
0786:
0787: return this ;
0788: }
0789:
0790: //-----------------------------------------------------------------------
0791: private void clearPrefix() throws IllegalStateException {
0792: if (iPrefix != null) {
0793: throw new IllegalStateException(
0794: "Prefix not followed by field");
0795: }
0796: iPrefix = null;
0797: }
0798:
0799: private PeriodFormatterBuilder append0(PeriodPrinter printer,
0800: PeriodParser parser) {
0801: iElementPairs.add(printer);
0802: iElementPairs.add(parser);
0803: iNotPrinter |= (printer == null);
0804: iNotParser |= (parser == null);
0805: return this ;
0806: }
0807:
0808: //-----------------------------------------------------------------------
0809: private static PeriodFormatter toFormatter(List elementPairs,
0810: boolean notPrinter, boolean notParser) {
0811: if (notPrinter && notParser) {
0812: throw new IllegalStateException(
0813: "Builder has created neither a printer nor a parser");
0814: }
0815: int size = elementPairs.size();
0816: if (size >= 2 && elementPairs.get(0) instanceof Separator) {
0817: Separator sep = (Separator) elementPairs.get(0);
0818: PeriodFormatter f = toFormatter(elementPairs.subList(2,
0819: size), notPrinter, notParser);
0820: sep = sep.finish(f.getPrinter(), f.getParser());
0821: return new PeriodFormatter(sep, sep);
0822: }
0823: Object[] comp = createComposite(elementPairs);
0824: if (notPrinter) {
0825: return new PeriodFormatter(null, (PeriodParser) comp[1]);
0826: } else if (notParser) {
0827: return new PeriodFormatter((PeriodPrinter) comp[0], null);
0828: } else {
0829: return new PeriodFormatter((PeriodPrinter) comp[0],
0830: (PeriodParser) comp[1]);
0831: }
0832: }
0833:
0834: private static Object[] createComposite(List elementPairs) {
0835: switch (elementPairs.size()) {
0836: case 0:
0837: return new Object[] { Literal.EMPTY, Literal.EMPTY };
0838: case 1:
0839: return new Object[] { elementPairs.get(0),
0840: elementPairs.get(1) };
0841: default:
0842: Composite comp = new Composite(elementPairs);
0843: return new Object[] { comp, comp };
0844: }
0845: }
0846:
0847: //-----------------------------------------------------------------------
0848: /**
0849: * Defines a formatted field's prefix or suffix text.
0850: * This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
0851: */
0852: static interface PeriodFieldAffix {
0853: int calculatePrintedLength(int value);
0854:
0855: void printTo(StringBuffer buf, int value);
0856:
0857: void printTo(Writer out, int value) throws IOException;
0858:
0859: /**
0860: * @return new position after parsing affix, or ~position of failure
0861: */
0862: int parse(String periodStr, int position);
0863:
0864: /**
0865: * @return position where affix starts, or original ~position if not found
0866: */
0867: int scan(String periodStr, int position);
0868: }
0869:
0870: //-----------------------------------------------------------------------
0871: /**
0872: * Implements an affix where the text does not vary by the amount.
0873: */
0874: static class SimpleAffix implements PeriodFieldAffix {
0875: private final String iText;
0876:
0877: SimpleAffix(String text) {
0878: iText = text;
0879: }
0880:
0881: public int calculatePrintedLength(int value) {
0882: return iText.length();
0883: }
0884:
0885: public void printTo(StringBuffer buf, int value) {
0886: buf.append(iText);
0887: }
0888:
0889: public void printTo(Writer out, int value) throws IOException {
0890: out.write(iText);
0891: }
0892:
0893: public int parse(String periodStr, int position) {
0894: String text = iText;
0895: int textLength = text.length();
0896: if (periodStr.regionMatches(true, position, text, 0,
0897: textLength)) {
0898: return position + textLength;
0899: }
0900: return ~position;
0901: }
0902:
0903: public int scan(String periodStr, final int position) {
0904: String text = iText;
0905: int textLength = text.length();
0906: int sourceLength = periodStr.length();
0907: search: for (int pos = position; pos < sourceLength; pos++) {
0908: if (periodStr.regionMatches(true, pos, text, 0,
0909: textLength)) {
0910: return pos;
0911: }
0912: // Only allow number characters to be skipped in search of suffix.
0913: switch (periodStr.charAt(pos)) {
0914: case '0':
0915: case '1':
0916: case '2':
0917: case '3':
0918: case '4':
0919: case '5':
0920: case '6':
0921: case '7':
0922: case '8':
0923: case '9':
0924: case '.':
0925: case ',':
0926: case '+':
0927: case '-':
0928: break;
0929: default:
0930: break search;
0931: }
0932: }
0933: return ~position;
0934: }
0935: }
0936:
0937: //-----------------------------------------------------------------------
0938: /**
0939: * Implements an affix where the text varies by the amount of the field.
0940: * Only singular (1) and plural (not 1) are supported.
0941: */
0942: static class PluralAffix implements PeriodFieldAffix {
0943: private final String iSingularText;
0944: private final String iPluralText;
0945:
0946: PluralAffix(String singularText, String pluralText) {
0947: iSingularText = singularText;
0948: iPluralText = pluralText;
0949: }
0950:
0951: public int calculatePrintedLength(int value) {
0952: return (value == 1 ? iSingularText : iPluralText).length();
0953: }
0954:
0955: public void printTo(StringBuffer buf, int value) {
0956: buf.append(value == 1 ? iSingularText : iPluralText);
0957: }
0958:
0959: public void printTo(Writer out, int value) throws IOException {
0960: out.write(value == 1 ? iSingularText : iPluralText);
0961: }
0962:
0963: public int parse(String periodStr, int position) {
0964: String text1 = iPluralText;
0965: String text2 = iSingularText;
0966:
0967: if (text1.length() < text2.length()) {
0968: // Swap in order to match longer one first.
0969: String temp = text1;
0970: text1 = text2;
0971: text2 = temp;
0972: }
0973:
0974: if (periodStr.regionMatches(true, position, text1, 0, text1
0975: .length())) {
0976: return position + text1.length();
0977: }
0978: if (periodStr.regionMatches(true, position, text2, 0, text2
0979: .length())) {
0980: return position + text2.length();
0981: }
0982:
0983: return ~position;
0984: }
0985:
0986: public int scan(String periodStr, final int position) {
0987: String text1 = iPluralText;
0988: String text2 = iSingularText;
0989:
0990: if (text1.length() < text2.length()) {
0991: // Swap in order to match longer one first.
0992: String temp = text1;
0993: text1 = text2;
0994: text2 = temp;
0995: }
0996:
0997: int textLength1 = text1.length();
0998: int textLength2 = text2.length();
0999:
1000: int sourceLength = periodStr.length();
1001: for (int pos = position; pos < sourceLength; pos++) {
1002: if (periodStr.regionMatches(true, pos, text1, 0,
1003: textLength1)) {
1004: return pos;
1005: }
1006: if (periodStr.regionMatches(true, pos, text2, 0,
1007: textLength2)) {
1008: return pos;
1009: }
1010: }
1011: return ~position;
1012: }
1013: }
1014:
1015: //-----------------------------------------------------------------------
1016: /**
1017: * Builds a composite affix by merging two other affix implementations.
1018: */
1019: static class CompositeAffix implements PeriodFieldAffix {
1020: private final PeriodFieldAffix iLeft;
1021: private final PeriodFieldAffix iRight;
1022:
1023: CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) {
1024: iLeft = left;
1025: iRight = right;
1026: }
1027:
1028: public int calculatePrintedLength(int value) {
1029: return iLeft.calculatePrintedLength(value)
1030: + iRight.calculatePrintedLength(value);
1031: }
1032:
1033: public void printTo(StringBuffer buf, int value) {
1034: iLeft.printTo(buf, value);
1035: iRight.printTo(buf, value);
1036: }
1037:
1038: public void printTo(Writer out, int value) throws IOException {
1039: iLeft.printTo(out, value);
1040: iRight.printTo(out, value);
1041: }
1042:
1043: public int parse(String periodStr, int position) {
1044: position = iLeft.parse(periodStr, position);
1045: if (position >= 0) {
1046: position = iRight.parse(periodStr, position);
1047: }
1048: return position;
1049: }
1050:
1051: public int scan(String periodStr, final int position) {
1052: int pos = iLeft.scan(periodStr, position);
1053: if (pos >= 0) {
1054: return iRight.scan(periodStr, pos);
1055: }
1056: return ~position;
1057: }
1058: }
1059:
1060: //-----------------------------------------------------------------------
1061: /**
1062: * Formats the numeric value of a field, potentially with prefix/suffix.
1063: */
1064: static class FieldFormatter implements PeriodPrinter, PeriodParser {
1065: private final int iMinPrintedDigits;
1066: private final int iPrintZeroSetting;
1067: private final int iMaxParsedDigits;
1068: private final boolean iRejectSignedValues;
1069:
1070: /** The index of the field type, 0=year, etc. */
1071: private final int iFieldType;
1072: /**
1073: * The array of the latest formatter added for each type.
1074: * This is shared between all the field formatters in a formatter.
1075: */
1076: private final FieldFormatter[] iFieldFormatters;
1077:
1078: private final PeriodFieldAffix iPrefix;
1079: private final PeriodFieldAffix iSuffix;
1080:
1081: FieldFormatter(int minPrintedDigits, int printZeroSetting,
1082: int maxParsedDigits, boolean rejectSignedValues,
1083: int fieldType, FieldFormatter[] fieldFormatters,
1084: PeriodFieldAffix prefix, PeriodFieldAffix suffix) {
1085: iMinPrintedDigits = minPrintedDigits;
1086: iPrintZeroSetting = printZeroSetting;
1087: iMaxParsedDigits = maxParsedDigits;
1088: iRejectSignedValues = rejectSignedValues;
1089: iFieldType = fieldType;
1090: iFieldFormatters = fieldFormatters;
1091: iPrefix = prefix;
1092: iSuffix = suffix;
1093: }
1094:
1095: FieldFormatter(FieldFormatter field, PeriodFieldAffix suffix) {
1096: iMinPrintedDigits = field.iMinPrintedDigits;
1097: iPrintZeroSetting = field.iPrintZeroSetting;
1098: iMaxParsedDigits = field.iMaxParsedDigits;
1099: iRejectSignedValues = field.iRejectSignedValues;
1100: iFieldType = field.iFieldType;
1101: iFieldFormatters = field.iFieldFormatters;
1102: iPrefix = field.iPrefix;
1103: if (field.iSuffix != null) {
1104: suffix = new CompositeAffix(field.iSuffix, suffix);
1105: }
1106: iSuffix = suffix;
1107: }
1108:
1109: public int countFieldsToPrint(ReadablePeriod period,
1110: int stopAt, Locale locale) {
1111: if (stopAt <= 0) {
1112: return 0;
1113: }
1114: if (iPrintZeroSetting == PRINT_ZERO_ALWAYS
1115: || getFieldValue(period) != Long.MAX_VALUE) {
1116: return 1;
1117: }
1118: return 0;
1119: }
1120:
1121: public int calculatePrintedLength(ReadablePeriod period,
1122: Locale locale) {
1123: long valueLong = getFieldValue(period);
1124: if (valueLong == Long.MAX_VALUE) {
1125: return 0;
1126: }
1127:
1128: int sum = Math.max(FormatUtils
1129: .calculateDigitCount(valueLong), iMinPrintedDigits);
1130: if (iFieldType >= SECONDS_MILLIS) {
1131: // valueLong contains the seconds and millis fields
1132: // the minimum output is 0.000, which is 4 digits
1133: sum = Math.max(sum, 4);
1134: // plus one for the decimal point
1135: sum++;
1136: if (iFieldType == SECONDS_OPTIONAL_MILLIS
1137: && (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND) == 0) {
1138: sum -= 4; // remove three digits and decimal point
1139: }
1140: // reset valueLong to refer to the seconds part for the prefic/suffix calculation
1141: valueLong = valueLong
1142: / DateTimeConstants.MILLIS_PER_SECOND;
1143: }
1144: int value = (int) valueLong;
1145:
1146: if (iPrefix != null) {
1147: sum += iPrefix.calculatePrintedLength(value);
1148: }
1149: if (iSuffix != null) {
1150: sum += iSuffix.calculatePrintedLength(value);
1151: }
1152:
1153: return sum;
1154: }
1155:
1156: public void printTo(StringBuffer buf, ReadablePeriod period,
1157: Locale locale) {
1158: long valueLong = getFieldValue(period);
1159: if (valueLong == Long.MAX_VALUE) {
1160: return;
1161: }
1162: int value = (int) valueLong;
1163: if (iFieldType >= SECONDS_MILLIS) {
1164: value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1165: }
1166:
1167: if (iPrefix != null) {
1168: iPrefix.printTo(buf, value);
1169: }
1170: int minDigits = iMinPrintedDigits;
1171: if (minDigits <= 1) {
1172: FormatUtils.appendUnpaddedInteger(buf, value);
1173: } else {
1174: FormatUtils.appendPaddedInteger(buf, value, minDigits);
1175: }
1176: if (iFieldType >= SECONDS_MILLIS) {
1177: int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1178: if (iFieldType == SECONDS_MILLIS || dp > 0) {
1179: buf.append('.');
1180: FormatUtils.appendPaddedInteger(buf, dp, 3);
1181: }
1182: }
1183: if (iSuffix != null) {
1184: iSuffix.printTo(buf, value);
1185: }
1186: }
1187:
1188: public void printTo(Writer out, ReadablePeriod period,
1189: Locale locale) throws IOException {
1190: long valueLong = getFieldValue(period);
1191: if (valueLong == Long.MAX_VALUE) {
1192: return;
1193: }
1194: int value = (int) valueLong;
1195: if (iFieldType >= SECONDS_MILLIS) {
1196: value = (int) (valueLong / DateTimeConstants.MILLIS_PER_SECOND);
1197: }
1198:
1199: if (iPrefix != null) {
1200: iPrefix.printTo(out, value);
1201: }
1202: int minDigits = iMinPrintedDigits;
1203: if (minDigits <= 1) {
1204: FormatUtils.writeUnpaddedInteger(out, value);
1205: } else {
1206: FormatUtils.writePaddedInteger(out, value, minDigits);
1207: }
1208: if (iFieldType >= SECONDS_MILLIS) {
1209: int dp = (int) (Math.abs(valueLong) % DateTimeConstants.MILLIS_PER_SECOND);
1210: if (iFieldType == SECONDS_MILLIS || dp > 0) {
1211: out.write('.');
1212: FormatUtils.writePaddedInteger(out, dp, 3);
1213: }
1214: }
1215: if (iSuffix != null) {
1216: iSuffix.printTo(out, value);
1217: }
1218: }
1219:
1220: public int parseInto(ReadWritablePeriod period, String text,
1221: int position, Locale locale) {
1222:
1223: boolean mustParse = (iPrintZeroSetting == PRINT_ZERO_ALWAYS);
1224:
1225: // Shortcut test.
1226: if (position >= text.length()) {
1227: return mustParse ? ~position : position;
1228: }
1229:
1230: if (iPrefix != null) {
1231: position = iPrefix.parse(text, position);
1232: if (position >= 0) {
1233: // If prefix is found, then the parse must finish.
1234: mustParse = true;
1235: } else {
1236: // Prefix not found, so bail.
1237: if (!mustParse) {
1238: // It's okay because parsing of this field is not
1239: // required. Don't return an error. Fields down the
1240: // chain can continue on, trying to parse.
1241: return ~position;
1242: }
1243: return position;
1244: }
1245: }
1246:
1247: int suffixPos = -1;
1248: if (iSuffix != null && !mustParse) {
1249: // Pre-scan the suffix, to help determine if this field must be
1250: // parsed.
1251: suffixPos = iSuffix.scan(text, position);
1252: if (suffixPos >= 0) {
1253: // If suffix is found, then parse must finish.
1254: mustParse = true;
1255: } else {
1256: // Suffix not found, so bail.
1257: if (!mustParse) {
1258: // It's okay because parsing of this field is not
1259: // required. Don't return an error. Fields down the
1260: // chain can continue on, trying to parse.
1261: return ~suffixPos;
1262: }
1263: return suffixPos;
1264: }
1265: }
1266:
1267: if (!mustParse
1268: && !isSupported(period.getPeriodType(), iFieldType)) {
1269: // If parsing is not required and the field is not supported,
1270: // exit gracefully so that another parser can continue on.
1271: return position;
1272: }
1273:
1274: int limit;
1275: if (suffixPos > 0) {
1276: limit = Math
1277: .min(iMaxParsedDigits, suffixPos - position);
1278: } else {
1279: limit = Math.min(iMaxParsedDigits, text.length()
1280: - position);
1281: }
1282:
1283: // validate input number
1284: int length = 0;
1285: int fractPos = -1;
1286: boolean hasDigits = false;
1287: while (length < limit) {
1288: char c = text.charAt(position + length);
1289: // leading sign
1290: if (length == 0 && (c == '-' || c == '+')
1291: && !iRejectSignedValues) {
1292: boolean negative = c == '-';
1293:
1294: // Next character must be a digit.
1295: if (length + 1 >= limit
1296: || (c = text.charAt(position + length + 1)) < '0'
1297: || c > '9') {
1298: break;
1299: }
1300:
1301: if (negative) {
1302: length++;
1303: } else {
1304: // Skip the '+' for parseInt to succeed.
1305: position++;
1306: }
1307: // Expand the limit to disregard the sign character.
1308: limit = Math.min(limit + 1, text.length()
1309: - position);
1310: continue;
1311: }
1312: // main number
1313: if (c >= '0' && c <= '9') {
1314: hasDigits = true;
1315: } else {
1316: if ((c == '.' || c == ',')
1317: && (iFieldType == SECONDS_MILLIS || iFieldType == SECONDS_OPTIONAL_MILLIS)) {
1318: if (fractPos >= 0) {
1319: // can't have two decimals
1320: break;
1321: }
1322: fractPos = position + length + 1;
1323: // Expand the limit to disregard the decimal point.
1324: limit = Math.min(limit + 1, text.length()
1325: - position);
1326: } else {
1327: break;
1328: }
1329: }
1330: length++;
1331: }
1332:
1333: if (!hasDigits) {
1334: return ~position;
1335: }
1336:
1337: if (suffixPos >= 0 && position + length != suffixPos) {
1338: // If there are additional non-digit characters before the
1339: // suffix is reached, then assume that the suffix found belongs
1340: // to a field not yet reached. Return original position so that
1341: // another parser can continue on.
1342: return position;
1343: }
1344:
1345: if (iFieldType != SECONDS_MILLIS
1346: && iFieldType != SECONDS_OPTIONAL_MILLIS) {
1347: // Handle common case.
1348: setFieldValue(period, iFieldType, parseInt(text,
1349: position, length));
1350: } else if (fractPos < 0) {
1351: setFieldValue(period, SECONDS, parseInt(text, position,
1352: length));
1353: setFieldValue(period, MILLIS, 0);
1354: } else {
1355: int wholeValue = parseInt(text, position, fractPos
1356: - position - 1);
1357: setFieldValue(period, SECONDS, wholeValue);
1358:
1359: int fractLen = position + length - fractPos;
1360: int fractValue;
1361: if (fractLen <= 0) {
1362: fractValue = 0;
1363: } else {
1364: if (fractLen >= 3) {
1365: fractValue = parseInt(text, fractPos, 3);
1366: } else {
1367: fractValue = parseInt(text, fractPos, fractLen);
1368: if (fractLen == 1) {
1369: fractValue *= 100;
1370: } else {
1371: fractValue *= 10;
1372: }
1373: }
1374: if (wholeValue < 0) {
1375: fractValue = -fractValue;
1376: }
1377: }
1378:
1379: setFieldValue(period, MILLIS, fractValue);
1380: }
1381:
1382: position += length;
1383:
1384: if (position >= 0 && iSuffix != null) {
1385: position = iSuffix.parse(text, position);
1386: }
1387:
1388: return position;
1389: }
1390:
1391: /**
1392: * @param text text to parse
1393: * @param position position in text
1394: * @param length exact count of characters to parse
1395: * @return parsed int value
1396: */
1397: private int parseInt(String text, int position, int length) {
1398: if (length >= 10) {
1399: // Since value may exceed max, use stock parser which checks for this.
1400: return Integer.parseInt(text.substring(position,
1401: position + length));
1402: }
1403: if (length <= 0) {
1404: return 0;
1405: }
1406: int value = text.charAt(position++);
1407: length--;
1408: boolean negative;
1409: if (value == '-') {
1410: if (--length < 0) {
1411: return 0;
1412: }
1413: negative = true;
1414: value = text.charAt(position++);
1415: } else {
1416: negative = false;
1417: }
1418: value -= '0';
1419: while (length-- > 0) {
1420: value = ((value << 3) + (value << 1))
1421: + text.charAt(position++) - '0';
1422: }
1423: return negative ? -value : value;
1424: }
1425:
1426: /**
1427: * @return Long.MAX_VALUE if nothing to print, otherwise value
1428: */
1429: long getFieldValue(ReadablePeriod period) {
1430: PeriodType type;
1431: if (iPrintZeroSetting == PRINT_ZERO_ALWAYS) {
1432: type = null; // Don't need to check if supported.
1433: } else {
1434: type = period.getPeriodType();
1435: }
1436: if (type != null && isSupported(type, iFieldType) == false) {
1437: return Long.MAX_VALUE;
1438: }
1439:
1440: long value;
1441:
1442: switch (iFieldType) {
1443: default:
1444: return Long.MAX_VALUE;
1445: case YEARS:
1446: value = period.get(DurationFieldType.years());
1447: break;
1448: case MONTHS:
1449: value = period.get(DurationFieldType.months());
1450: break;
1451: case WEEKS:
1452: value = period.get(DurationFieldType.weeks());
1453: break;
1454: case DAYS:
1455: value = period.get(DurationFieldType.days());
1456: break;
1457: case HOURS:
1458: value = period.get(DurationFieldType.hours());
1459: break;
1460: case MINUTES:
1461: value = period.get(DurationFieldType.minutes());
1462: break;
1463: case SECONDS:
1464: value = period.get(DurationFieldType.seconds());
1465: break;
1466: case MILLIS:
1467: value = period.get(DurationFieldType.millis());
1468: break;
1469: case SECONDS_MILLIS: // drop through
1470: case SECONDS_OPTIONAL_MILLIS:
1471: int seconds = period.get(DurationFieldType.seconds());
1472: int millis = period.get(DurationFieldType.millis());
1473: value = (seconds * (long) DateTimeConstants.MILLIS_PER_SECOND)
1474: + millis;
1475: break;
1476: }
1477:
1478: // determine if period is zero and this is the last field
1479: if (value == 0) {
1480: switch (iPrintZeroSetting) {
1481: case PRINT_ZERO_NEVER:
1482: return Long.MAX_VALUE;
1483: case PRINT_ZERO_RARELY_LAST:
1484: if (isZero(period)
1485: && iFieldFormatters[iFieldType] == this ) {
1486: for (int i = iFieldType + 1; i <= MAX_FIELD; i++) {
1487: if (isSupported(type, i)
1488: && iFieldFormatters[i] != null) {
1489: return Long.MAX_VALUE;
1490: }
1491: }
1492: } else {
1493: return Long.MAX_VALUE;
1494: }
1495: break;
1496: case PRINT_ZERO_RARELY_FIRST:
1497: if (isZero(period)
1498: && iFieldFormatters[iFieldType] == this ) {
1499: int i = Math.min(iFieldType, 8); // line split out for IBM JDK
1500: i--; // see bug 1660490
1501: for (; i >= 0 && i <= MAX_FIELD; i++) {
1502: if (isSupported(type, i)
1503: && iFieldFormatters[i] != null) {
1504: return Long.MAX_VALUE;
1505: }
1506: }
1507: } else {
1508: return Long.MAX_VALUE;
1509: }
1510: break;
1511: }
1512: }
1513:
1514: return value;
1515: }
1516:
1517: boolean isZero(ReadablePeriod period) {
1518: for (int i = 0, isize = period.size(); i < isize; i++) {
1519: if (period.getValue(i) != 0) {
1520: return false;
1521: }
1522: }
1523: return true;
1524: }
1525:
1526: boolean isSupported(PeriodType type, int field) {
1527: switch (field) {
1528: default:
1529: return false;
1530: case YEARS:
1531: return type.isSupported(DurationFieldType.years());
1532: case MONTHS:
1533: return type.isSupported(DurationFieldType.months());
1534: case WEEKS:
1535: return type.isSupported(DurationFieldType.weeks());
1536: case DAYS:
1537: return type.isSupported(DurationFieldType.days());
1538: case HOURS:
1539: return type.isSupported(DurationFieldType.hours());
1540: case MINUTES:
1541: return type.isSupported(DurationFieldType.minutes());
1542: case SECONDS:
1543: return type.isSupported(DurationFieldType.seconds());
1544: case MILLIS:
1545: return type.isSupported(DurationFieldType.millis());
1546: case SECONDS_MILLIS: // drop through
1547: case SECONDS_OPTIONAL_MILLIS:
1548: return type.isSupported(DurationFieldType.seconds())
1549: || type.isSupported(DurationFieldType.millis());
1550: }
1551: }
1552:
1553: void setFieldValue(ReadWritablePeriod period, int field,
1554: int value) {
1555: switch (field) {
1556: default:
1557: break;
1558: case YEARS:
1559: period.setYears(value);
1560: break;
1561: case MONTHS:
1562: period.setMonths(value);
1563: break;
1564: case WEEKS:
1565: period.setWeeks(value);
1566: break;
1567: case DAYS:
1568: period.setDays(value);
1569: break;
1570: case HOURS:
1571: period.setHours(value);
1572: break;
1573: case MINUTES:
1574: period.setMinutes(value);
1575: break;
1576: case SECONDS:
1577: period.setSeconds(value);
1578: break;
1579: case MILLIS:
1580: period.setMillis(value);
1581: break;
1582: }
1583: }
1584:
1585: int getFieldType() {
1586: return iFieldType;
1587: }
1588: }
1589:
1590: //-----------------------------------------------------------------------
1591: /**
1592: * Handles a simple literal piece of text.
1593: */
1594: static class Literal implements PeriodPrinter, PeriodParser {
1595: static final Literal EMPTY = new Literal("");
1596: private final String iText;
1597:
1598: Literal(String text) {
1599: iText = text;
1600: }
1601:
1602: public int countFieldsToPrint(ReadablePeriod period,
1603: int stopAt, Locale locale) {
1604: return 0;
1605: }
1606:
1607: public int calculatePrintedLength(ReadablePeriod period,
1608: Locale locale) {
1609: return iText.length();
1610: }
1611:
1612: public void printTo(StringBuffer buf, ReadablePeriod period,
1613: Locale locale) {
1614: buf.append(iText);
1615: }
1616:
1617: public void printTo(Writer out, ReadablePeriod period,
1618: Locale locale) throws IOException {
1619: out.write(iText);
1620: }
1621:
1622: public int parseInto(ReadWritablePeriod period,
1623: String periodStr, int position, Locale locale) {
1624: if (periodStr.regionMatches(true, position, iText, 0, iText
1625: .length())) {
1626: return position + iText.length();
1627: }
1628: return ~position;
1629: }
1630: }
1631:
1632: //-----------------------------------------------------------------------
1633: /**
1634: * Handles a separator, that splits the fields into multiple parts.
1635: * For example, the 'T' in the ISO8601 standard.
1636: */
1637: static class Separator implements PeriodPrinter, PeriodParser {
1638: private final String iText;
1639: private final String iFinalText;
1640: private final String[] iParsedForms;
1641:
1642: private final boolean iUseBefore;
1643: private final boolean iUseAfter;
1644:
1645: private PeriodPrinter iBeforePrinter;
1646: private PeriodPrinter iAfterPrinter;
1647: private PeriodParser iBeforeParser;
1648: private PeriodParser iAfterParser;
1649:
1650: Separator(String text, String finalText, String[] variants,
1651: PeriodPrinter beforePrinter, PeriodParser beforeParser,
1652: boolean useBefore, boolean useAfter) {
1653: iText = text;
1654: iFinalText = finalText;
1655:
1656: if ((finalText == null || text.equals(finalText))
1657: && (variants == null || variants.length == 0)) {
1658:
1659: iParsedForms = new String[] { text };
1660: } else {
1661: // Filter and reverse sort the parsed forms.
1662: TreeSet parsedSet = new TreeSet(
1663: String.CASE_INSENSITIVE_ORDER);
1664: parsedSet.add(text);
1665: parsedSet.add(finalText);
1666: if (variants != null) {
1667: for (int i = variants.length; --i >= 0;) {
1668: parsedSet.add(variants[i]);
1669: }
1670: }
1671: ArrayList parsedList = new ArrayList(parsedSet);
1672: Collections.reverse(parsedList);
1673: iParsedForms = (String[]) parsedList
1674: .toArray(new String[parsedList.size()]);
1675: }
1676:
1677: iBeforePrinter = beforePrinter;
1678: iBeforeParser = beforeParser;
1679: iUseBefore = useBefore;
1680: iUseAfter = useAfter;
1681: }
1682:
1683: public int countFieldsToPrint(ReadablePeriod period,
1684: int stopAt, Locale locale) {
1685: int sum = iBeforePrinter.countFieldsToPrint(period, stopAt,
1686: locale);
1687: if (sum < stopAt) {
1688: sum += iAfterPrinter.countFieldsToPrint(period, stopAt,
1689: locale);
1690: }
1691: return sum;
1692: }
1693:
1694: public int calculatePrintedLength(ReadablePeriod period,
1695: Locale locale) {
1696: PeriodPrinter before = iBeforePrinter;
1697: PeriodPrinter after = iAfterPrinter;
1698:
1699: int sum = before.calculatePrintedLength(period, locale)
1700: + after.calculatePrintedLength(period, locale);
1701:
1702: if (iUseBefore) {
1703: if (before.countFieldsToPrint(period, 1, locale) > 0) {
1704: if (iUseAfter) {
1705: int afterCount = after.countFieldsToPrint(
1706: period, 2, locale);
1707: if (afterCount > 0) {
1708: sum += (afterCount > 1 ? iText : iFinalText)
1709: .length();
1710: }
1711: } else {
1712: sum += iText.length();
1713: }
1714: }
1715: } else if (iUseAfter
1716: && after.countFieldsToPrint(period, 1, locale) > 0) {
1717: sum += iText.length();
1718: }
1719:
1720: return sum;
1721: }
1722:
1723: public void printTo(StringBuffer buf, ReadablePeriod period,
1724: Locale locale) {
1725: PeriodPrinter before = iBeforePrinter;
1726: PeriodPrinter after = iAfterPrinter;
1727:
1728: before.printTo(buf, period, locale);
1729: if (iUseBefore) {
1730: if (before.countFieldsToPrint(period, 1, locale) > 0) {
1731: if (iUseAfter) {
1732: int afterCount = after.countFieldsToPrint(
1733: period, 2, locale);
1734: if (afterCount > 0) {
1735: buf.append(afterCount > 1 ? iText
1736: : iFinalText);
1737: }
1738: } else {
1739: buf.append(iText);
1740: }
1741: }
1742: } else if (iUseAfter
1743: && after.countFieldsToPrint(period, 1, locale) > 0) {
1744: buf.append(iText);
1745: }
1746: after.printTo(buf, period, locale);
1747: }
1748:
1749: public void printTo(Writer out, ReadablePeriod period,
1750: Locale locale) throws IOException {
1751: PeriodPrinter before = iBeforePrinter;
1752: PeriodPrinter after = iAfterPrinter;
1753:
1754: before.printTo(out, period, locale);
1755: if (iUseBefore) {
1756: if (before.countFieldsToPrint(period, 1, locale) > 0) {
1757: if (iUseAfter) {
1758: int afterCount = after.countFieldsToPrint(
1759: period, 2, locale);
1760: if (afterCount > 0) {
1761: out.write(afterCount > 1 ? iText
1762: : iFinalText);
1763: }
1764: } else {
1765: out.write(iText);
1766: }
1767: }
1768: } else if (iUseAfter
1769: && after.countFieldsToPrint(period, 1, locale) > 0) {
1770: out.write(iText);
1771: }
1772: after.printTo(out, period, locale);
1773: }
1774:
1775: public int parseInto(ReadWritablePeriod period,
1776: String periodStr, int position, Locale locale) {
1777: int oldPos = position;
1778: position = iBeforeParser.parseInto(period, periodStr,
1779: position, locale);
1780:
1781: if (position < 0) {
1782: return position;
1783: }
1784:
1785: boolean found = false;
1786: if (position > oldPos) {
1787: // Consume this separator.
1788: String[] parsedForms = iParsedForms;
1789: int length = parsedForms.length;
1790: for (int i = 0; i < length; i++) {
1791: String parsedForm = parsedForms[i];
1792: if ((parsedForm == null || parsedForm.length() == 0)
1793: || periodStr.regionMatches(true, position,
1794: parsedForm, 0, parsedForm.length())) {
1795:
1796: position += parsedForm.length();
1797: found = true;
1798: break;
1799: }
1800: }
1801: }
1802:
1803: oldPos = position;
1804: position = iAfterParser.parseInto(period, periodStr,
1805: position, locale);
1806:
1807: if (position < 0) {
1808: return position;
1809: }
1810:
1811: if (found && position == oldPos) {
1812: // Separator should not have been supplied.
1813: return ~oldPos;
1814: }
1815:
1816: if (position > oldPos && !found && !iUseBefore) {
1817: // Separator was required.
1818: return ~oldPos;
1819: }
1820:
1821: return position;
1822: }
1823:
1824: Separator finish(PeriodPrinter afterPrinter,
1825: PeriodParser afterParser) {
1826: iAfterPrinter = afterPrinter;
1827: iAfterParser = afterParser;
1828: return this ;
1829: }
1830: }
1831:
1832: //-----------------------------------------------------------------------
1833: /**
1834: * Composite implementation that merges other fields to create a full pattern.
1835: */
1836: static class Composite implements PeriodPrinter, PeriodParser {
1837:
1838: private final PeriodPrinter[] iPrinters;
1839: private final PeriodParser[] iParsers;
1840:
1841: Composite(List elementPairs) {
1842: List printerList = new ArrayList();
1843: List parserList = new ArrayList();
1844:
1845: decompose(elementPairs, printerList, parserList);
1846:
1847: if (printerList.size() <= 0) {
1848: iPrinters = null;
1849: } else {
1850: iPrinters = (PeriodPrinter[]) printerList
1851: .toArray(new PeriodPrinter[printerList.size()]);
1852: }
1853:
1854: if (parserList.size() <= 0) {
1855: iParsers = null;
1856: } else {
1857: iParsers = (PeriodParser[]) parserList
1858: .toArray(new PeriodParser[parserList.size()]);
1859: }
1860: }
1861:
1862: public int countFieldsToPrint(ReadablePeriod period,
1863: int stopAt, Locale locale) {
1864: int sum = 0;
1865: PeriodPrinter[] printers = iPrinters;
1866: for (int i = printers.length; sum < stopAt && --i >= 0;) {
1867: sum += printers[i].countFieldsToPrint(period,
1868: Integer.MAX_VALUE, locale);
1869: }
1870: return sum;
1871: }
1872:
1873: public int calculatePrintedLength(ReadablePeriod period,
1874: Locale locale) {
1875: int sum = 0;
1876: PeriodPrinter[] printers = iPrinters;
1877: for (int i = printers.length; --i >= 0;) {
1878: sum += printers[i].calculatePrintedLength(period,
1879: locale);
1880: }
1881: return sum;
1882: }
1883:
1884: public void printTo(StringBuffer buf, ReadablePeriod period,
1885: Locale locale) {
1886: PeriodPrinter[] printers = iPrinters;
1887: int len = printers.length;
1888: for (int i = 0; i < len; i++) {
1889: printers[i].printTo(buf, period, locale);
1890: }
1891: }
1892:
1893: public void printTo(Writer out, ReadablePeriod period,
1894: Locale locale) throws IOException {
1895: PeriodPrinter[] printers = iPrinters;
1896: int len = printers.length;
1897: for (int i = 0; i < len; i++) {
1898: printers[i].printTo(out, period, locale);
1899: }
1900: }
1901:
1902: public int parseInto(ReadWritablePeriod period,
1903: String periodStr, int position, Locale locale) {
1904: PeriodParser[] parsers = iParsers;
1905: if (parsers == null) {
1906: throw new UnsupportedOperationException();
1907: }
1908:
1909: int len = parsers.length;
1910: for (int i = 0; i < len && position >= 0; i++) {
1911: position = parsers[i].parseInto(period, periodStr,
1912: position, locale);
1913: }
1914: return position;
1915: }
1916:
1917: private void decompose(List elementPairs, List printerList,
1918: List parserList) {
1919: int size = elementPairs.size();
1920: for (int i = 0; i < size; i += 2) {
1921: Object element = elementPairs.get(i);
1922: if (element instanceof PeriodPrinter) {
1923: if (element instanceof Composite) {
1924: addArrayToList(printerList,
1925: ((Composite) element).iPrinters);
1926: } else {
1927: printerList.add(element);
1928: }
1929: }
1930:
1931: element = elementPairs.get(i + 1);
1932: if (element instanceof PeriodParser) {
1933: if (element instanceof Composite) {
1934: addArrayToList(parserList,
1935: ((Composite) element).iParsers);
1936: } else {
1937: parserList.add(element);
1938: }
1939: }
1940: }
1941: }
1942:
1943: private void addArrayToList(List list, Object[] array) {
1944: if (array != null) {
1945: for (int i = 0; i < array.length; i++) {
1946: list.add(array[i]);
1947: }
1948: }
1949: }
1950: }
1951:
1952: }
|