]> granicus.if.org Git - icu/commitdiff
ICU-11669 Thread safety of DateIntervalFormat::format()
authorAndy Heninger <andy.heninger@gmail.com>
Thu, 7 Jan 2016 21:15:19 +0000 (21:15 +0000)
committerAndy Heninger <andy.heninger@gmail.com>
Thu, 7 Jan 2016 21:15:19 +0000 (21:15 +0000)
X-SVN-Rev: 38157

icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java

index 8b8f7d997e78500ac5fc42d2b3d0b9d0cf6d6e26..0808052d4c1c568ebc914ceceef8ccad0b85429f 100644 (file)
@@ -1,5 +1,5 @@
 /*
-*   Copyright (C) 2008-2015, International Business Machines
+*   Copyright (C) 2008-2016, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 */
 
@@ -247,6 +247,12 @@ import com.ibm.icu.util.ULocale.Category;
  * 
  *
  * </pre>
+ * <h4>Synchronization</h4>
+ * 
+ * The format methods of DateIntervalFormat may be used concurrently from multiple threads.
+ * Functions that alter the state of a DateIntervalFormat object (setters) 
+ * may not be used concurrently with any other functions.
+ * 
  * @stable ICU 4.0
  */
 
@@ -296,7 +302,9 @@ public class DateIntervalFormat extends UFormat {
     private DateIntervalInfo     fInfo;
 
     /*
-     * The DateFormat object used to format single pattern
+     * The DateFormat object used to format single pattern.
+     * Because fDateFormat is modified during format operations, all
+     * access to it from logically const, thread safe functions must be synchronized.
      */
     private SimpleDateFormat     fDateFormat;
 
@@ -304,6 +312,8 @@ public class DateIntervalFormat extends UFormat {
      * The 2 calendars with the from and to date.
      * could re-use the calendar in fDateFormat,
      * but keeping 2 calendars make it clear and clean.
+     * Because these Calendars are modified during format operations, all
+     * access to them from logically const, thread safe functions must be synchronized.
      */
     private Calendar fFromCalendar;
     private Calendar fToCalendar;
@@ -559,7 +569,7 @@ public class DateIntervalFormat extends UFormat {
      * @return    A copy of the object.
      * @stable ICU 4.0
      */
-    public Object clone()
+    public synchronized Object clone()
     {
         DateIntervalFormat other = (DateIntervalFormat) super.clone();
         other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
@@ -618,7 +628,7 @@ public class DateIntervalFormat extends UFormat {
      * @return                  Reference to 'appendTo' parameter.
      * @stable ICU 4.0
      */
-    public final StringBuffer format(DateInterval dtInterval,
+    public final synchronized StringBuffer format(DateInterval dtInterval,
                                      StringBuffer appendTo,
                                      FieldPosition fieldPosition)
     {
@@ -686,7 +696,7 @@ public class DateIntervalFormat extends UFormat {
      * @throws    IllegalArgumentException  if the two calendars are not equivalent.
      * @stable ICU 4.0
      */
-    public final StringBuffer format(Calendar fromCalendar,
+    public final synchronized StringBuffer format(Calendar fromCalendar,
                                      Calendar toCalendar,
                                      StringBuffer appendTo,
                                      FieldPosition pos)
@@ -1008,7 +1018,7 @@ public class DateIntervalFormat extends UFormat {
      * this date interval formatter.
      * @stable ICU 4.0
      */
-    public DateFormat getDateFormat()
+    public synchronized DateFormat getDateFormat()
     {
         return (DateFormat)fDateFormat.clone();
     }
index fe3fadda1f05b1d0bdd9abdd4365e265069e6d33..97f9d09a92bb8b944059394ba4b5c9dab814d032 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *******************************************************************************
- * Copyright (C) 2001-2015, International Business Machines Corporation and    *
+ * Copyright (C) 2001-2016, International Business Machines Corporation and    *
  * others. All Rights Reserved.                                                *
  *******************************************************************************
  */
@@ -14,7 +14,10 @@ package com.ibm.icu.dev.test.format;
 
 import java.text.FieldPosition;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
+import java.util.ListIterator;
 import java.util.Locale;
 
 import com.ibm.icu.impl.Utility;
@@ -1714,4 +1717,74 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk {
             }
         }
     }
+    
+    // TestTicket11669 - Check the thread safety of DateIntervalFormat.format().
+    //                   This test failed with ICU 56.
+    
+    public void TestTicket11669 () {
+        // These final variables are accessed directly by the concurrent threads.
+        final DateIntervalFormat formatter = DateIntervalFormat.getInstance(DateFormat.YEAR_MONTH_DAY, ULocale.US);
+        final ArrayList<DateInterval> testIntervals = new ArrayList<DateInterval>();
+        final ArrayList<String>expectedResults = new ArrayList<String>();
+        
+        // Create and save the input test data.
+        TimeZone tz = TimeZone.getTimeZone("Americal/Los_Angeles");
+        Calendar intervalStart = Calendar.getInstance(tz, ULocale.US);
+        Calendar intervalEnd = Calendar.getInstance(tz, ULocale.US);        
+        intervalStart.set(2009,  6, 1);
+        intervalEnd.set(2009, 6, 2);
+        testIntervals.add(new DateInterval(intervalStart.getTimeInMillis(), intervalEnd.getTimeInMillis()));        
+        intervalStart.set(2015, 2, 27);
+        intervalEnd.set(2015, 3, 1);
+        testIntervals.add(new DateInterval(intervalStart.getTimeInMillis(), intervalEnd.getTimeInMillis()));
+        
+        // Run the formatter single-threaded to create and save the expected results.
+        for (DateInterval interval: testIntervals) {
+            FieldPosition pos = new FieldPosition(0);
+            StringBuffer result = new StringBuffer();
+            formatter.format(interval, result, pos);
+            expectedResults.add(result.toString());
+        }
+               
+        class TestThread extends Thread {
+            public String errorMessage;
+            public void run() {
+                for (int loop=0; loop < 2000; ++loop) {
+                    ListIterator<String> expectedItr = expectedResults.listIterator();
+                    for (DateInterval interval: testIntervals) {
+                        String expected = expectedItr.next();
+                        FieldPosition pos = new FieldPosition(0);
+                        StringBuffer result = new StringBuffer();
+                        formatter.format(interval, result, pos);
+                        if (!expected.equals(result.toString())) {
+                            // Note: The ICU test framework doesn't support reporting failures from within a sub-thread.
+                            //       Save the failure for the main thread to pick up later.
+                            errorMessage = String.format("Expected \"%s\", actual \"%s\"", expected, result);
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+        
+        List<TestThread> threads = new ArrayList<TestThread>();
+        for (int i=0; i<4; ++i) {
+            threads.add(new TestThread());
+        }
+        for (Thread t: threads) {
+            t.start();
+        }
+        for (TestThread t: threads) {
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                fail("Unexpected exception: " + e.toString());
+            }
+            if (t.errorMessage != null) {
+                fail(t.errorMessage);
+            }
+        }
+    }
+    
+    
 }