From 0616121414c5962853bccfe75b22805b64424526 Mon Sep 17 00:00:00 2001 From: simonbrandhof Date: Fri, 25 Feb 2011 18:16:54 +0100 Subject: [PATCH] SONAR-2218 add NewCoverageDecorator + add DateUtils to API --- .../org/sonar/plugins/core/CorePlugin.java | 1 + .../timemachine/NewCoverageDecorator.java | 210 ++++++++++++++++++ .../timemachine/NewCoverageDecoratorTest.java | 183 +++++++++++++++ .../org/sonar/api/measures/CoreMetrics.java | 22 +- .../java/org/sonar/api/utils/DateUtils.java | 28 +++ .../org/sonar/api/utils/KeyValueFormat.java | 15 +- 6 files changed, 452 insertions(+), 7 deletions(-) create mode 100644 plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java create mode 100644 plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index 4d6063a9d71..a343985c1de 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -237,6 +237,7 @@ public class CorePlugin implements Plugin { extensions.add(ViolationPersisterDecorator.class); extensions.add(NewViolationsDecorator.class); extensions.add(TimeMachineConfigurationPersister.class); + extensions.add(NewCoverageDecorator.class); return extensions; } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java new file mode 100644 index 00000000000..18297718721 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java @@ -0,0 +1,210 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.timemachine; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.commons.lang.NumberUtils; +import org.apache.commons.lang.ObjectUtils; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.components.PastSnapshot; +import org.sonar.batch.components.TimeMachineConfiguration; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @since 2.7 + */ +public final class NewCoverageDecorator implements Decorator { + + private List structs; + + public NewCoverageDecorator(TimeMachineConfiguration timeMachineConfiguration) { + structs = Lists.newArrayList(); + for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { + structs.add(new PeriodStruct(pastSnapshot)); + } + } + + NewCoverageDecorator(List structs) { + this.structs = structs; + } + + + public boolean shouldExecuteOnProject(Project project) { + return project.isLatestAnalysis() && !structs.isEmpty(); + } + + private boolean shouldDecorate(Resource resource) { + return isFile(resource); + } + + @DependsUpon + public List dependsOnMetrics() { + return Arrays.asList(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, CoreMetrics.COVERAGE_LINE_HITS_DATA, + CoreMetrics.CONDITIONS_BY_LINE, CoreMetrics.COVERED_CONDITIONS_BY_LINE); + } + + @DependedUpon + public List generatesNewCoverageMetrics() { + return Arrays.asList(CoreMetrics.NEW_LINES_TO_COVER, CoreMetrics.NEW_UNCOVERED_LINES, + CoreMetrics.NEW_CONDITIONS_TO_COVER, CoreMetrics.NEW_UNCOVERED_CONDITIONS); + } + + public void decorate(Resource resource, DecoratorContext context) { + if (shouldDecorate(resource)) { + doDecorate(context); + } + } + + void doDecorate(DecoratorContext context) { + if (parse(context)) { + compute(context); + } + } + + private boolean parse(DecoratorContext context) { + Measure lastCommits = context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE); + Measure hitsByLineMeasure = context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA); + + if (lastCommits != null && lastCommits.hasData() && hitsByLineMeasure != null && hitsByLineMeasure.hasData()) { + Map datesByLine = KeyValueFormat.parseIntDateTime(lastCommits.getData()); + Map hitsByLine = parseCountByLine(hitsByLineMeasure); + Map conditionsByLine = parseCountByLine(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)); + Map coveredConditionsByLine = parseCountByLine(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)); + + reset(); + + for (Map.Entry entry : hitsByLine.entrySet()) { + int lineId = entry.getKey(); + int hits = entry.getValue(); + int conditions = (Integer)ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0); + int coveredConditions = (Integer)ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0); + Date date = datesByLine.get(lineId); + for (PeriodStruct struct : structs) { + struct.analyze(date, hits, conditions, coveredConditions); + } + } + + return true; + } + return false; + } + + private void reset() { + for (PeriodStruct struct : structs) { + struct.reset(); + } + } + + private void compute(DecoratorContext context) { + Measure newLines = new Measure(CoreMetrics.NEW_LINES_TO_COVER); + Measure newUncoveredLines = new Measure(CoreMetrics.NEW_UNCOVERED_LINES); + Measure newConditions = new Measure(CoreMetrics.NEW_CONDITIONS_TO_COVER); + Measure newUncoveredConditions = new Measure(CoreMetrics.NEW_UNCOVERED_CONDITIONS); + + for (PeriodStruct struct : structs) { + newLines.setVariation(struct.index, (double)struct.newLines); + newUncoveredLines.setVariation(struct.index, (double) (struct.newLines - struct.newCoveredLines)); + newConditions.setVariation(struct.index, (double)struct.newConditions); + newUncoveredConditions.setVariation(struct.index, (double)struct.newConditions-struct.newCoveredConditions); + } + + context.saveMeasure(newLines); + context.saveMeasure(newUncoveredLines); + context.saveMeasure(newConditions); + context.saveMeasure(newUncoveredConditions); + } + + + private Map parseCountByLine(Measure measure) { + if (measure != null && measure.hasData()) { + return KeyValueFormat.parseIntInt(measure.getData()); + } + return Maps.newHashMap(); + } + + + private boolean isFile(Resource resource) { + return Scopes.isFile(resource) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier()); + } + + public static final class PeriodStruct { + int index; + Date date; + int newLines = 0, newCoveredLines = 0, newConditions = 0, newCoveredConditions = 0; + + PeriodStruct(PastSnapshot pastSnapshot) { + this.index = pastSnapshot.getIndex(); + this.date = pastSnapshot.getDate(); + } + + PeriodStruct(int index, Date date) { + this.index = index; + this.date = date; + } + + void reset() { + newLines = 0; + newCoveredLines = 0; + newConditions = 0; + newCoveredConditions = 0; + } + + void analyze(Date lineDate, int hits, int conditions, int coveredConditions) { + if (lineDate == null) { + //TODO warning + + } else if (lineDate.after(date)) { + // TODO experiment if date comparison is faster or not + addLine(hits > 0); + addConditions(conditions, coveredConditions); + } + } + + void addLine(boolean covered) { + newLines += 1; + if (covered) { + newCoveredLines += 1; + } + } + + void addConditions(int count, int countCovered) { + newConditions += count; + if (count > 0) { + newCoveredConditions += countCovered; + } + } + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java new file mode 100644 index 00000000000..e94cb5b8518 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java @@ -0,0 +1,183 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.timemachine; + +import org.apache.commons.lang.NumberUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.utils.DateUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.mockito.Mockito.*; + +public class NewCoverageDecoratorTest { + + @Test + public void shouldDoNothingIfNoScmData() throws ParseException { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)) + .thenReturn(new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "1=10")); + + NewCoverageDecorator decorator = newDecorator(); + decorator.doDecorate(context); + verify(context, never()).saveMeasure((Measure) anyObject()); + } + + @Test + public void shouldDoNothingIfNoCoverageData() throws ParseException { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)) + .thenReturn(new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2008-05-18T00:00:00+0000")); + + NewCoverageDecorator decorator = newDecorator(); + decorator.doDecorate(context); + + verify(context, never()).saveMeasure((Measure) anyObject()); + } + + @Test + public void shouldGetNewLines() throws ParseException { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn( + new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3")); + when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( + new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); + + NewCoverageDecorator decorator = newDecorator(); + decorator.doDecorate(context); + + // line 11 has been updated after date1 (2009-12-25). This line is covered. + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 1, 1.0))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 1, 0.0))); + + // no line have been updated after date3 (2011-02-18) + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 3, 0.0))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 3, 0.0))); + + // no data on other periods + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 2, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 4, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 5, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 2, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 4, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 5, null))); + } + + @Test + public void shouldGetNewConditions() throws ParseException { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn( + new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3")); + when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn( + new Measure(CoreMetrics.CONDITIONS_BY_LINE, "11=4")); + when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn( + new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "11=1")); + when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( + new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); + + NewCoverageDecorator decorator = newDecorator(); + decorator.doDecorate(context); + + // line 11 has been updated after date1 (2009-12-25). This line has 1 covered condition amongst 4 + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 1, 4.0))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 1, 3.0))); + + // no line have been updated after date3 (2011-02-18) + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 3, 0.0))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 3, 0.0))); + + // no data on other periods + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 2, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 4, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 5, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 2, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 4, null))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 5, null))); + } + + @Test + public void shouldNotGetNewConditionsWhenNewLineHasNoConditions() throws ParseException { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn( + new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3")); + when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn( + new Measure(CoreMetrics.CONDITIONS_BY_LINE, "10=1")); + when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn( + new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "10=1")); + when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( + new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); + + NewCoverageDecorator decorator = newDecorator(); + decorator.doDecorate(context); + + // line 11 has been updated after date1 (2009-12-25) but it has no conditions + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 1, 0.0))); + verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 1, 0.0))); + } + + + static class VariationMatcher extends BaseMatcher { + Metric metric; + int index; + Double variation; + + VariationMatcher(Metric metric, int index, Double variation) { + this.metric = metric; + this.index = index; + this.variation = variation; + } + + public boolean matches(Object o) { + Measure m = (Measure)o; + if (m.getMetric().equals(metric)) { + if ((variation==null && m.getVariation(index)==null) || + (variation!=null && variation.equals(m.getVariation(index)))) { + return true; + } + } + return false; + } + + public void describeTo(Description description) { + + } + } + + private NewCoverageDecorator newDecorator() throws ParseException { + List structs = Arrays.asList( + new NewCoverageDecorator.PeriodStruct(1, newDate("2009-12-25")), + new NewCoverageDecorator.PeriodStruct(3, newDate("2011-02-18"))); + return new NewCoverageDecorator(structs); + } + + private Date newDate(String s) throws ParseException { + return new SimpleDateFormat(DateUtils.DATE_FORMAT).parse(s); + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index e88512934b9..59c1f66ed9a 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -224,6 +224,15 @@ public final class CoreMetrics { public static final Metric LINES_TO_COVER = new Metric(LINES_TO_COVER_KEY, "Lines to cover", "Lines to cover", Metric.ValueType.INT, Metric.DIRECTION_BETTER, false, DOMAIN_TESTS).setFormula(new SumChildValuesFormula(false)).setHidden(true); + public static final String NEW_LINES_TO_COVER_KEY = "new_lines_to_cover"; + public static final Metric NEW_LINES_TO_COVER = new Metric.Builder(NEW_LINES_TO_COVER_KEY, Metric.ValueType.INT) + .setName("New lines to cover") + .setDescription("New lines to cover") + .setDirection(Metric.DIRECTION_BETTER) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .setHidden(true) + .create(); public static final String UNCOVERED_LINES_KEY = "uncovered_lines"; public static final Metric UNCOVERED_LINES = new Metric.Builder(UNCOVERED_LINES_KEY, Metric.ValueType.INT) @@ -256,7 +265,15 @@ public final class CoreMetrics { public static final Metric CONDITIONS_TO_COVER = new Metric.Builder(CONDITIONS_TO_COVER_KEY, Metric.ValueType.INT) .setName("Conditions to cover") .setDescription("Conditions to cover") - .setDirection(Metric.DIRECTION_BETTER) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .setHidden(true) + .create(); + + public static final String NEW_CONDITIONS_TO_COVER_KEY = "new_conditions_to_cover"; + public static final Metric NEW_CONDITIONS_TO_COVER = new Metric.Builder(NEW_CONDITIONS_TO_COVER_KEY, Metric.ValueType.INT) + .setName("New conditions to cover") + .setDescription("New conditions to cover") .setDomain(DOMAIN_TESTS) .setFormula(new SumChildValuesFormula(false)) .setHidden(true) @@ -565,7 +582,6 @@ public final class CoreMetrics { Metric.ValueType.INT, Metric.DIRECTION_BETTER, false, DOMAIN_DESIGN).setHidden(true); - // Alerts public static final String ALERT_STATUS_KEY = "alert_status"; public static final Metric ALERT_STATUS = new Metric.Builder(ALERT_STATUS_KEY, Metric.ValueType.LEVEL) @@ -575,7 +591,7 @@ public final class CoreMetrics { .setQualitative(true) .setDomain(DOMAIN_GENERAL) .create(); - + /* quality profile */ public static final String PROFILE_KEY = "profile"; 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 new file mode 100644 index 00000000000..510cb0c0161 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java @@ -0,0 +1,28 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.utils; + +/** + * @since 2.7 + */ +public interface DateUtils { + String DATE_FORMAT = "yyyy-MM-dd"; + String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java index 8ba231af888..165152c3537 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/KeyValueFormat.java @@ -141,10 +141,10 @@ public final class KeyValueFormat { } public static class DateConverter extends Converter { - private DateFormat dateFormat; + private SimpleDateFormat dateFormat; public DateConverter() { - this("yyyy-MM-dd"); + this(DateUtils.DATE_FORMAT); } DateConverter(String format) { @@ -161,14 +161,14 @@ public final class KeyValueFormat { try { return StringUtils.isBlank(s) ? null : dateFormat.parse(s); } catch (ParseException e) { - throw new SonarException("Not a date: " + s, e); + throw new SonarException("Not a date with format: " + dateFormat.toPattern(), e); } } } public static class DateTimeConverter extends DateConverter { public DateTimeConverter() { - super("yyyy-MM-dd'T'HH:mm:ssZ"); + super(DateUtils.DATETIME_FORMAT); } } @@ -225,6 +225,13 @@ public final class KeyValueFormat { return parse(data, IntegerConverter.INSTANCE, new DateConverter()); } + /** + * @since 2.7 + */ + public static Map parseIntInt(String data) { + return parse(data, IntegerConverter.INSTANCE, IntegerConverter.INSTANCE); + } + /** * @since 2.7 */ -- 2.39.5