Open Source Repository

Home /jodd/jodd-3.3.2 | Repository Home


jodd/datetime/format/AbstractFormatter.java
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.datetime.format;

import jodd.datetime.DateTimeStamp;
import jodd.datetime.JDateTime;

/**
 * Abstract formatter for easier {@link JdtFormatter} implementations.
 <p>
 * For setting date and time, default formatter parses input String against
 * specified format. It extracts parts of input string upon patterns
 * and then each part is converted to a number for a date/time information.
 * It doesn't ignore any non-number character. If conversion fails,
 <code>null</code> is returned.
 *
 <p>
 * Getting date time is also user friendly. Specified format may not only
 * contains patterns but also any text. To remove errors in decoding when
 * text may be recognize as one of patterns, format text may be quoted
 * with the special escape sign. Double quote in the text will be decoded
 * as a single quote, of course. 
 <p>
 *
 * It is not necessary to have parsers for all patterns.
 */
public abstract class AbstractFormatter implements JdtFormatter {

  /**
   * Available patterns list. Used by {@link #findPattern(char[], int)}
   * when parsing date time format. Each formatter will have its own set of
   * patterns, in strictly defined order.
   */
  protected char[][] patterns;

  /**
   * Escape character.
   */
  protected char escapeChar = '\'';


  /**
   * Converts String array of patterns to char arrays. 
   */
  protected void preparePatterns(String[] spat) {
    patterns = new char[spat.length][];
    for (int i = 0; i < spat.length; i++) {
      patterns[i= spat[i].toCharArray();
    }
  }

  /**
   * Finds the longest pattern in provided format starting from specified position.
   * All available patterns are stored in {@link #patterns}.
   *
   @param format  date time format to examine
   @param i         starting index
   *
   @return  0-based index of founded pattern, or <code>-1</code> if pattern not found
   */
  protected int findPattern(char[] format, int i) {
    int frmtc_len = format.length;
    boolean match;
    int n, lastn = -1;
    int maxLen = 0;
    for (n = 0; n < patterns.length; n++) {
      char[] curr = patterns[n];          // current pattern from the pattern list
      if (i > frmtc_len - curr.length) {
        continue;
      }
      match = true;
      int delta = 0;
      while (delta < curr.length) {          // match given pattern
        if (curr[delta!= format[i + delta]) {
          match = false;              // no match, go to next
          break;
        }
        delta++;
      }
      if (match == true) {                // match
        if (patterns[n].length > maxLen) {    // find longest match
          lastn = n;
          maxLen = patterns[n].length;
        }
      }
    }
    return lastn;
  }

  // ---------------------------------------------------------------- convert

  /**
   * Creates a date-time string for founded pattern. Founded patterns
   * is identified by its {@link #patterns} index.
   *
   @param patternIndex      index of founded pattern
   @param jdt               date time information
   */
  protected abstract String convertPattern(int patternIndex, JDateTime jdt);

  /**
   * {@inheritDoc}
   @see JdtFormatter#convert(JDateTime, String)
   */
  public String convert(JDateTime jdt, String format) {
    char[] fmtc = format.toCharArray();
    int fmtc_len = fmtc.length;
    StringBuilder result = new StringBuilder(fmtc_len);

    int i = 0;
    while (i < fmtc_len) {
      if (fmtc[i== escapeChar) {            // quote founded
        int end = i + 1;
        while (end < fmtc_len) {
          if (fmtc[end== escapeChar) {        // second quote founded
            if (end + < fmtc_len) {
              end++;
              if (fmtc[end== escapeChar) {  // skip double quotes
                result.append(escapeChar);  // and continue
              else {
                break;
              }
            }
          else {
            result.append(fmtc[end]);
          }
          end++;
        }
        i = end;
        continue;      // end of quoted string, continue the main loop
      }

      int n = findPattern(fmtc, i);
      if (n != -1) {      // pattern founded
        result.append(convertPattern(n, jdt));
        i += patterns[n].length;
      else {
        result.append(fmtc[i]);
        i++;
      }
    }
    return result.toString();
  }

  // ---------------------------------------------------------------- parse

  /**
   * Parses value for matched pattern. Founded patterns
   * is identified by its {@link #patterns} index.
   * Note that value may represent both integer and decimals.
   * May throw {@link NumberFormatException}.
   *
   @param patternIndex      index of founded pattern
   @param value             value to parse, no spaces or tabs
   @param destination       destination to modify
   */
  protected abstract void parseValue(int patternIndex, String value, DateTimeStamp destination);

  /**
   * {@inheritDoc}
   @see JdtFormatter#parse(String, String)
   */
  public DateTimeStamp parse(String value, String format) {
    char[] sc = value.toCharArray();
    char[] fc = format.toCharArray();

    int i = 0, j = 0;
    int slen = value.length();
    int tlen = format.length();

    DateTimeStamp time = new DateTimeStamp();
    StringBuilder w = new StringBuilder();
    while (true) {
      int n = findPattern(fc, i);
      if (n != -1) {          // pattern founded
        i += patterns[n].length;
        w.setLength(0);
        char next = 0xFFFF;
        if (i < tlen) {
          next = fc[i];      // next = delimiter
        }
        while ((j < slen&& (sc[j!= next)) {
          char scj = sc[j];
          if ((scj != ' '&& (scj != '\t')) {    // ignore surrounding whitespaces
            w.append(sc[j]);
          }
          j++;
        }
        parseValue(n, w.toString(), time);
      else  {
        if (fc[i== sc[j]) {
          j++;
        }
        i++;
      }
      if ((i == tlen|| (j == slen)) {
        break;
      }
    }
    return time;
  }


  // ---------------------------------------------------------------- util

  /**
   * Prints values 00 - 99.
   */
  protected String print2(int value) {
    if (value < 0) {
      throw new IllegalArgumentException("Value must be positive: " + value);
    else if (value < 10) {
      return '0' + Integer.toString(value);
    else if (value < 100) {
      return Integer.toString(value);
    }
    throw new IllegalArgumentException("Value must be less then 100: " + value);
  }

  /**
   * Prints values 00 - 999.
   */
  protected String print3(int value) {
    if (value < 0) {
      throw new IllegalArgumentException("Value must be positive: " + value);
    else if (value < 10) {
      return "00" + Integer.toString(value);
    else if (value < 100) {
      return '0' + Integer.toString(value);
    else if (value < 1000) {
      return Integer.toString(value);
    }
    throw new IllegalArgumentException("Value must be less then 1000: " + value);
  }
}