001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.datetime.formatter;
004:
005: import jodd.datetime.DateTimeStamp;
006: import jodd.datetime.JDateTime;
007:
008: /**
009: * Abstract formatter for easier {@link JdtFormatter} implementations.
010: * <p>
011: * For setting date and time, default formatter parses input String against
012: * specified format. It extracts parts of input string upon patterns
013: * and then each part is converted to a number for a date/time information.
014: * It doesn't ignore any non-number charater. If conversion fails,
015: * <code>null</code> is returned.
016: *
017: * <p>
018: * Getting date time is also user firendly. Specified format may not only
019: * contains patterns but also any text. To remove errors in decoding when
020: * text may be recognized as one of patterns, format text may be quoted
021: * with the special escape sign. Double quote in the text will be decoded
022: * as a single quote, of course.
023: * <p>
024: *
025: * It is not necessary to have parsers for all patterns.
026: */
027: public abstract class AbstractFormatter implements JdtFormatter {
028:
029: /**
030: * Availiable patterns list. Used by {@link #findPattern(char[], int)}
031: * when parsing date time format. Each formatter will have its own set of
032: * patterns, in strictly defined order.
033: */
034: protected char[][] patterns;
035:
036: /**
037: * Escape character.
038: */
039: protected char ESCAPE_CHAR = '\'';
040:
041: /**
042: * Converts String array of patterns to char arrays.
043: */
044: protected void preparePatterns(String[] spat) {
045: patterns = new char[spat.length][];
046: for (int i = 0; i < spat.length; i++) {
047: patterns[i] = spat[i].toCharArray();
048: }
049: }
050:
051: /**
052: * Finds the longest pattern in provided format starting from specified possition.
053: * All availiable patterns are stored in {@link #patterns}.
054: *
055: * @param format date time format to examine
056: * @param i starting index
057: *
058: * @return 0-based index of founded pattern, or <code>-1</code> if pattern not found
059: */
060: protected int findPattern(char[] format, int i) {
061: int frmtc_len = format.length;
062: boolean match;
063: int n, lastn = -1;
064: int maxLen = 0;
065: for (n = 0; n < patterns.length; n++) {
066: char[] curr = patterns[n]; // current pattern from the pattern list
067: if (i > frmtc_len - curr.length) {
068: continue;
069: }
070: match = true;
071: int delta = 0;
072: while (delta < curr.length) { // match given pattern
073: if (curr[delta] != format[i + delta]) {
074: match = false; // no match, goto next
075: break;
076: }
077: delta++;
078: }
079: if (match == true) { // match
080: if (patterns[n].length > maxLen) { // find longest match
081: lastn = n;
082: maxLen = patterns[n].length;
083: }
084: }
085: }
086: return lastn;
087: }
088:
089: // ---------------------------------------------------------------- convert
090:
091: /**
092: * Creates a date-time string for founded pattern. Founded patterns
093: * is identified by its {@link #patterns} index.
094: *
095: * @param patternIndex index of founded pattern
096: * @param jdt date time information
097: */
098: protected abstract String convertPattern(int patternIndex,
099: JDateTime jdt);
100:
101: /**
102: * @see jodd.datetime.formatter.JdtFormatter#convert(jodd.datetime.JDateTime, String)
103: */
104: public String convert(JDateTime jdt, String format) {
105: char[] fmtc = format.toCharArray();
106: int fmtc_len = fmtc.length;
107: StringBuilder result = new StringBuilder(fmtc_len);
108:
109: int i = 0;
110: while (i < fmtc_len) {
111: if (fmtc[i] == ESCAPE_CHAR) { // quote founded
112: int end = i + 1;
113: while (end < fmtc_len) {
114: if (fmtc[end] == ESCAPE_CHAR) { // second quote founded
115: if (end + 1 < fmtc_len) {
116: end++;
117: if (fmtc[end] == ESCAPE_CHAR) { // skip double quotes
118: result.append(ESCAPE_CHAR); // and continue
119: } else {
120: break;
121: }
122: }
123: } else {
124: result.append(fmtc[end]);
125: }
126: end++;
127: }
128: i = end;
129: continue; // end of quoted string, continue the main loop
130: }
131:
132: int n = findPattern(fmtc, i);
133: if (n != -1) { // pattern founded
134: result.append(convertPattern(n, jdt));
135: i += patterns[n].length;
136: } else {
137: result.append(fmtc[i]);
138: i++;
139: }
140: }
141: return result.toString();
142: }
143:
144: // ---------------------------------------------------------------- parse
145:
146: /**
147: * Parses value for matched pattern. Founded patterns
148: * is identified by its {@link #patterns} index.
149: * Note that value may represent both integer and decimals.
150: * May throw {@link NumberFormatException}.
151: *
152: * @param patternIndex index of founded pattern
153: * @param value value to parse
154: * @param destination destination to modify
155: */
156: protected abstract void parseValue(int patternIndex, String value,
157: DateTimeStamp destination);
158:
159: /**
160: * @see jodd.datetime.formatter.JdtFormatter#parse(String, String)
161: */
162: public DateTimeStamp parse(String string, String format) {
163: char[] sc = string.toCharArray();
164: char[] fc = format.toCharArray();
165:
166: int i = 0, j = 0;
167: int slen = string.length();
168: int tlen = format.length();
169:
170: DateTimeStamp time = new DateTimeStamp();
171:
172: while (true) {
173: int n = findPattern(fc, i);
174: if (n != -1) { // pattern founded
175: i += patterns[n].length;
176: StringBuilder w = new StringBuilder();
177: char next = 0xFFFF;
178: if (i < tlen) {
179: next = fc[i]; // next = delimeter
180: }
181: while ((j < slen) && (sc[j] != next)) {
182: w.append(sc[j]);
183: j++;
184: }
185: try {
186: parseValue(n, w.toString(), time);
187: } catch (NumberFormatException nfe) {
188: return null;
189: }
190: } else {
191: if (fc[i] == sc[j]) {
192: j++;
193: }
194: i++;
195: }
196: if ((i == tlen) || (j == slen)) {
197: break;
198: }
199: }
200: return time;
201: }
202: }
|