Author: chas
Date: Thu Sep 27 04:50:21 2012
New Revision: 1390839

URL: http://svn.apache.org/viewvc?rev=1390839&view=rev
Log:
LANG-829: FastDateParser could use Calendar.getDisplayNames for all text fields

Modified:
    
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java

Modified: 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
URL: 
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java?rev=1390839&r1=1390838&r2=1390839&view=diff
==============================================================================
--- 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
 (original)
+++ 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
 Thu Sep 27 04:50:21 2012
@@ -19,7 +19,6 @@ package org.apache.commons.lang3.time;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
-import java.text.DateFormatSymbols;
 import java.text.ParseException;
 import java.text.ParsePosition;
 import java.util.ArrayList;
@@ -73,9 +72,6 @@ public class FastDateParser implements D
      */
     private static final long serialVersionUID = 1L;
 
-    private static final ConcurrentMap<Locale,TimeZoneStrategy> tzsCache=
-        new ConcurrentHashMap<Locale,TimeZoneStrategy>(3);
-
     static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
 
     // defining fields
@@ -87,7 +83,6 @@ public class FastDateParser implements D
     private transient Pattern parsePattern;
     private transient Strategy[] strategies;
     private transient int thisYear;
-    private transient ConcurrentMap<Integer, KeyValue[]> nameValues;
 
     // dynamic fields to communicate with Strategy
     private transient String currentFormatField;
@@ -113,9 +108,8 @@ public class FastDateParser implements D
      * This is called from constructor and from readObject (de-serialization)
      */
     private void init() {
-        thisYear= Calendar.getInstance(timeZone, locale).get(Calendar.YEAR);
-
-        nameValues= new ConcurrentHashMap<Integer, KeyValue[]>();
+        Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
+               thisYear= definingCalendar.get(Calendar.YEAR);
 
         StringBuilder regex= new StringBuilder();
         List<Strategy> collector = new ArrayList<Strategy>();
@@ -127,7 +121,7 @@ public class FastDateParser implements D
         }
 
         currentFormatField= patternMatcher.group();
