]> source.dussan.org Git - sonarqube.git/commitdiff
API: DateUtils is now thread-safe
authorsimonbrandhof <simon.brandhof@gmail.com>
Fri, 11 Mar 2011 16:28:03 +0000 (17:28 +0100)
committersimonbrandhof <simon.brandhof@gmail.com>
Mon, 14 Mar 2011 07:41:33 +0000 (08:41 +0100)
sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java
sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java

index abf8e2bfa0ea030cc1e43754815637b8a6c7a223..b7702d660e50769438e76e89abf5e0531b6cb4eb 100644 (file)
  */
 package org.sonar.api.utils;
 
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.lang.ref.SoftReference;
+import java.text.*;
 import java.util.Date;
 
 /**
+ * Parses and formats ISO 8601 dates. See http://en.wikipedia.org/wiki/ISO_8601.
+ * This class is thread-safe.
+ *
  * @since 2.7
  */
 public final class DateUtils {
   public static final String DATE_FORMAT = "yyyy-MM-dd";
   public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
 
-  private static final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
-  private static final SimpleDateFormat dateTimeFormat = new SimpleDateFormat(DATETIME_FORMAT);
+  private static final DateFormat dateFormat = new ThreadSafeDateFormat(DATE_FORMAT);
+  private static final DateFormat dateTimeFormat = new ThreadSafeDateFormat(DATETIME_FORMAT);
 
-  /**
-   * This method is not thread-safe.
-   */
   public static String formatDate(Date d) {
     return dateFormat.format(d);
   }
 
-  /**
-   * This method is not thread-safe.
-   */
   public static String formatDateTime(Date d) {
     return dateTimeFormat.format(d);
   }
 
-  /**
-   * This method is not thread-safe.
-   */
   public static Date parseDate(String s) {
     try {
       return dateFormat.parse(s);
@@ -59,9 +53,6 @@ public final class DateUtils {
     }
   }
 
-  /**
-   * This method is not thread-safe.
-   */
   public static Date parseDateTime(String s) {
     try {
       return dateTimeFormat.parse(s);
@@ -70,4 +61,35 @@ public final class DateUtils {
       throw new SonarException("The date '" + s + "' does not respect format '" + DATETIME_FORMAT + "'");
     }
   }
+
+  static class ThreadSafeDateFormat extends DateFormat {
+    private final String format;
+
+    ThreadSafeDateFormat(String format) {
+      this.format = format;
+    }
+
+    private final ThreadLocal cache = new ThreadLocal() {
+      public Object get() {
+        SoftReference softRef = (SoftReference) super.get();
+        if (softRef == null || softRef.get() == null) {
+          softRef = new SoftReference(new SimpleDateFormat(format));
+          super.set(softRef);
+        }
+        return softRef;
+      }
+    };
+
+    private DateFormat getDateFormat() {
+      return (DateFormat) ((SoftReference) cache.get()).get();
+    }
+
+    public StringBuffer format(Date date,StringBuffer toAppendTo, FieldPosition fieldPosition) {
+      return getDateFormat().format(date, toAppendTo, fieldPosition);
+    }
+
+    public Date parse(String source, ParsePosition pos) {
+      return getDateFormat().parse(source, pos);
+    }
+  }
 }
index 8ecebab8dca87242464eaad03543b3048a260c5f..8b41e61ba9ac3e1ed881a517f29368d0e41e51cf 100644 (file)
  */
 package org.sonar.api.utils;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import org.junit.Test;
 
+import java.text.ParseException;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.number.OrderingComparisons.greaterThan;
+import static org.hamcrest.number.OrderingComparisons.greaterThanOrEqualTo;
 import static org.hamcrest.text.StringStartsWith.startsWith;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.*;
 
 public class DateUtilsTest {
 
@@ -63,4 +70,80 @@ public class DateUtilsTest {
     assertThat(DateUtils.formatDateTime(new Date()), startsWith("20"));
     assertThat(DateUtils.formatDateTime(new Date()).length(), greaterThan(20));
   }
+
+  /**
+   * Cordially copied from XStream unit test
+   * See http://koders.com/java/fid8A231D75F2C6E6909FB26BCA11C12D08AD05FB50.aspx?s=ThreadSafeDateFormatTest
+   */
+  @Test
+  public void shouldBeThreadSafe() throws InterruptedException {
+
+    final DateUtils.ThreadSafeDateFormat format = new DateUtils.ThreadSafeDateFormat("yyyy-MM-dd'T'HH:mm:ss,S z");
+    final Date now = new Date();
+    final List<Throwable> throwables = Lists.newArrayList();
+
+    final ThreadGroup tg = new ThreadGroup("shouldBeThreadSafe") {
+      public void uncaughtException(Thread t, Throwable e) {
+        throwables.add(e);
+        super.uncaughtException(t, e);
+      }
+    };
+
+    final int[] counter = new int[1];
+    counter[0] = 0;
+    final Thread[] threads = new Thread[10];
+    for (int i = 0; i < threads.length; ++i) {
+      threads[i] = new Thread(tg, "JUnit Thread " + i) {
+
+        public void run() {
+          int i = 0;
+          try {
+            synchronized (this) {
+              notifyAll();
+              wait();
+            }
+            while (i < 1000 && !interrupted()) {
+              String formatted = format.format(now);
+              Thread.yield();
+              assertEquals(now, format.parse(formatted));
+              ++i;
+            }
+          } catch (Exception e) {
+            fail("Unexpected exception: " + e);
+          }
+          synchronized (counter) {
+            counter[0] += i;
+          }
+        }
+
+      };
+    }
+
+    for (int i = 0; i < threads.length; ++i) {
+      synchronized (threads[i]) {
+        threads[i].start();
+        threads[i].wait();
+      }
+    }
+
+    for (int i = 0; i < threads.length; ++i) {
+      synchronized (threads[i]) {
+        threads[i].notifyAll();
+      }
+    }
+
+    Thread.sleep(1000);
+
+    for (int i = 0; i < threads.length; ++i) {
+      threads[i].interrupt();
+    }
+    for (int i = 0; i < threads.length; ++i) {
+      synchronized (threads[i]) {
+        threads[i].join();
+      }
+    }
+
+    assertThat(throwables.size(), is(0));
+    assertThat(counter[0], greaterThanOrEqualTo(threads.length));
+  }
 }