You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ba...@apache.org on 2011/04/20 08:51:16 UTC

svn commit: r1095299 - in /commons/proper/lang/trunk/src: main/java/org/apache/commons/lang3/time/FastDateFormat.java main/java/org/apache/commons/lang3/time/FormatCache.java test/java/org/apache/commons/lang3/time/FastDateFormatTest.java

Author: bayard
Date: Wed Apr 20 06:51:16 2011
New Revision: 1095299

URL: http://svn.apache.org/viewvc?rev=1095299&view=rev
Log:
Refactoring FastDateFormat per LANG-462 to use the FormatCache class created for an upcoming DateParser functionality. I've kept FormatCache package-private for now. 

Added:
    commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java   (with props)
Modified:
    commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
    commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java

Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java?rev=1095299&r1=1095298&r2=1095299&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java Wed Apr 20 06:51:16 2011
@@ -23,16 +23,14 @@ import java.text.DateFormatSymbols;
 import java.text.FieldPosition;
 import java.text.Format;
 import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.apache.commons.lang3.Validate;
 
@@ -99,15 +97,15 @@ public class FastDateFormat extends Form
      */
     public static final int SHORT = DateFormat.SHORT;
 
-    //@GuardedBy("this")
-    private static String cDefaultPattern; // lazily initialised by getInstance()
+    private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
+        @Override
+        protected FastDateFormat createInstance(String pattern,    TimeZone timeZone, Locale locale) {
+            return new FastDateFormat(pattern, timeZone, locale);
+        }
+    };
 
-    private static final Map<FastDateFormat, FastDateFormat> cInstanceCache =
-        new HashMap<FastDateFormat, FastDateFormat>(7);
-    private static final Map<Object, FastDateFormat> cDateInstanceCache = new HashMap<Object, FastDateFormat>(7);
-    private static final Map<Object, FastDateFormat> cTimeInstanceCache = new HashMap<Object, FastDateFormat>(7);
-    private static final Map<Object, FastDateFormat> cDateTimeInstanceCache = new HashMap<Object, FastDateFormat>(7);
-    private static final Map<Object, String> cTimeZoneDisplayCache = new HashMap<Object, String>(7);
+    private static ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
+        new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
 
     /**
      * The pattern.
@@ -118,18 +116,10 @@ public class FastDateFormat extends Form
      */
     private final TimeZone mTimeZone;
     /**
-     * Whether the time zone overrides any on Calendars.
-     */
-    private final boolean mTimeZoneForced;
-    /**
      * The locale.
      */
     private final Locale mLocale;
     /**
-     * Whether the locale overrides the default.
-     */
-    private final boolean mLocaleForced;
-    /**
      * The parsed rules.
      */
     private transient Rule[] mRules;
@@ -146,7 +136,7 @@ public class FastDateFormat extends Form
      * @return a date/time formatter
      */
     public static FastDateFormat getInstance() {
-        return getInstance(getDefaultPattern(), null, null);
+        return cache.getDateTimeInstance(SHORT, SHORT, null, null);
     }
 
     /**
@@ -159,7 +149,7 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if pattern is invalid
      */
     public static FastDateFormat getInstance(String pattern) {
-        return getInstance(pattern, null, null);
+        return cache.getInstance(pattern, null, null);
     }
 
     /**
@@ -174,7 +164,7 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if pattern is invalid
      */
     public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
-        return getInstance(pattern, timeZone, null);
+        return cache.getInstance(pattern, timeZone, null);
     }
 
     /**
@@ -188,7 +178,7 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if pattern is invalid
      */
     public static FastDateFormat getInstance(String pattern, Locale locale) {
-        return getInstance(pattern, null, locale);
+        return cache.getInstance(pattern, null, locale);
     }
 
     /**
@@ -204,15 +194,8 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if pattern is invalid
      *  or <code>null</code>
      */
-    public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
-        FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
-        FastDateFormat format = cInstanceCache.get(emptyFormat);
-        if (format == null) {
-            format = emptyFormat;
-            format.init();  // convert shell format into usable one
-            cInstanceCache.put(format, format);  // this is OK!
-        }
-        return format;
+    public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
+        return cache.getInstance(pattern, timeZone, locale);
     }
 
     //-----------------------------------------------------------------------
