diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2017-09-11 16:18:34 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2017-09-15 08:51:08 +0200 |
commit | f76f82c6ef66e60e4986521d141514763e5c405a (patch) | |
tree | 87f9aae33bcf56de22a0f1f2306d2a209682d935 /sonar-plugin-api/src | |
parent | 3078c44cf8dd58238ac05ee39b77dff660a20560 (diff) | |
download | sonarqube-f76f82c6ef66e60e4986521d141514763e5c405a.tar.gz sonarqube-f76f82c6ef66e60e4986521d141514763e5c405a.zip |
SONAR-9718 Fail with a clear message when sonar.projectDate is empty
Diffstat (limited to 'sonar-plugin-api/src')
4 files changed, 119 insertions, 164 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java index 2a210e519e2..4ec66633e7e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java @@ -32,7 +32,6 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.SonarException; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; @@ -246,7 +245,7 @@ public abstract class Request { try { return DateUtils.parseDate(s); - } catch (SonarException notDateException) { + } catch (RuntimeException notDateException) { throw new IllegalArgumentException(notDateException); } } 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 d226fe6270a..ea232916471 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,15 +19,13 @@ */ package org.sonar.api.utils; -import java.io.NotSerializableException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.text.DateFormat; -import java.text.FieldPosition; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; import java.util.Date; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -44,26 +42,44 @@ 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 ThreadSafeDateFormat THREAD_SAFE_DATE_FORMAT = new ThreadSafeDateFormat(DATE_FORMAT); - private static final ThreadSafeDateFormat THREAD_SAFE_DATETIME_FORMAT = new ThreadSafeDateFormat(DATETIME_FORMAT); + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMAT); private DateUtils() { } + /** + * Warning: relies on default timezone! + */ public static String formatDate(Date d) { - return THREAD_SAFE_DATE_FORMAT.format(d); + return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString(); } + /** + * Warning: relies on default timezone! + */ public static String formatDateTime(Date d) { - return THREAD_SAFE_DATETIME_FORMAT.format(d); + return formatDateTime(OffsetDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault())); } + /** + * Warning: relies on default timezone! + */ public static String formatDateTime(long ms) { - return THREAD_SAFE_DATETIME_FORMAT.format(new Date(ms)); + return formatDateTime(OffsetDateTime.ofInstant(Instant.ofEpochMilli(ms), ZoneId.systemDefault())); + } + + /** + * @since 6.6 + */ + public static String formatDateTime(OffsetDateTime dt) { + return DATETIME_FORMATTER.format(dt); } + /** + * Warning: relies on default timezone! + */ public static String formatDateTimeNullSafe(@Nullable Date date) { - return date == null ? "" : THREAD_SAFE_DATETIME_FORMAT.format(date); + return date == null ? "" : formatDateTime(date); } @CheckForNull @@ -77,16 +93,12 @@ public final class DateUtils { } /** + * Return a date at the start of day. * @param s string in format {@link #DATE_FORMAT} * @throws SonarException when string cannot be parsed */ public static Date parseDate(String s) { - ParsePosition pos = new ParsePosition(0); - Date result = THREAD_SAFE_DATE_FORMAT.parse(s, pos); - if (pos.getIndex() != s.length()) { - throw new SonarException("The date '" + s + "' does not respect format '" + DATE_FORMAT + "'"); - } - return result; + return Date.from(parseLocalDate(s).atStartOfDay(ZoneId.systemDefault()).toInstant()); } /** @@ -111,17 +123,56 @@ public final class DateUtils { } /** + * @since 6.6 + */ + public static LocalDate parseLocalDate(String s) { + try { + return LocalDate.parse(s); + } catch (DateTimeParseException e) { + throw MessageException.of("The date '" + s + "' does not respect format '" + DATE_FORMAT + "'", e); + } + } + + /** + * Parse format {@link #DATE_FORMAT}. This method never throws exception. + * + * @param s any string + * @return the date, {@code null} if parsing error or if parameter is {@code null} + * @since 6.6 + */ + @CheckForNull + public static LocalDate parseLocalDateQuietly(@Nullable String s) { + LocalDate date = null; + if (s != null) { + try { + date = parseLocalDate(s); + } catch (RuntimeException e) { + // ignore + } + + } + return date; + } + + /** * @param s string in format {@link #DATETIME_FORMAT} * @throws SonarException when string cannot be parsed */ - public static Date parseDateTime(String s) { - ParsePosition pos = new ParsePosition(0); - Date result = THREAD_SAFE_DATETIME_FORMAT.parse(s, pos); - if (pos.getIndex() != s.length()) { - throw new SonarException("The date '" + s + "' does not respect format '" + DATETIME_FORMAT + "'"); + return Date.from(parseOffsetDateTime(s).toInstant()); + } + + /** + * @param s string in format {@link #DATETIME_FORMAT} + * @throws SonarException when string cannot be parsed + * @since 6.6 + */ + public static OffsetDateTime parseOffsetDateTime(String s) { + try { + return OffsetDateTime.parse(s, DATETIME_FORMATTER); + } catch (DateTimeParseException e) { + throw MessageException.of("The date '" + s + "' does not respect format '" + DATETIME_FORMAT + "'", e); } - return result; } /** @@ -145,6 +196,28 @@ public final class DateUtils { } /** + * Parse format {@link #DATETIME_FORMAT}. This method never throws exception. + * + * @param s any string + * @return the datetime, {@code null} if parsing error or if parameter is {@code null} + * @since 6.6 + */ + @CheckForNull + public static OffsetDateTime parseOffsetDateTimeQuietly(@Nullable String s) { + OffsetDateTime datetime = null; + if (s != null) { + try { + datetime = parseOffsetDateTime(s); + } catch (RuntimeException e) { + // ignore + } + + } + return datetime; + } + + /** + * Warning: may rely on default timezone! * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime * @return the datetime, {@code null} if stringDate is null * @since 6.1 @@ -155,18 +228,19 @@ public final class DateUtils { return null; } - Date date = parseDateTimeQuietly(stringDate); - if (date != null) { - return date; + OffsetDateTime odt = parseOffsetDateTimeQuietly(stringDate); + if (odt != null) { + return Date.from(odt.toInstant()); } - date = parseDateQuietly(stringDate); - checkArgument(date != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate); + LocalDate ld = parseLocalDateQuietly(stringDate); + checkArgument(ld != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate); - return date; + return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()); } /** + * Warning: may rely on default timezone! * @see #parseDateOrDateTime(String) */ @CheckForNull @@ -176,7 +250,7 @@ public final class DateUtils { /** * Return the datetime if @param stringDate is a datetime, date + 1 day if stringDate is a date. - * So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT + * So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT (Warning: relies on default timezone!) * @see #parseDateOrDateTime(String) * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime * @return the datetime, {@code null} if stringDate is null @@ -208,49 +282,7 @@ public final class DateUtils { * @return the new date object with the amount added */ public static Date addDays(Date date, int numberOfDays) { - return org.apache.commons.lang.time.DateUtils.addDays(date, numberOfDays); + return Date.from(date.toInstant().plus(numberOfDays, ChronoUnit.DAYS)); } - static class ThreadSafeDateFormat extends DateFormat { - private final String format; - private final ThreadLocal<Reference<DateFormat>> cache = new ThreadLocal<Reference<DateFormat>>() { - @Override - public Reference<DateFormat> get() { - Reference<DateFormat> softRef = super.get(); - if (softRef == null || softRef.get() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(format); - sdf.setLenient(false); - softRef = new SoftReference<>(sdf); - super.set(softRef); - } - return softRef; - } - }; - - ThreadSafeDateFormat(String format) { - this.format = format; - } - - private DateFormat getDateFormat() { - return cache.get().get(); - } - - @Override - public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { - return getDateFormat().format(date, toAppendTo, fieldPosition); - } - - @Override - public Date parse(String source, ParsePosition pos) { - return getDateFormat().parse(source, pos); - } - - private void readObject(ObjectInputStream ois) throws NotSerializableException { - throw new NotSerializableException(); - } - - private void writeObject(ObjectOutputStream ois) throws NotSerializableException { - throw new NotSerializableException(); - } - } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java index 22b1cecf794..0cea02ff7ea 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java @@ -20,6 +20,7 @@ package org.sonar.api.utils; import java.net.URL; +import java.time.Clock; import java.util.Date; import java.util.Map; import java.util.Properties; @@ -72,7 +73,9 @@ public class System2 { /** * Shortcut for {@link System#currentTimeMillis()} + * @deprecated since 6.6 use {@link Clock} that is available in pico. */ + @Deprecated public long now() { return System.currentTimeMillis(); } 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 cda37e7996f..4c3dd17e2ef 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 @@ -22,16 +22,13 @@ package org.sonar.api.utils; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; -import java.util.ArrayList; import java.util.Date; -import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Fail.fail; import static org.sonar.api.utils.DateUtils.parseDate; import static org.sonar.api.utils.DateUtils.parseDateOrDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime; @@ -52,13 +49,13 @@ public class DateUtilsTest { @Test public void parseDate_not_valid_format() { - expectedException.expect(SonarException.class); + expectedException.expect(MessageException.class); DateUtils.parseDate("2010/05/18"); } @Test public void parseDate_not_lenient() { - expectedException.expect(SonarException.class); + expectedException.expect(MessageException.class); DateUtils.parseDate("2010-13-18"); } @@ -71,7 +68,7 @@ public class DateUtilsTest { @Test public void parseDate_fail_if_additional_characters() { - expectedException.expect(SonarException.class); + expectedException.expect(MessageException.class); DateUtils.parseDate("1986-12-04foo"); } @@ -83,13 +80,13 @@ public class DateUtilsTest { @Test public void parseDateTime_not_valid_format() { - expectedException.expect(SonarException.class); + expectedException.expect(MessageException.class); DateUtils.parseDate("2010/05/18 10:55"); } @Test public void parseDateTime_fail_if_additional_characters() { - expectedException.expect(SonarException.class); + expectedException.expect(MessageException.class); DateUtils.parseDateTime("1986-12-04T01:02:03+0300foo"); } @@ -103,7 +100,7 @@ public class DateUtilsTest { @Test public void shouldFormatDate() { assertThat(DateUtils.formatDate(new Date())).startsWith("20"); - assertThat(DateUtils.formatDate(new Date()).length()).isEqualTo(10); + assertThat(DateUtils.formatDate(new Date())).hasSize(10); } @Test @@ -194,80 +191,4 @@ public class DateUtilsTest { parseEndingDateOrDateTime("polop"); } - /** - * Cordially copied from XStream unit test - * See http://koders.com/java/fid8A231D75F2C6E6909FB26BCA11C12D08AD05FB50.aspx?s=ThreadSafeDateFormatTest - */ - @Test - public void shouldBeThreadSafe() throws Exception { - 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 = new ArrayList<>(); - - final ThreadGroup tg = new ThreadGroup("shouldBeThreadSafe") { - @Override - 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) { - - @Override - public void run() { - int i = 0; - try { - synchronized (this) { - notifyAll(); - wait(); - } - while (i < 1000 && !interrupted()) { - String formatted = format.format(now); - Thread.yield(); - assertThat(now).isEqualTo(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).isEmpty(); - assertThat(counter[0]).isGreaterThanOrEqualTo(threads.length); - } } |