-        Strategy currentStrategy= getStrategy(currentFormatField);
+        Strategy currentStrategy= getStrategy(currentFormatField, 
definingCalendar);
         for(;;) {
             patternMatcher.region(patternMatcher.end(), 
patternMatcher.regionEnd());
             if(!patternMatcher.lookingAt()) {
@@ -135,7 +129,7 @@ public class FastDateParser implements D
                 break;
             }
             String nextFormatField= patternMatcher.group();
-            nextStrategy = getStrategy(nextFormatField);
+            nextStrategy = getStrategy(nextFormatField, definingCalendar);
             if(currentStrategy.addRegex(this, regex)) {
                 collector.add(currentStrategy);
             }
@@ -368,112 +362,29 @@ public class FastDateParser implements D
     /**
      * Get the short and long values displayed for a field
      * @param field The field of interest
+     * @param definingCalendar The calendar to obtain the short and long values
+     * @param locale The locale of dislay names
      * @return A sorted array of the field key / value pairs
      */
-    KeyValue[] getDisplayNames(int field) {
-        Integer fieldInt = Integer.valueOf(field);
-        KeyValue[] fieldKeyValues= nameValues.get(fieldInt);
-        if(fieldKeyValues==null) {
-            DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
-            switch(field) {
-            case Calendar.ERA:
-                // DateFormatSymbols#getEras() only returns AD/BC or 
translations
-                // It does not work for the Thai Buddhist or Japanese Imperial 
calendars.
-                // see: https://issues.apache.org/jira/browse/TRINIDAD-2126
-                Calendar c = Calendar.getInstance(locale);
-                // N.B. Some calendars have different short and long symbols, 
e.g. ja_JP_JP
-                // TODO Seems to be only that locale; if that is guaranteed, 
could skip some work here
-                String[] shortEras = toArray(c.getDisplayNames(Calendar.ERA, 
Calendar.SHORT, locale));
-                String[] longEras = toArray(c.getDisplayNames(Calendar.ERA, 
Calendar.LONG, locale));
-                if (Arrays.equals(shortEras, longEras)) {
-                    fieldKeyValues = createKeyValues(longEras, null); // save 
memory
-                } else {
-                    fieldKeyValues = createKeyValues(longEras, shortEras);     
               
-                }
-                break;
-            case Calendar.DAY_OF_WEEK:
-                fieldKeyValues= createKeyValues(symbols.getWeekdays(), 
symbols.getShortWeekdays());
-                break;
-            case Calendar.AM_PM:
-                fieldKeyValues= createKeyValues(symbols.getAmPmStrings(), 
null);
-                break;
-            case Calendar.MONTH:
-                fieldKeyValues= createKeyValues(symbols.getMonths(), 
symbols.getShortMonths());
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid field value 
"+field);
-            }
-            KeyValue[] prior = nameValues.putIfAbsent(fieldInt, 
fieldKeyValues);
-            if(prior!=null) {
-                fieldKeyValues= prior;
-            }
-        }
-        return fieldKeyValues;
+    private static KeyValue[] getDisplayNames(int field, Calendar 
definingCalendar, Locale locale) {
+               List<KeyValue> keyValues = new ArrayList<KeyValue>(24);
+               addNamesToKeyValues(keyValues, 
definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale));
+        return createKeyValues(keyValues);
     }
 
-    private String[] toArray(Map<String, Integer> era) {
-        String[] eras = new String[era.size()]; // assume no gaps in entry 
values
-        for(Map.Entry<String, Integer> me : era.entrySet()) {
-            int idx = me.getValue().intValue();
-            final String key = me.getKey();
-            if (key == null) {
-                throw new IllegalArgumentException();
-            }
-            eras[idx] = key;
+       private static void addNamesToKeyValues(List<KeyValue> keyValues, 
Map<String, Integer> displayNames) {
+        for(Map.Entry<String, Integer> me : displayNames.entrySet()) {
+               keyValues.add(new KeyValue(me.getKey(), me.getValue()));
         }
-        return eras;
     }
 
-    /**
-     * Create key / value pairs from keys
-     * @param longValues The allowable long names for a field
-     * @param shortValues The optional allowable short names for a field
-     * @return The sorted name / value pairs for the field
-     */
-    private static KeyValue[] createKeyValues(String[] longValues, String[] 
shortValues) {
-        KeyValue[] fieldKeyValues= new 
KeyValue[count(longValues)+count(shortValues)];
-        copy(fieldKeyValues, copy(fieldKeyValues, 0, longValues), shortValues);
+    private static KeyValue[] createKeyValues(List<KeyValue> keyValues) {
+        KeyValue[] fieldKeyValues= keyValues.toArray(new 
KeyValue[keyValues.size()]);
         Arrays.sort(fieldKeyValues, IGNORE_CASE_COMPARATOR);
         return fieldKeyValues;
     }
 
     /**
-     * Get a count of valid values in array.  A valid value is of non-zero 
length.
-     * @param values The values to check.  This parameter may be null
-     * @return The number of valid values
-     */
-    private static int count(String[] values) {
-        int count= 0;
-        if(values!=null) {
-            for(String value : values) {
-                if(value.length()>0) {
-                    ++count;
-                }
-            }
-        }
-        return count;
-    }
-
-    /**
-     * Create key / value pairs from values
-     * @param fieldKeyValues The destination array
-     * @param offset The offset into the destination array
-     * @param values The values to use to create key / value pairs.  This 
parameter may be null.
-     * @return The offset into the destination array
-     */
-    private static int copy(KeyValue[] fieldKeyValues, int offset, String[] 
values) {
-        if(values!=null) {
-            for(int i= 0; i<values.length; ++i) {
-                String value= values[i];
-                if(value.length()>0) {
-                    fieldKeyValues[offset++]= new KeyValue(value, i);
-                }
-            }
-        }
-        return offset;
-    }
-
-    /**
      * Adjust dates to be within 80 years before and 20 years after 
instantiation
      * @param twoDigitYear The year to adjust
      * @return A value within -80 and +20 years from instantiation of this 
instance
@@ -523,6 +434,7 @@ public class FastDateParser implements D
          * which will accept this field
          * @param parser The parser calling this strategy
          * @param regex The <code>StringBuilder</code> to append to
+         * @param Calendar The calendar this strategy must parse
          * @return true, if this field will set the calendar;
          * false, if this field is a constant value
          */
@@ -538,9 +450,10 @@ public class FastDateParser implements D
     /**
      * Obtain a Strategy given a field from a SimpleDateFormat pattern
      * @param formatField A sub-sequence of the SimpleDateFormat pattern
+     * @param definingCalendar The calendar to obtain the short and long values
      * @return The Strategy that will handle parsing for the field
      */
-    private Strategy getStrategy(String formatField) {
+    private Strategy getStrategy(String formatField, Calendar 
definingCalendar) {
         switch(formatField.charAt(0)) {
         case '\'':
             if(formatField.length()>2) {
@@ -552,25 +465,23 @@ public class FastDateParser implements D
         case 'D':
             return DAY_OF_YEAR_STRATEGY;
         case 'E':
-            return DAY_OF_WEEK_STRATEGY;
+            return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, 
definingCalendar);
         case 'F':
             return DAY_OF_WEEK_IN_MONTH_STRATEGY;
         case 'G':
-            return ERA_STRATEGY;
+            return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
         case 'H':
             return MODULO_HOUR_OF_DAY_STRATEGY;
         case 'K':
             return HOUR_STRATEGY;
         case 'M':
-            return formatField.length()>=3 ?TEXT_MONTH_STRATEGY 
:NUMBER_MONTH_STRATEGY;
+            return formatField.length()>=3 
?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) 
:NUMBER_MONTH_STRATEGY;
         case 'S':
             return MILLISECOND_STRATEGY;
         case 'W':
             return WEEK_OF_MONTH_STRATEGY;
-        case 'Z':
-            break;
         case 'a':
-            return AM_PM_STRATEGY;
+            return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
         case 'd':
             return DAY_OF_MONTH_STRATEGY;
         case 'h':
@@ -585,21 +496,52 @@ public class FastDateParser implements D
             return WEEK_OF_YEAR_STRATEGY;
         case 'y':
             return formatField.length()>2 ?LITERAL_YEAR_STRATEGY 
:ABBREVIATED_YEAR_STRATEGY;
+        case 'Z':
         case 'z':
-            break;
+               return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, 
definingCalendar);
         }
-        TimeZoneStrategy tzs= tzsCache.get(locale);
-        if(tzs==null) {
-            tzs= new TimeZoneStrategy(locale);
-            TimeZoneStrategy inCache= tzsCache.putIfAbsent(locale, tzs);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static ConcurrentMap<Locale, Strategy>[] caches = new 
ConcurrentMap[Calendar.FIELD_COUNT];
+
+    /**
+     * Get a cache of Strategies for a particular field
+     * @param field The Calendar field
+     * @return a cache of Locale to Strategy
+     */
+    private static ConcurrentMap<Locale, Strategy> getCache(int field) {
+       synchronized(caches) {
+               if(caches[field]==null) {
+                       caches[field]= new 
ConcurrentHashMap<Locale,Strategy>(3);
+               }
+               return caches[field];
+       }
+    }
+
+    /**
+     * Construct a Strategy that parses a Text field
+     * @param locale The Locale of the TimeZone to parse
+     * @param field The Calendar field
+     * @param definingCalendar The calendar to obtain the short and long values
+     * @return a TextStrategy for the field and Locale
+     */
+    private Strategy getLocaleSpecificStrategy(int field, Calendar 
definingCalendar) {
+       ConcurrentMap<Locale,Strategy> cache = getCache(field);
+       Strategy strategy= cache.get(field);
+        if(strategy==null) {
+               strategy= field==Calendar.ZONE_OFFSET
+                               ? new TimeZoneStrategy(locale)
+                               : new TextStrategy(field, definingCalendar, 
locale);
+            Strategy inCache= cache.putIfAbsent(locale, strategy);
             if(inCache!=null) {
                 return inCache;
             }
         }
-        return tzs;
+        return strategy;
     }
 
-    /**
+       /**
      * A strategy that copies the static or quoted field in the parsing pattern
      */
     private static class CopyQuotedStrategy implements Strategy {
@@ -642,18 +584,20 @@ public class FastDateParser implements D
         }
     }
 
-    /**
+       /**
      * A strategy that handles a text field in the parsing pattern
      */
-    private static class TextStrategy implements Strategy {
+     private static class TextStrategy implements Strategy {
         private final int field;
+        private final KeyValue[] keyValues;
 
         /**
          * Construct a Strategy that parses a Text field
          * @param field The Calendar field
          */
-        TextStrategy(int field) {
+        TextStrategy(int field, Calendar definingCalendar, Locale locale) {
             this.field= field;
+            this.keyValues= getDisplayNames(field, definingCalendar, locale);
         }
 
         /**
@@ -670,7 +614,7 @@ public class FastDateParser implements D
         @Override
         public boolean addRegex(FastDateParser parser, StringBuilder regex) {
             regex.append('(');
-            for(KeyValue textKeyValue : parser.getDisplayNames(field)) {
+            for(KeyValue textKeyValue : keyValues) {
                 escapeRegex(regex, textKeyValue.key, false).append('|');
             }
             regex.setCharAt(regex.length()-1, ')');
@@ -682,21 +626,21 @@ public class FastDateParser implements D
          */
         @Override
         public void setCalendar(FastDateParser parser, Calendar cal, String 
value) {
-            KeyValue[] textKeyValues= parser.getDisplayNames(field);
-            int idx= Arrays.binarySearch(textKeyValues, new KeyValue(value, 
-1), IGNORE_CASE_COMPARATOR);
+            int idx= Arrays.binarySearch(keyValues, new KeyValue(value, -1), 
IGNORE_CASE_COMPARATOR);
             if(idx<0) {
                 StringBuilder sb= new StringBuilder(value);
                 sb.append(" not in (");
-                for(KeyValue textKeyValue : textKeyValues) {
+                for(KeyValue textKeyValue : keyValues) {
                     sb.append(textKeyValue.key).append(' ');
                 }
                 sb.setCharAt(sb.length()-1, ')');
                 throw new IllegalArgumentException(sb.toString());
             }
-            cal.set(field, textKeyValues[idx].value);
+            cal.set(field, keyValues[idx].value);
         }
     }
 
+
     /**
      * A strategy that handles a number field in the parsing pattern
      */
@@ -838,12 +782,6 @@ public class FastDateParser implements D
         }
     }
 
-
-    private static final Strategy ERA_STRATEGY = new 
TextStrategy(Calendar.ERA);
-    private static final Strategy DAY_OF_WEEK_STRATEGY = new 
TextStrategy(Calendar.DAY_OF_WEEK);
-    private static final Strategy AM_PM_STRATEGY = new 
TextStrategy(Calendar.AM_PM);
-    private static final Strategy TEXT_MONTH_STRATEGY = new 
TextStrategy(Calendar.MONTH);
-
     private static final Strategy NUMBER_MONTH_STRATEGY = new 
NumberStrategy(Calendar.MONTH) {
         @Override
         public int modify(int iValue) {


Reply via email to