@@ -227,7 +210,7 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getDateInstance(int style) {
-        return getDateInstance(style, null, null);
+        return cache.getDateTimeInstance(style, null, null, null);
     }
 
     /**
@@ -242,7 +225,7 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getDateInstance(int style, Locale locale) {
-        return getDateInstance(style, null, locale);
+        return cache.getDateTimeInstance(style, null, null, locale);
     }
 
     /**
@@ -258,8 +241,9 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
-        return getDateInstance(style, timeZone, null);
+        return cache.getDateTimeInstance(style, null, timeZone, null);
     }
+    
     /**
      * <p>Gets a date formatter instance using the specified style, time
      * zone and locale.</p>
@@ -272,31 +256,8 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if the Locale has no date
      *  pattern defined
      */
-    public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
-        Object key = Integer.valueOf(style);
-        if (timeZone != null) {
-            key = new Pair(key, timeZone);
-        }
-
-        if (locale == null) {
-            locale = Locale.getDefault();
-        }
-
-        key = new Pair(key, locale);
-
-        FastDateFormat format = cDateInstanceCache.get(key);
-        if (format == null) {
-            try {
-                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
-                String pattern = formatter.toPattern();
-                format = getInstance(pattern, timeZone, locale);
-                cDateInstanceCache.put(key, format);
-
-            } catch (ClassCastException ex) {
-                throw new IllegalArgumentException("No date pattern for locale: " + locale);
-            }
-        }
-        return format;
+    public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
+        return cache.getDateTimeInstance(style, null, timeZone, locale);
     }
 
     //-----------------------------------------------------------------------
@@ -311,7 +272,7 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getTimeInstance(int style) {
-        return getTimeInstance(style, null, null);
+        return cache.getDateTimeInstance(null, style, null, null);
     }
 
     /**
@@ -326,7 +287,7 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getTimeInstance(int style, Locale locale) {
-        return getTimeInstance(style, null, locale);
+        return cache.getDateTimeInstance(null, style, null, locale);
     }
 
     /**
@@ -342,7 +303,7 @@ public class FastDateFormat extends Form
      * @since 2.1
      */
     public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
-        return getTimeInstance(style, timeZone, null);
+        return cache.getDateTimeInstance(null, style, timeZone, null);
     }
 
     /**
@@ -357,32 +318,8 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if the Locale has no time
      *  pattern defined
      */
-    public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
-        Object key = Integer.valueOf(style);
-        if (timeZone != null) {
-            key = new Pair(key, timeZone);
-        }
-        if (locale != null) {
-            key = new Pair(key, locale);
-        }
-
-        FastDateFormat format = cTimeInstanceCache.get(key);
-        if (format == null) {
-            if (locale == null) {
-                locale = Locale.getDefault();
-            }
-
-            try {
-                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
-                String pattern = formatter.toPattern();
-                format = getInstance(pattern, timeZone, locale);
-                cTimeInstanceCache.put(key, format);
-
-            } catch (ClassCastException ex) {
-                throw new IllegalArgumentException("No date pattern for locale: " + locale);
-            }
-        }
-        return format;
+    public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
+        return cache.getDateTimeInstance(null, style, timeZone, locale);
     }
 
     //-----------------------------------------------------------------------
@@ -397,9 +334,8 @@ public class FastDateFormat extends Form
      *  pattern defined
      * @since 2.1
      */
-    public static FastDateFormat getDateTimeInstance(
-            int dateStyle, int timeStyle) {
-        return getDateTimeInstance(dateStyle, timeStyle, null, null);
+    public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
     }
 
     /**
@@ -414,9 +350,8 @@ public class FastDateFormat extends Form
      *  pattern defined
      * @since 2.1
      */
-    public static FastDateFormat getDateTimeInstance(
-            int dateStyle, int timeStyle, Locale locale) {
-        return getDateTimeInstance(dateStyle, timeStyle, null, locale);
+    public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
     }
 
     /**
@@ -432,8 +367,7 @@ public class FastDateFormat extends Form
      *  pattern defined
      * @since 2.1
      */
