Author: oheger Date: Tue Oct 30 14:35:11 2007 New Revision: 590474 URL: http://svn.apache.org/viewvc?rev=590474&view=rev Log: CONFIGURATION-261: Parsing and formatting date values for PropertyListConfiguration is now done manually and works on Java 1.3, too
Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.java commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.jj commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListParser.java Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java?rev=590474&r1=590473&r2=590474&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java (original) +++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java Tue Oct 30 14:35:11 2007 @@ -23,12 +23,12 @@ import java.io.Writer; import java.net.URL; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Date; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.util.TimeZone; import org.apache.commons.codec.binary.Hex; import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration; @@ -82,15 +82,51 @@ */ public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration { - /** The format used for the date objects in the plist files. */ - static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + /** Constant for the separator parser for the date part. */ + private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser( + "-"); + + /** Constant for the separator parser for the time part. */ + private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser( + ":"); + + /** Constant for the separator parser for blanks between the parts. */ + private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser( + " "); + + /** An array with the component parsers for dealing with dates. */ + private static final DateComponentParser[] DATE_PARSERS = + {new DateSeparatorParser("<*D"), new DateFieldParser(Calendar.YEAR, 4), + DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.MONTH, 2, 1), + DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.DATE, 2), + BLANK_SEPARATOR_PARSER, + new DateFieldParser(Calendar.HOUR_OF_DAY, 2), + TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.MINUTE, 2), + TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.SECOND, 2), + BLANK_SEPARATOR_PARSER, new DateTimeZoneParser(), + new DateSeparatorParser(">")}; + + /** Constant for the ID prefix for GMT time zones. */ + private static final String TIME_ZONE_PREFIX = "GMT"; /** The serial version UID. */ private static final long serialVersionUID = 3227248503779092127L; + /** Constant for the milliseconds of a minute.*/ + private static final int MILLIS_PER_MINUTE = 1000 * 60; + + /** Constant for the minutes per hour.*/ + private static final int MINUTES_PER_HOUR = 60; + /** Size of the indentation for the generated file. */ private static final int INDENT_SIZE = 4; + /** Constant for the length of a time zone.*/ + private static final int TIME_ZONE_LENGTH = 5; + + /** Constant for the padding character in the date format.*/ + private static final char PAD_CHAR = '0'; + /** * Creates an empty PropertyListConfiguration object which can be * used to synthesize a new plist file by adding values and @@ -332,7 +368,7 @@ } else if (value instanceof Date) { - out.print("<*D" + DATE_FORMAT.format((Date) value) + ">"); + out.print(formatDate((Date) value)); } else if (value != null) { @@ -382,5 +418,265 @@ } return s; + } + + /** + * Parses a date in a format like + * <code><*D2002-03-22 11:30:00 +0100></code>. + * + * @param s the string with the date to be parsed + * @return the parsed date + * @throws ParseException if an error occurred while parsing the string + */ + static Date parseDate(String s) throws ParseException + { + Calendar cal = Calendar.getInstance(); + cal.clear(); + int index = 0; + + for (int i = 0; i < DATE_PARSERS.length; i++) + { + index += DATE_PARSERS[i].parseComponent(s, index, cal); + } + + return cal.getTime(); + } + + /** + * Returns a string representation for the date specified by the given + * calendar. + * + * @param cal the calendar with the initialized date + * @return a string for this date + */ + static String formatDate(Calendar cal) + { + StringBuffer buf = new StringBuffer(); + + for (int i = 0; i < DATE_PARSERS.length; i++) + { + DATE_PARSERS[i].formatComponent(buf, cal); + } + + return buf.toString(); + } + + /** + * Returns a string representation for the specified date. + * + * @param date the date + * @return a string for this date + */ + static String formatDate(Date date) + { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return formatDate(cal); + } + + /** + * A helper class for parsing and formatting date literals. Usually we would + * use <code>SimpleDateFormat</code> for this purpose, but in Java 1.3 the + * functionality of this class is limited. So we have a hierarchy of parser + * classes instead that deal with the different components of a date + * literal. + */ + private abstract static class DateComponentParser + { + /** + * Parses a component from the given input string. + * + * @param s the string to be parsed + * @param index the current parsing position + * @param cal the calendar where to store the result + * @return the length of the processed component + * @throws ParseException if the component cannot be extracted + */ + public abstract int parseComponent(String s, int index, Calendar cal) + throws ParseException; + + /** + * Formats a date component. This method is used for converting a date + * in its internal representation into a string literal. + * + * @param buf the target buffer + * @param cal the calendar with the current date + */ + public abstract void formatComponent(StringBuffer buf, Calendar cal); + + /** + * Checks whether the given string has at least <code>length</code> + * characters starting from the given parsing position. If this is not + * the case, an exception will be thrown. + * + * @param s the string to be tested + * @param index the current index + * @param length the minimum length after the index + * @throws ParseException if the string is too short + */ + protected void checkLength(String s, int index, int length) + throws ParseException + { + int len = (s == null) ? 0 : s.length(); + if (index + length > len) + { + throw new ParseException("Input string too short: " + s + + ", index: " + index); + } + } + + /** + * Adds a number to the given string buffer and adds leading '0' + * characters until the given length is reached. + * + * @param buf the target buffer + * @param num the number to add + * @param length the required length + */ + protected void padNum(StringBuffer buf, int num, int length) + { + buf.append(StringUtils.leftPad(String.valueOf(num), length, + PAD_CHAR)); + } + } + + /** + * A specialized date component parser implementation that deals with + * numeric calendar fields. The class is able to extract fields from a + * string literal and to format a literal from a calendar. + */ + private static class DateFieldParser extends DateComponentParser + { + /** Stores the calendar field to be processed. */ + private int calendarField; + + /** Stores the length of this field. */ + private int length; + + /** An optional offset to add to the calendar field. */ + private int offset; + + /** + * Creates a new instance of <code>DateFieldParser</code>. + * + * @param calFld the calendar field code + * @param len the length of this field + */ + public DateFieldParser(int calFld, int len) + { + this(calFld, len, 0); + } + + /** + * Creates a new instance of <code>DateFieldParser</code> and fully + * initializes it. + * + * @param calFld the calendar field code + * @param len the length of this field + * @param ofs an offset to add to the calendar field + */ + public DateFieldParser(int calFld, int len, int ofs) + { + calendarField = calFld; + length = len; + offset = ofs; + } + + public void formatComponent(StringBuffer buf, Calendar cal) + { + padNum(buf, cal.get(calendarField) + offset, length); + } + + public int parseComponent(String s, int index, Calendar cal) + throws ParseException + { + checkLength(s, index, length); + try + { + cal.set(calendarField, Integer.parseInt(s.substring(index, + index + length)) + - offset); + return length; + } + catch (NumberFormatException nfex) + { + throw new ParseException("Invalid number: " + s + ", index " + + index); + } + } + } + + /** + * A specialized date component parser implementation that deals with + * separator characters. + */ + private static class DateSeparatorParser extends DateComponentParser + { + /** Stores the separator. */ + private String separator; + + /** + * Creates a new instance of <code>DateSeparatorParser</code> and sets + * the separator string. + * + * @param sep the separator string + */ + public DateSeparatorParser(String sep) + { + separator = sep; + } + + public void formatComponent(StringBuffer buf, Calendar cal) + { + buf.append(separator); + } + + public int parseComponent(String s, int index, Calendar cal) + throws ParseException + { + checkLength(s, index, separator.length()); + if (!s.startsWith(separator, index)) + { + throw new ParseException("Invalid input: " + s + ", index " + + index + ", expected " + separator); + } + return separator.length(); + } + } + + /** + * A specialized date component parser implementation that deals with the + * time zone part of a date component. + */ + private static class DateTimeZoneParser extends DateComponentParser + { + public void formatComponent(StringBuffer buf, Calendar cal) + { + TimeZone tz = cal.getTimeZone(); + int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE; + if (ofs < 0) + { + buf.append('-'); + ofs = -ofs; + } + else + { + buf.append('+'); + } + int hour = ofs / MINUTES_PER_HOUR; + int min = ofs % MINUTES_PER_HOUR; + padNum(buf, hour, 2); + padNum(buf, min, 2); + } + + public int parseComponent(String s, int index, Calendar cal) + throws ParseException + { + checkLength(s, index, TIME_ZONE_LENGTH); + TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX + + s.substring(index, index + TIME_ZONE_LENGTH)); + cal.setTimeZone(tz); + return TIME_ZONE_LENGTH; + } } } Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.java?rev=590474&r1=590473&r2=590474&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.java (original) +++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.java Tue Oct 30 14:35:11 2007 @@ -84,17 +84,7 @@ */ protected Date parseDate(String s) throws ParseException { - // remove the prefix "<*D" and the suffix ">" - String substring = s.substring(3, s.length() - 1); - - try - { - return PropertyListConfiguration.DATE_FORMAT.parse(substring); - } - catch (Exception e) - { - throw (ParseException) new ParseException("Unable to parse the date '" + s + "' : " + e.getMessage()); - } + return PropertyListConfiguration.parseDate(s); } final public PropertyListConfiguration parse() throws ParseException { Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.jj URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.jj?rev=590474&r1=590473&r2=590474&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.jj (original) +++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/PropertyListParser.jj Tue Oct 30 14:35:11 2007 @@ -107,17 +107,7 @@ */ protected Date parseDate(String s) throws ParseException { - // remove the prefix "<*D" and the suffix ">" - String substring = s.substring(3, s.length() - 1); - - try - { - return PropertyListConfiguration.DATE_FORMAT.parse(substring); - } - catch (Exception e) - { - throw (ParseException) new ParseException("Unable to parse the date '" + s + "' : " + e.getMessage()); - } + return PropertyListConfiguration.parseDate(s); } } Modified: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java?rev=590474&r1=590473&r2=590474&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java (original) +++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java Tue Oct 30 14:35:11 2007 @@ -19,9 +19,11 @@ import java.io.File; import java.io.StringReader; +import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Date; +import java.util.TimeZone; import junit.framework.TestCase; import junitx.framework.ArrayAssert; @@ -177,9 +179,13 @@ public void testDate() throws Exception { - Date date = PropertyListConfiguration.DATE_FORMAT.parse("2002-03-22 11:30:00 +0100"); + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2002, 2, 22, 11, 30, 0); + cal.setTimeZone(TimeZone.getTimeZone("GMT+0100")); + Date date = cal.getTime(); - assertEquals("date", date, config.getProperty("date")); + assertEquals("date", date, config.getProperty("date")); } public void testSave() throws Exception @@ -296,5 +302,89 @@ { PropertyListConfiguration copy = new PropertyListConfiguration(config); assertFalse("Nothing was copied", copy.isEmpty()); + } + + /** + * Tests parsing a date with an invalid numeric value. + */ + public void testParseDateNoNumber() + { + try + { + PropertyListConfiguration + .parseDate("<*D2002-03-22 1c:30:00 +0100>"); + fail("Could parse date with an invalid number!"); + } + catch (ParseException pex) + { + // ok + } + } + + /** + * Tests parsing a date that is not long enough. + */ + public void testParseDateTooShort() + { + try + { + PropertyListConfiguration.parseDate("<*D2002-03-22 11:3>"); + fail("Could parse too short date!"); + } + catch (ParseException pex) + { + // ok + } + } + + /** + * Tests parsing a date that contains an invalid separator character. + */ + public void testParseDateInvalidChar() + { + try + { + PropertyListConfiguration + .parseDate("<*D2002+03-22 11:30:00 +0100>"); + fail("Could parse date with an invalid separator!"); + } + catch (ParseException pex) + { + // ok + } + } + + /** + * Tries parsing a null date. This should cause an exception.n + */ + public void testParseDateNull() + { + try + { + PropertyListConfiguration.parseDate(null); + fail("Could parse null date!"); + } + catch (ParseException pex) + { + // ok + } + } + + /** + * Tests formatting a date. + */ + public void testFormatDate() + { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2007, 9, 29, 23, 4, 30); + cal.setTimeZone(TimeZone.getTimeZone("GMT-0230")); + assertEquals("Wrong date literal (1)", "<*D2007-10-29 23:04:30 -0230>", + PropertyListConfiguration.formatDate(cal)); + cal.clear(); + cal.set(2007, 9, 30, 22, 2, 15); + cal.setTimeZone(TimeZone.getTimeZone("GMT+1111")); + assertEquals("Wrong date literal (2)", "<*D2007-10-30 22:02:15 +1111>", + PropertyListConfiguration.formatDate(cal)); } } Modified: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListParser.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListParser.java?rev=590474&r1=590473&r2=590474&view=diff ============================================================================== --- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListParser.java (original) +++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListParser.java Tue Oct 30 14:35:11 2007 @@ -19,7 +19,6 @@ import java.io.Reader; import java.util.Calendar; -import java.util.TimeZone; import java.util.SimpleTimeZone; import junit.framework.TestCase;