aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2011-03-11 17:28:03 +0100
committersimonbrandhof <simon.brandhof@gmail.com>2011-03-14 08:41:33 +0100
commitec581b9a040c30eb69e6002ae2af16ab36b35d05 (patch)
treed12cd72a90f1fbba5bbad461a45ea5105478f133 /sonar-plugin-api
parent8d4fab85509a8864cecb5781fd4bc8d407a5e964 (diff)
downloadsonarqube-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.java54
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/utils/DateUtilsTest.java85
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));
+ }
}