-    public static FastDateFormat getDateTimeInstance(
-            int dateStyle, int timeStyle, TimeZone timeZone) {
+    public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) {
         return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
     }
     /**
@@ -449,32 +383,9 @@ public class FastDateFormat extends Form
      * @throws IllegalArgumentException if the Locale has no date/time
      *  pattern defined
      */
-    public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone,
-            Locale locale) {
-
-        Object key = new Pair(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle));
-        if (timeZone != null) {
-            key = new Pair(key, timeZone);
-        }
-        if (locale == null) {
-            locale = Locale.getDefault();
-        }
-        key = new Pair(key, locale);
-
-        FastDateFormat format = cDateTimeInstanceCache.get(key);
-        if (format == null) {
-            try {
-                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
-                        locale);
-                String pattern = formatter.toPattern();
-                format = getInstance(pattern, timeZone, locale);
-                cDateTimeInstanceCache.put(key, format);
-
-            } catch (ClassCastException ex) {
-                throw new IllegalArgumentException("No date time pattern for locale: " + locale);
-            }
-        }
-        return format;
+    public static FastDateFormat getDateTimeInstance(
+            int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) {
+        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
     }
 
     //-----------------------------------------------------------------------
@@ -488,68 +399,42 @@ public class FastDateFormat extends Form
      * @param locale  the locale to use
      * @return the textual name of the time zone
      */
-    static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
-        Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
+    static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
+        TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
         String value = cTimeZoneDisplayCache.get(key);
         if (value == null) {
             // This is a very slow call, so cache the results.
             value = tz.getDisplayName(daylight, style, locale);
-            cTimeZoneDisplayCache.put(key, value);
+            String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
+            if (prior != null) {
+                value= prior;
+            }
         }
         return value;
     }
 
-    /**
-     * <p>Gets the default pattern.</p>
-     *
-     * @return the default pattern
-     */
-    private static synchronized String getDefaultPattern() {
-        if (cDefaultPattern == null) {
-            cDefaultPattern = new SimpleDateFormat().toPattern();
-        }
-        return cDefaultPattern;
-    }
-
     // Constructor
     //-----------------------------------------------------------------------
     /**
      * <p>Constructs a new FastDateFormat.</p>
      *
-     * @param pattern  {@link java.text.SimpleDateFormat} compatible
-     *  pattern
-     * @param timeZone  time zone to use, <code>null</code> means use
-     *  default for <code>Date</code> and value within for
-     *  <code>Calendar</code>
-     * @param locale  locale, <code>null</code> means use system
-     *  default
-     * @throws IllegalArgumentException if pattern is invalid or
-     *  <code>null</code>
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
+     * @param timeZone  non-null time zone to use
+     * @param locale  non-null locale to use
+     * @throws NullPointerException if pattern, timeZone, or locale is null.
      */
     protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
-        super();
-        if (pattern == null) {
-            throw new IllegalArgumentException("The pattern must not be null");
-        }
         mPattern = pattern;
-
-        mTimeZoneForced = (timeZone != null);
-        if (timeZone == null) {
-            timeZone = TimeZone.getDefault();
-        }
         mTimeZone = timeZone;
-
-        mLocaleForced = (locale != null);
-        if (locale == null) {
-            locale = Locale.getDefault();
-        }
         mLocale = locale;
+
+        init();
     }
 
     /**
      * <p>Initializes the instance for first use.</p>
      */
