diff options
author | simonbrandhof <simon.brandhof@gmail.com> | 2011-03-11 17:28:03 +0100 |
---|---|---|
committer | simonbrandhof <simon.brandhof@gmail.com> | 2011-03-14 08:41:33 +0100 |
commit | ec581b9a040c30eb69e6002ae2af16ab36b35d05 (patch) | |
tree | d12cd72a90f1fbba5bbad461a45ea5105478f133 /sonar-plugin-api | |
parent | 8d4fab85509a8864cecb5781fd4bc8d407a5e964 (diff) | |
download | sonarqube-ec581b9a040c30eb69e6002ae2af16ab36b35d05.tar.gz sonarqube-ec581b9a040c30eb69e6002ae2af16ab36b35d05.zip |
API: DateUtils is now thread-safe
Diffstat (limited to 'sonar-plugin-api')
-rw-r--r-- | sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java | 54 | ||||
-rw-r--r-- | sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java | 85 |
2 files changed, 122 insertions, 17 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java index abf8e2bfa0e..b7702d660e5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java @@ -19,37 +19,31 @@ */ 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); + } + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java index 8ecebab8dca..8b41e61ba9a 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java @@ -19,14 +19,21 @@ */ 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)); + } } |