]> granicus.if.org Git - icu/commitdiff
ICU-11363 Updated SimpleDateFormat clone implementation to create a copy of an array...
authorYoshito Umaoka <y.umaoka@gmail.com>
Thu, 13 Nov 2014 18:50:44 +0000 (18:50 +0000)
committerYoshito Umaoka <y.umaoka@gmail.com>
Thu, 13 Nov 2014 18:50:44 +0000 (18:50 +0000)
X-SVN-Rev: 36729

icu4j/main/classes/core/src/com/ibm/icu/impl/DateNumberFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java

index 03349de1854877de277680a30218da939ae5b568..7f75bfae654e648df4ec8eb1dc258569b1df6c91 100644 (file)
@@ -1,6 +1,6 @@
 /*
 *******************************************************************************
-*   Copyright (C) 2007-2011, International Business Machines
+*   Copyright (C) 2007-2014, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 *******************************************************************************
 */
@@ -23,6 +23,7 @@ import com.ibm.icu.util.UResourceBundle;
 /*
  * NumberFormat implementation dedicated/optimized for DateFormat,
  * used by SimpleDateFormat implementation.
+ * This class is not thread-safe.
  */
 public final class DateNumberFormat extends NumberFormat {
 
@@ -33,7 +34,8 @@ public final class DateNumberFormat extends NumberFormat {
     private char minusSign;
     private boolean positiveOnly = false;
 
-    private transient char[] decimalBuf = new char[20]; // 20 digits is good enough to store Long.MAX_VALUE
+    private static final int DECIMAL_BUF_SIZE = 20; // 20 digits is good enough to store Long.MAX_VALUE
+    private transient char[] decimalBuf = new char[DECIMAL_BUF_SIZE];
 
     private static SimpleCache<ULocale, char[]> CACHE = new SimpleCache<ULocale, char[]>();
 
@@ -123,7 +125,7 @@ public final class DateNumberFormat extends NumberFormat {
     }
 
     public char[] getDigits() {
-        return digits;
+        return digits.clone();
     }
 
     public StringBuffer format(double number, StringBuffer toAppendTo,
@@ -254,7 +256,15 @@ public final class DateNumberFormat extends NumberFormat {
             setZeroDigit(zeroDigit);
         }
         // re-allocate the work buffer
-        decimalBuf = new char[20];
+        decimalBuf = new char[DECIMAL_BUF_SIZE];
+    }
+
+    @Override
+    public Object clone() {
+        DateNumberFormat dnfmt = (DateNumberFormat)super.clone();
+        dnfmt.digits = this.digits.clone();
+        dnfmt.decimalBuf = new char[DECIMAL_BUF_SIZE];
+        return dnfmt;
     }
 }
 
index bf7f59843cd9983f9a57c0c94e732282e4da8fc8..cf0b6f44eae31d6bc503a9a93a698299b5f93193 100644 (file)
@@ -1995,6 +1995,10 @@ public class SimpleDateFormat extends DateFormat {
         }
     }
 
+    /*
+     * Initializes transient fields for fast simple numeric formatting
+     * code. This method should be called whenever number format is updated.
+     */
     private void initLocalZeroPaddingNumberFormat() {
         if (numberFormat instanceof DecimalFormat) {
             decDigits = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getDigits();
@@ -2007,14 +2011,15 @@ public class SimpleDateFormat extends DateFormat {
         }
 
         if (useLocalZeroPaddingNumberFormat) {
-            decimalBuf = new char[10];  // sufficient for int numbers
+            decimalBuf = new char[DECIMAL_BUF_SIZE];
         }
     }
 
     // If true, use local version of zero padding number format
     private transient boolean useLocalZeroPaddingNumberFormat;
-    private transient char[] decDigits;
-    private transient char[] decimalBuf;
+    private transient char[] decDigits;     // read-only - can be shared by multiple instances
+    private transient char[] decimalBuf;    // mutable - one per instance
+    private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers
 
     /*
      * Lightweight zero padding integer number format function.
@@ -3470,6 +3475,11 @@ public class SimpleDateFormat extends DateFormat {
     public Object clone() {
         SimpleDateFormat other = (SimpleDateFormat) super.clone();
         other.formatData = (DateFormatSymbols) formatData.clone();
+        // We must create a new copy of work buffer used by
+        // the fast numeric field format code.
+        if (this.decimalBuf != null) {
+            other.decimalBuf = new char[DECIMAL_BUF_SIZE];
+        }
         return other;
     }
 
index e5f15f34aacfb6525060a8de2b463237daf8ce61..eacd70519d1cc1dfc565b274d6f782188c1b71dd 100644 (file)
@@ -1427,5 +1427,57 @@ public class DateFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
       }
 
   }
-    
+
+    // Test case for numeric field format threading problem
+    public void TestT11363() {
+
+        class TestThread extends Thread {
+            SimpleDateFormat fmt;
+            Date d;
+
+            TestThread(SimpleDateFormat fmt, Date d) {
+                this.fmt = fmt;
+                this.d = d;
+            }
+
+            public void run() {
+                String s0 = fmt.format(d);
+                for (int i = 0; i < 1000; i++) {
+                    String s = fmt.format(d);
+                    if (!s0.equals(s)) {
+                        errln("Result: " + s + ", Expected: " + s0);
+                    }
+                }
+            }
+        }
+
+        SimpleDateFormat fmt0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+        Thread[] threads = new Thread[10];
+
+        GregorianCalendar cal = new GregorianCalendar(2014, Calendar.NOVEMBER, 5, 12, 34, 56);
+        cal.set(Calendar.MILLISECOND, 777);
+
+        // calls format() once on the base object to trigger
+        // lazy initialization stuffs.
+        fmt0.format(cal.getTime());
+
+        for (int i = 0; i < threads.length; i++) {
+            // Add 1 to all fields to use different numbers in each thread
+            cal.add(Calendar.YEAR, 1);
+            cal.add(Calendar.MONTH, 1);
+            cal.add(Calendar.DAY_OF_MONTH, 1);
+            cal.add(Calendar.HOUR_OF_DAY, 1);
+            cal.add(Calendar.MINUTE, 1);
+            cal.add(Calendar.SECOND, 1);
+            cal.add(Calendar.MILLISECOND, 1);
+            Date d = cal.getTime();
+            SimpleDateFormat fmt = (SimpleDateFormat)fmt0.clone();
+            threads[i] = new TestThread(fmt, d);
+        }
+
+        for (Thread t : threads) {
+            t.start();
+        }
+    }
 }