-    protected void init() {
+    private void init() {
         List<Rule> rulesList = parsePattern();
         mRules = rulesList.toArray(new Rule[rulesList.size()]);
 
@@ -662,9 +547,9 @@ public class FastDateFormat extends Form
                 break;
             case 'z': // time zone (text)
                 if (tokenLen >= 4) {
-                    rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
+                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
                 } else {
-                    rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
+                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
                 }
                 break;
             case 'Z': // time zone (value)
@@ -812,7 +697,7 @@ public class FastDateFormat extends Form
      * @return the formatted string
      */
     public String format(Date date) {
-        Calendar c = new GregorianCalendar(mTimeZone, mLocale);
+        Calendar c = Calendar.getInstance(mTimeZone, mLocale);
         c.setTime(date);
         return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
     }
@@ -849,7 +734,7 @@ public class FastDateFormat extends Form
      * @return the specified string buffer
      */
     public StringBuffer format(Date date, StringBuffer buf) {
-        Calendar c = new GregorianCalendar(mTimeZone);
+        Calendar c = Calendar.getInstance(mTimeZone, mLocale);
         c.setTime(date);
         return applyRules(c, buf);
     }
@@ -863,11 +748,6 @@ public class FastDateFormat extends Form
      * @return the specified string buffer
      */
     public StringBuffer format(Calendar calendar, StringBuffer buf) {
-        if (mTimeZoneForced) {
-            calendar.getTimeInMillis(); /// LANG-538
-            calendar = (Calendar) calendar.clone();
-            calendar.setTimeZone(mTimeZone);
-        }
         return applyRules(calendar, buf);
     }
 
@@ -880,10 +760,8 @@ public class FastDateFormat extends Form
      * @return the specified string buffer
      */
     protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
-        Rule[] rules = mRules;
-        int len = mRules.length;
-        for (int i = 0; i < len; i++) {
-            rules[i].appendTo(buf, calendar);
+        for (Rule rule : mRules) {
+            rule.appendTo(buf, calendar);
         }
         return buf;
     }
@@ -918,10 +796,7 @@ public class FastDateFormat extends Form
     /**
      * <p>Gets the time zone used by this formatter.</p>
      *
-     * <p>This zone is always used for <code>Date</code> formatting.
-     * If a <code>Calendar</code> is passed in to be formatted, the
-     * time zone on that may be used depending on
-     * {@link #getTimeZoneOverridesCalendar()}.</p>
+     * <p>This zone is always used for <code>Date</code> formatting. </p>
      *
      * @return the time zone
      */
@@ -930,17 +805,6 @@ public class FastDateFormat extends Form
     }
 
     /**
-     * <p>Returns <code>true</code> if the time zone of the
-     * calendar overrides the time zone of the formatter.</p>
-     *
-     * @return <code>true</code> if time zone of formatter
-     *  overridden for calendars
-     */
-    public boolean getTimeZoneOverridesCalendar() {
-        return mTimeZoneForced;
-    }
-
-    /**
      * <p>Gets the locale used by this formatter.</p>
      *
      * @return the locale
@@ -976,16 +840,9 @@ public class FastDateFormat extends Form
             return false;
         }
         FastDateFormat other = (FastDateFormat) obj;
-        if (
-            (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
-            (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
-            (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
-            (mTimeZoneForced == other.mTimeZoneForced) &&
-            (mLocaleForced == other.mLocaleForced)
-            ) {
-            return true;
-        }
-        return false;
+        return mPattern.equals(other.mPattern)
+            && mTimeZone.equals(other.mTimeZone) 
+            && mLocale.equals(other.mLocale);
     }
 
     /**
@@ -995,13 +852,7 @@ public class FastDateFormat extends Form
      */
     @Override
     public int hashCode() {
-        int total = 0;
-        total += mPattern.hashCode();
-        total += mTimeZone.hashCode();
-        total += (mTimeZoneForced ? 1 : 0);
-        total += mLocale.hashCode();
-        total += (mLocaleForced ? 1 : 0);
-        return total;
+        return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
     }
 
     /**
@@ -1517,9 +1368,6 @@ public class FastDateFormat extends Form
      */
     private static class TimeZoneNameRule implements Rule {
         private final TimeZone mTimeZone;
-        private final boolean mTimeZoneForced;
-        private final Locale mLocale;
-        private final int mStyle;
         private final String mStandard;
         private final String mDaylight;
 
@@ -1527,55 +1375,31 @@ public class FastDateFormat extends Form
          * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
          *
          * @param timeZone the time zone
-         * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
          * @param locale the locale
          * @param style the style
          */
-        TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
+        TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) {
             mTimeZone = timeZone;
-            mTimeZoneForced = timeZoneForced;
-            mLocale = locale;
-            mStyle = style;
 
-            if (timeZoneForced) {
-                mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
-                mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
-            } else {
-                mStandard = null;
-                mDaylight = null;
-            }
+            mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
+            mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
         }
 
         /**
          * {@inheritDoc}
          */
         public int estimateLength() {
-            if (mTimeZoneForced) {
-                return Math.max(mStandard.length(), mDaylight.length());
-            } else if (mStyle == TimeZone.SHORT) {
-                return 4;
-            } else {
-                return 40;
-            }
+            return Math.max(mStandard.length(), mDaylight.length());
         }
 
         /**
          * {@inheritDoc}
          */
         public void appendTo(StringBuffer buffer, Calendar calendar) {
-            if (mTimeZoneForced) {
-                if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
-                    buffer.append(mDaylight);
-                } else {
-                    buffer.append(mStandard);
-                }
+            if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
+                buffer.append(mDaylight);
             } else {
-                TimeZone timeZone = calendar.getTimeZone();
-                if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
-                    buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
-                } else {
-                    buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
-                }
+                buffer.append(mStandard);
             }
         }
     }
