jodd.datetime.format.AbstractFormatter Maven / Gradle / Ivy
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.datetime.format;
import jodd.datetime.DateTimeStamp;
import jodd.datetime.JDateTime;
import jodd.util.CharUtil;
/**
* Abstract formatter for easier {@link JdtFormatter} implementations.
*
* 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,
* null
is returned.
*
*
* 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.
*
*
* 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 -1
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) { // 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 + 1 < 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[] valueChars = value.toCharArray();
char[] formatChars = format.toCharArray();
int i = 0, j = 0;
int valueLen = valueChars.length;
int formatLen = formatChars.length;
// detect if separators are used
boolean useSeparators = true;
if (valueLen == formatLen) {
useSeparators = false;
for (char valueChar : valueChars) {
if (!CharUtil.isDigit(valueChar)) {
useSeparators = true;
break;
}
}
}
DateTimeStamp time = new DateTimeStamp();
StringBuilder sb = new StringBuilder();
while (true) {
int n = findPattern(formatChars, i);
if (n != -1) { // pattern founded
int patternLen = patterns[n].length;
i += patternLen;
sb.setLength(0);
if (!useSeparators) {
for (int k = 0; k < patternLen; k++) {
sb.append(valueChars[j++]);
}
} else {
char next = 0xFFFF;
if (i < formatLen) {
next = formatChars[i]; // next = delimiter
}
while ((j < valueLen) && (valueChars[j] != next)) {
char scj = valueChars[j];
if ((scj != ' ') && (scj != '\t')) { // ignore surrounding whitespaces
sb.append(valueChars[j]);
}
j++;
}
}
parseValue(n, sb.toString(), time);
} else {
if (!useSeparators) {
throw new IllegalArgumentException("Invalid value: " + value);
}
if (formatChars[i] == valueChars[j]) {
j++;
}
i++;
}
if ((i == formatLen) || (j == valueLen)) {
break;
}
}
return time;
}
// ---------------------------------------------------------------- util
/**
* Prints values 00 - 99.
*/
protected String print2(int value) {
if (value < 0) {
throw new IllegalArgumentException("Value must be positive: " + value);
}
if (value < 10) {
return '0' + Integer.toString(value);
}
if (value < 100) {
return Integer.toString(value);
}
throw new IllegalArgumentException("Value too big: " + value);
}
/**
* Prints values 00 - 999.
*/
protected String print3(int value) {
if (value < 0) {
throw new IllegalArgumentException("Value must be positive: " + value);
}
if (value < 10) {
return "00" + Integer.toString(value);
}
if (value < 100) {
return '0' + Integer.toString(value);
}
if (value < 1000) {
return Integer.toString(value);
}
throw new IllegalArgumentException("Value too big: " + value);
}
/**
* Prints 4 digits and optional minus sign.
*/
protected String printPad4(int value) {
char[] result = new char[4];
int count = 0;
if (value < 0) {
result[count++] = '-';
value = -value;
}
String str = Integer.toString(value);
if (value < 10) {
result[count++] = '0';
result[count++] = '0';
result[count++] = '0';
result[count++] = str.charAt(0);
} else if (value < 100) {
result[count++] = '0';
result[count++] = '0';
result[count++] = str.charAt(0);
result[count++] = str.charAt(1);
} else if (value < 1000) {
result[count++] = '0';
result[count++] = str.charAt(0);
result[count++] = str.charAt(1);
result[count++] = str.charAt(2);
} else {
if (count > 0) {
return '-' + str;
}
return str;
}
return new String(result, 0, count);
}
}