You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2007/10/30 22:35:12 UTC

svn commit: r590474 - in /commons/proper/configuration/trunk/src: java/org/apache/commons/configuration/plist/ test/org/apache/commons/configuration/plist/

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>&lt;*D2002-03-22 11:30:00 +0100&gt;</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;