@@ -1665,7 +1489,7 @@ public class FastDateFormat extends Form
          */
         @Override
         public int hashCode() {
-            return mStyle * 31 + mLocale.hashCode();
+            return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
         }
 
         /**
@@ -1686,67 +1510,4 @@ public class FastDateFormat extends Form
             return false;
         }
     }
-
-    // ----------------------------------------------------------------------
-    /**
-     * <p>Helper class for creating compound objects.</p>
-     *
-     * <p>One use for this class is to create a hashtable key
-     * out of multiple objects.</p>
-     */
-    private static class Pair {
-        private final Object mObj1;
-        private final Object mObj2;
-
-        /**
-         * Constructs an instance of <code>Pair</code> to hold the specified objects.
-         * @param obj1 one object in the pair
-         * @param obj2 second object in the pair
-         */
-        public Pair(Object obj1, Object obj2) {
-            mObj1 = obj1;
-            mObj2 = obj2;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-
-            if (!(obj instanceof Pair)) {
-                return false;
-            }
-
-            Pair key = (Pair)obj;
-
-            return
-                (mObj1 == null ?
-                 key.mObj1 == null : mObj1.equals(key.mObj1)) &&
-                (mObj2 == null ?
-                 key.mObj2 == null : mObj2.equals(key.mObj2));
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int hashCode() {
-            return
-                (mObj1 == null ? 0 : mObj1.hashCode()) +
-                (mObj2 == null ? 0 : mObj2.hashCode());
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public String toString() {
-            return "[" + mObj1 + ':' + mObj2 + ']';
-        }
-    }
-
 }

Added: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java?rev=1095299&view=auto
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java (added)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java Wed Apr 20 06:51:16 2011
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.DateFormat;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>FormatCache is a cache and factory for {@link Format}s.</p>
+ * 
+ * @since 3.0
+ * @version $Id: FormatCache 892161 2009-12-18 07:21:10Z  $
+ */
+abstract class FormatCache<F extends Format> {
+    /**
+     * No date or no time.  Used in same parameters as DateFormat.SHORT or DateFormat.LONG
+     */
+    static final int NONE= -1;
+    
+    private final ConcurrentMap<MultipartKey, F> cInstanceCache 
+        = new ConcurrentHashMap<MultipartKey, F>(7);
+    
+    private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache 
+        = new ConcurrentHashMap<MultipartKey, String>(7);
+
+    /**
+     * <p>Gets a formatter instance using the default pattern in the
+     * default timezone and locale.</p>
+     * 
+     * @return a date/time formatter
+     */
+    public F getInstance() {
+        return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
+    }
+
+    /**
+     * <p>Gets a formatter instance using the specified pattern, time zone
+     * and locale.</p>
+     * 
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible
+     *  pattern
+     * @param timeZone  the non-null time zone
+     * @param locale  the non-null locale
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     *  or <code>null</code>
+     */
+    public F getInstance(String pattern, TimeZone timeZone, Locale locale) {
+        if (pattern == null) {
+            throw new NullPointerException("pattern must not be null");
+        }
+        if (timeZone == null) {
+            timeZone = TimeZone.getDefault();
+        }
+        if (locale == null) {
+            locale = Locale.getDefault();
+        }
+        MultipartKey key = new MultipartKey(pattern, timeZone, locale);
+        F format = cInstanceCache.get(key);
+        if (format == null) {           
+            format = createInstance(pattern, timeZone, locale);
+            F previousValue= cInstanceCache.putIfAbsent(key, format);
+            if (previousValue != null) {
+                // another thread snuck in and did the same work
+                // we should return the instance that is in ConcurrentMap
+                format= previousValue;              
+            }
+        }
+        return format;
+    }
+    
+    /**
+     * <p>Create a format instance using the specified pattern, time zone
+     * and locale.</p>
+     * 
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
+     * @param timeZone  time zone, this will not be null.
+     * @param locale  locale, this will not be null.
+     * @return a pattern based date/time formatter
+     * @throws IllegalArgumentException if pattern is invalid
+     *  or <code>null</code>
+     */
+    abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);
+        
+    /**
+     * <p>Gets a date/time formatter instance using the specified style,
+     * time zone and locale.</p>
+     * 
+     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
+     * @param timeZone  optional time zone, overrides time zone of
+     *  formatted date
+     * @param locale  optional locale, overrides system locale
+     * @return a localized standard date/time formatter
+     * @throws IllegalArgumentException if the Locale has no date/time
+     *  pattern defined
+     */
+    public F getDateTimeInstance(Integer dateStyle, Integer timeStyle, TimeZone timeZone, Locale locale) {
+        if (locale == null) {
+            locale = Locale.getDefault();
+        }
+        MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
+
+        String pattern = cDateTimeInstanceCache.get(key);
+        if (pattern == null) {
+            try {
+                DateFormat formatter;
+                if (dateStyle == null) {
+                    formatter = DateFormat.getTimeInstance(timeStyle, locale);                    
+                }
+                else if (timeStyle == null) {
+                    formatter = DateFormat.getDateInstance(dateStyle, locale);                    
+                }
+                else {
+                    formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
+                }
+                pattern = ((SimpleDateFormat)formatter).toPattern();
+                String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
+                if (previous != null) {
+                    // even though it doesn't matter if another thread put the pattern
+                    // it's still good practice to return the String instance that is
+                    // actually in the ConcurrentMap
+                    pattern= previous;
+                }
+            } catch (ClassCastException ex) {
+                throw new IllegalArgumentException("No date time pattern for locale: " + locale);
+            }
+        }
+        
+        return getInstance(pattern, timeZone, locale);
+    }
+
+    // ----------------------------------------------------------------------
+    /**
+     * <p>Helper class to hold multi-part Map keys</p>
+     */
+    private static class MultipartKey {
+        private final Object[] keys;
+        private int hashCode;
+
+        /**
+         * Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
+         * @param keys the set of objects that make up the key.  Each key may be null.
+         */
+        public MultipartKey(Object... keys) {
+            this.keys = keys;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if ( obj instanceof MultipartKey == false ) {
+                return false;
+            }
+            return Arrays.equals(keys, ((MultipartKey)obj).keys);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int hashCode() {
+            if(hashCode==0) {
+                int rc= 0;
+                for(Object key : keys) {
+                    if(key!=null) {
+                        rc= rc*7 + key.hashCode();
+                    }
+                }
+                hashCode= rc;
+            }
+            return hashCode;
+        }
+    }
+
+}

Propchange: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FormatCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java?rev=1095299&r1=1095298&r2=1095299&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java Wed Apr 20 06:51:16 2011
@@ -56,8 +56,6 @@ public class FastDateFormatTest extends 
         assertEquals("MM/DD/yyyy", format1.getPattern());
         assertEquals(TimeZone.getDefault(), format1.getTimeZone());
         assertEquals(TimeZone.getDefault(), format2.getTimeZone());
-        assertEquals(false, format1.getTimeZoneOverridesCalendar());
-        assertEquals(false, format2.getTimeZoneOverridesCalendar());
     }
 
     public void test_getInstance_String_TimeZone() {
@@ -77,9 +75,7 @@ public class FastDateFormatTest extends 
 
             assertTrue(format1 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2);
             assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
-            assertEquals(true, format1.getTimeZoneOverridesCalendar());
             assertEquals(TimeZone.getDefault(), format2.getTimeZone());
-            assertEquals(false, format2.getTimeZoneOverridesCalendar());
             assertSame(format3, format4);
             assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5);
             assertTrue(format4 != format6); // -- junit 3.8 version -- assertFalse(format3 == format5);
@@ -164,9 +160,6 @@ public class FastDateFormatTest extends 
             assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
             assertEquals(TimeZone.getDefault(), format2.getTimeZone());
             assertEquals(TimeZone.getDefault(), format3.getTimeZone());
-            assertEquals(true, format1.getTimeZoneOverridesCalendar());
-            assertEquals(false, format2.getTimeZoneOverridesCalendar());
-            assertEquals(true, format3.getTimeZoneOverridesCalendar());
             assertEquals(Locale.GERMANY, format1.getLocale());
             assertEquals(Locale.GERMANY, format2.getLocale());
             assertEquals(Locale.GERMANY, format3.getLocale());
@@ -183,8 +176,6 @@ public class FastDateFormatTest extends 
         try {
             Locale.setDefault(Locale.US);
             TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
-            FastDateFormat fdf = null;
-            SimpleDateFormat sdf = null;
 
             GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
             GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
@@ -193,8 +184,8 @@ public class FastDateFormatTest extends 
             long millis1 = date1.getTime();
             long millis2 = date2.getTime();
 
-            fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
-            sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+            FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
             assertEquals(sdf.format(date1), fdf.format(date1));
             assertEquals("2003-01-10T15:33:20", fdf.format(date1));
             assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
@@ -208,7 +199,6 @@ public class FastDateFormatTest extends 
             assertEquals("-0500", fdf.format(cal1));
             assertEquals("-0500", fdf.format(millis1));
 
-            fdf = FastDateFormat.getInstance("Z");
             assertEquals("-0400", fdf.format(date2));
             assertEquals("-0400", fdf.format(cal2));
             assertEquals("-0400", fdf.format(millis2));
@@ -218,7 +208,6 @@ public class FastDateFormatTest extends 
             assertEquals("-05:00", fdf.format(cal1));
             assertEquals("-05:00", fdf.format(millis1));
 
-            fdf = FastDateFormat.getInstance("ZZ");
             assertEquals("-04:00", fdf.format(date2));
             assertEquals("-04:00", fdf.format(cal2));
             assertEquals("-04:00", fdf.format(millis2));
@@ -228,14 +217,13 @@ public class FastDateFormatTest extends 
             fdf = FastDateFormat.getInstance(pattern);
             sdf = new SimpleDateFormat(pattern);
             assertEquals(sdf.format(date1), fdf.format(date1));
-            assertEquals(sdf.format(date2), fdf.format(date2));
-
+            assertEquals(sdf.format(date2), fdf.format(date2));            
         } finally {
             Locale.setDefault(realDefaultLocale);
             TimeZone.setDefault(realDefaultZone);
         }
     }
-
+    
     /**
      * Test case for {@link FastDateFormat#getDateInstance(int, java.util.Locale)}.
      */
@@ -283,7 +271,6 @@ public class FastDateFormatTest extends 
      * testLowYearPadding showed that the date was buggy
      * This test confirms it, getting 366 back as a date
      */
-     // TODO: Fix this problem
     public void testSimpleDate() {
         Calendar cal = Calendar.getInstance();
         FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
@@ -308,8 +295,6 @@ public class FastDateFormatTest extends 
     }
 
     public void testLang538() {
-        final String dateTime = "2009-10-16T16:42:16.000Z";
-
         // more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
         // for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
         GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
@@ -317,7 +302,8 @@ public class FastDateFormatTest extends 
         cal.set(2009, 9, 16, 8, 42, 16);
 
         FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
-        assertEquals("dateTime", dateTime, format.format(cal));
+        assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
+        assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
     }
 
     public void testLang645() {