From 6d51e3018188eb40235a1d9d1a70d416c1910a31 Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Mon, 29 Jan 2024 15:48:33 +0100 Subject: [PATCH] SONAR-21455 Use a impact measure builder between all the components --- .../formula/ImpactSumFormula.java | 9 +- .../formula/MeasureImpactBuilder.java | 42 ------ .../projectanalysis/issue/IssueCounter.java | 15 +- .../formula/ImpactSumFormulaTest.java | 9 +- .../formula/MeasureImpactBuilderTest.java | 57 -------- .../issue/IssueCounterTest.java | 22 ++- .../server/measure/ImpactMeasureBuilder.java | 102 ++++++++++++++ .../measure/ImpactMeasureBuilderTest.java | 129 ++++++++++++++++++ .../server/measure/live/IssueCounter.java | 18 +-- .../live/MeasureUpdateFormulaFactoryImpl.java | 33 +---- 10 files changed, 276 insertions(+), 160 deletions(-) delete mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilder.java delete mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilderTest.java create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java create mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormula.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormula.java index 2fdf0ef7a83..5c31d780f4b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormula.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormula.java @@ -21,6 +21,7 @@ package org.sonar.ce.task.projectanalysis.formula; import java.util.Optional; import org.sonar.ce.task.projectanalysis.measure.Measure; +import org.sonar.server.measure.ImpactMeasureBuilder; import static java.util.Objects.requireNonNull; @@ -56,14 +57,14 @@ public class ImpactSumFormula implements Formula private boolean initialized = false; private boolean hasEmptyValue = false; - private final MeasureImpactBuilder measureImpactBuilder = new MeasureImpactBuilder(); + private final ImpactMeasureBuilder measureImpactBuilder = ImpactMeasureBuilder.createEmpty(); @Override public void aggregate(ImpactSumFormula.ImpactCounter counter) { Optional value = counter.getValue(); if (value.isPresent()) { initialized = true; - measureImpactBuilder.add(value.get()); + measureImpactBuilder.add(ImpactMeasureBuilder.fromString(value.get())); } else { hasEmptyValue = true; } @@ -75,7 +76,7 @@ public class ImpactSumFormula implements Formula String data = measureOptional.map(Measure::getData).orElse(null); if (data != null) { initialized = true; - measureImpactBuilder.add(data); + measureImpactBuilder.add(ImpactMeasureBuilder.fromString(data)); } else { hasEmptyValue = true; } @@ -83,7 +84,7 @@ public class ImpactSumFormula implements Formula public Optional getValue() { if (initialized && !hasEmptyValue) { - return Optional.ofNullable(measureImpactBuilder.build()); + return Optional.ofNullable(measureImpactBuilder.buildAsString()); } return Optional.empty(); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilder.java deleted file mode 100644 index bc194833114..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.formula; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Type; -import java.util.LinkedHashMap; -import java.util.Map; - -public class MeasureImpactBuilder { - private static final Type GSON_MAP_TYPE = new TypeToken>() { - }.getType(); - private static final Gson gson = new Gson(); - private final Map map = new LinkedHashMap<>(); - - public void add(String value) { - Map impactMap = gson.fromJson(value, GSON_MAP_TYPE); - impactMap.forEach((key, val) -> map.merge(key, val, Integer::sum)); - } - - public String build() { - return gson.toJson(map); - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java index 3d8502d2f6b..b2b8f2cd0e7 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java @@ -23,7 +23,6 @@ import com.google.common.collect.EnumMultiset; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multiset; -import com.google.gson.Gson; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; @@ -37,6 +36,7 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.measure.ImpactMeasureBuilder; import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; import static org.sonar.api.issue.Issue.STATUS_OPEN; @@ -126,8 +126,6 @@ public class IssueCounter extends IssueVisitor { .put(SECURITY_HOTSPOT, NEW_SECURITY_HOTSPOTS_KEY) .build(); - private static final Gson gson = new Gson(); - private final MetricRepository metricRepository; private final MeasureRepository measureRepository; private final NewIssueClassifier newIssueClassifier; @@ -191,7 +189,7 @@ public class IssueCounter extends IssueVisitor { private void addMeasuresByImpact(Component component) { for (Map.Entry> impactEntry : currentCounters.counter().impactsBag.entrySet()) { - String json = gson.toJson(impactEntry.getValue()); + String json = ImpactMeasureBuilder.fromMap(impactEntry.getValue()).buildAsString(); addMeasure(component, IMPACT_TO_METRIC_KEY.get(impactEntry.getKey()), json); } } @@ -267,12 +265,7 @@ public class IssueCounter extends IssueVisitor { private void initImpactsBag() { for (SoftwareQuality quality : SoftwareQuality.values()) { - Map severityMap = new HashMap<>(); - for (Severity severity : Severity.values()) { - severityMap.put(severity.name(), 0L); - } - severityMap.put("total", 0L); - impactsBag.put(quality.name(), severityMap); + impactsBag.put(quality.name(), ImpactMeasureBuilder.createEmpty().buildAsMap()); } } @@ -336,7 +329,7 @@ public class IssueCounter extends IssueVisitor { for (Map.Entry impact : issue.impacts().entrySet()) { impactsBag.compute(impact.getKey().name(), (key, value) -> { value.compute(impact.getValue().name(), (severity, count) -> count == null ? 1 : count + 1); - value.compute("total", (total, count) -> count == null ? 1 : count + 1); + value.compute(ImpactMeasureBuilder.TOTAL_KEY, (total, count) -> count == null ? 1 : count + 1); return value; }); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormulaTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormulaTest.java index 7c69d94e956..b0eadc31a14 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormulaTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/ImpactSumFormulaTest.java @@ -20,7 +20,7 @@ package org.sonar.ce.task.projectanalysis.formula; import com.google.gson.Gson; -import java.util.Map; +import java.util.LinkedHashMap; import java.util.Optional; import javax.annotation.Nullable; import org.junit.Test; @@ -131,7 +131,12 @@ public class ImpactSumFormulaTest { public String newImpactJson(Integer total, Integer high, Integer medium, Integer low) { - return gson.toJson(Map.of("total", total, Severity.HIGH.name(), high, Severity.MEDIUM.name(), medium, Severity.LOW.name(), low)); + LinkedHashMap map = new LinkedHashMap<>(); + map.put(Severity.LOW.name(), low); + map.put(Severity.MEDIUM.name(), medium); + map.put(Severity.HIGH.name(), high); + map.put("total", total); + return gson.toJson(map); } private void addMeasure(String metricKey, @Nullable String value) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilderTest.java deleted file mode 100644 index 7eb308e3740..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/formula/MeasureImpactBuilderTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.formula; - -import com.google.gson.Gson; -import java.util.Map; -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.sonar.api.issue.impact.Severity; - - -public class MeasureImpactBuilderTest { - - Gson gson = new Gson(); - - @Test - public void add_shouldAddExpectedInitialValues() { - MeasureImpactBuilder measureImpactBuilder = new MeasureImpactBuilder(); - Assertions.assertThat(measureImpactBuilder.build()).isEqualTo("{}"); - - measureImpactBuilder = new MeasureImpactBuilder(); - Map map = Map.of("total", 3, Severity.HIGH.name(), 2, Severity.MEDIUM.name(), 1, Severity.LOW.name(), 4); - measureImpactBuilder.add(gson.toJson(map)); - Assertions.assertThat(measureImpactBuilder.build()).isEqualTo(gson.toJson(map)); - } - - @Test - public void add_shouldMergeValuesFromDifferentMaps() { - - MeasureImpactBuilder measureImpactBuilder = new MeasureImpactBuilder(); - Map map = Map.of("total", 3, Severity.HIGH.name(), 2, Severity.MEDIUM.name(), 1, Severity.LOW.name(), 4); - measureImpactBuilder.add(gson.toJson(map)); - - Map map2 = Map.of("total", 6, Severity.HIGH.name(), 4, Severity.MEDIUM.name(), 2, Severity.LOW.name(), 1); - measureImpactBuilder.add(gson.toJson(map2)); - - Assertions.assertThat(measureImpactBuilder.build()).isEqualTo(gson.toJson(Map.of("total", 9, Severity.HIGH.name(), 6, Severity.MEDIUM.name(), 3, Severity.LOW.name(), 5))); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java index 73034bf9774..49d8ca5bd2f 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java @@ -20,12 +20,11 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.gson.Gson; -import java.lang.constant.Constable; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.assertj.core.data.MapEntry; import org.junit.Rule; @@ -42,6 +41,7 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule; import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.rule.RuleTesting; +import org.sonar.server.measure.ImpactMeasureBuilder; import static java.util.Arrays.stream; import static org.assertj.core.api.Assertions.assertThat; @@ -360,14 +360,22 @@ public class IssueCounterTest { Set> entries = measureRepository.getRawMeasures(FILE1).entrySet(); - assertSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, Map.of(HIGH, 2, MEDIUM, 2, LOW, 0, "total", 4), entries); - assertSoftwareQualityMeasures(SoftwareQuality.SECURITY, Map.of(HIGH, 1, MEDIUM, 1, LOW, 0, "total", 2), entries); - assertSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, Map.of(HIGH, 0, MEDIUM, 0, LOW, 0, "total", 0), entries); + assertSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(4, 2, 2, 0), entries); + assertSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(2, 1, 1, 0), entries); + assertSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(0, 0, 0, 0), entries); } - private void assertSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map expectedRaw, + private static Map getImpactMeasure(long total, long high, long medium, long low) { + Map map = new LinkedHashMap<>(); + map.put(LOW.name(), low); + map.put(MEDIUM.name(), medium); + map.put(HIGH.name(), high); + map.put(ImpactMeasureBuilder.TOTAL_KEY, total); + return map; + } + + private void assertSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map expectedMap, Set> actualRaw) { - Map expectedMap = expectedRaw.entrySet().stream().collect(Collectors.toMap(k -> k.getKey().toString(), v -> v.getValue().longValue())); Map.Entry softwareQualityMap = actualRaw.stream() .filter(e -> e.getKey().equals(IMPACT_TO_METRIC_KEY.get(softwareQuality.name()))) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java new file mode 100644 index 00000000000..a60e7e10999 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.measure; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import org.sonar.api.issue.impact.Severity; + +import static org.sonar.api.utils.Preconditions.checkArgument; + +/** + * Builder class to help build measures based on impacts with payload such as @{link {@link org.sonar.api.measures.CoreMetrics#RELIABILITY_ISSUES}}. + */ +public class ImpactMeasureBuilder { + + private static final Gson GSON = new GsonBuilder().create(); + private static final Type GSON_MAP_TYPE = new TypeToken>() { + }.getType(); + public static final String TOTAL_KEY = "total"; + + private final Map map; + + private ImpactMeasureBuilder(Map map) { + this.map = new LinkedHashMap<>(map); + } + + public static ImpactMeasureBuilder createEmpty() { + Map severityMap = new LinkedHashMap<>(); + for (Severity severity : Severity.values()) { + severityMap.put(severity.name(), 0L); + } + severityMap.put(TOTAL_KEY, 0L); + return new ImpactMeasureBuilder(severityMap); + } + + public static ImpactMeasureBuilder newInstance() { + return new ImpactMeasureBuilder(new LinkedHashMap<>()); + } + + public static ImpactMeasureBuilder fromMap(Map map) { + checkImpactMap(map); + return new ImpactMeasureBuilder(map); + } + + private static void checkImpactMap(Map map) { + checkArgument(map.containsKey(TOTAL_KEY), "Map must contain a total key"); + Arrays.stream(Severity.values()).forEach(severity -> checkArgument(map.containsKey(severity.name()), "Map must contain a key for severity " + severity.name())); + } + + public static ImpactMeasureBuilder fromString(String value) { + Map impactMap = GSON.fromJson(value, GSON_MAP_TYPE); + checkImpactMap(impactMap); + return new ImpactMeasureBuilder(impactMap); + } + + public ImpactMeasureBuilder setSeverity(Severity severity, long value) { + map.put(severity.name(), value); + return this; + } + + public ImpactMeasureBuilder setTotal(long value) { + map.put(TOTAL_KEY, value); + return this; + } + + public ImpactMeasureBuilder add(ImpactMeasureBuilder other) { + other.buildAsMap().forEach((key, val) -> map.merge(key, val, Long::sum)); + return this; + } + + public String buildAsString() { + checkImpactMap(map); + return GSON.toJson(map); + } + + public Map buildAsMap() { + checkImpactMap(map); + return map; + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java new file mode 100644 index 00000000000..edd4f188d4d --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java @@ -0,0 +1,129 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.measure; + +import java.util.Map; +import org.junit.Test; +import org.sonar.api.issue.impact.Severity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ImpactMeasureBuilderTest { + + @Test + public void createEmptyMeasure_shouldReturnMeasureWithAllFields() { + ImpactMeasureBuilder builder = ImpactMeasureBuilder.createEmpty(); + assertThat(builder.buildAsMap()) + .containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L)); + } + + private static Map getImpactMap(Long total, Long high, Long medium, Long low) { + return Map.of("total", total, Severity.HIGH.name(), high, Severity.MEDIUM.name(), medium, Severity.LOW.name(), low); + } + + @Test + public void fromMap_shouldInitializeCorrectlyTheBuilder() { + Map map = getImpactMap(6L, 3L, 2L, 1L); + ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(map); + assertThat(builder.buildAsMap()) + .isEqualTo(map); + } + + @Test + public void fromMap_whenMissingField_shouldThrowException() { + Map map = Map.of(); + assertThatThrownBy(() -> ImpactMeasureBuilder.fromMap(map)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Map must contain a total key"); + } + + @Test + public void toString_shouldInitializeCorrectlyTheBuilder() { + ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromString(""" + { + total: 6, + HIGH: 3, + MEDIUM: 2, + LOW: 1 + } + """); + assertThat(builder.buildAsMap()) + .isEqualTo(getImpactMap(6L, 3L, 2L, 1L)); + } + + @Test + public void buildAsMap_whenIsEmpty_shouldThrowException() { + ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); + assertThatThrownBy(impactMeasureBuilder::buildAsMap) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Map must contain a total key"); + } + + @Test + public void buildAsMap_whenMissingSeverity_shouldThrowException() { + ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() + .setTotal(1L) + .setSeverity(Severity.HIGH, 1L) + .setSeverity(Severity.MEDIUM, 1L); + assertThatThrownBy(impactMeasureBuilder::buildAsMap) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Map must contain a key for severity LOW"); + } + + @Test + public void buildAsString_whenMissingSeverity_shouldThrowException() { + ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() + .setTotal(1L) + .setSeverity(Severity.HIGH, 1L) + .setSeverity(Severity.MEDIUM, 1L); + assertThatThrownBy(impactMeasureBuilder::buildAsString) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Map must contain a key for severity LOW"); + } + + @Test + public void setSeverity_shouldInitializeSeverityValues() { + ImpactMeasureBuilder builder = ImpactMeasureBuilder.newInstance() + .setSeverity(Severity.HIGH, 3L) + .setSeverity(Severity.MEDIUM, 2L) + .setSeverity(Severity.LOW, 1L) + .setTotal(6L); + assertThat(builder.buildAsMap()) + .isEqualTo(getImpactMap(6L, 3L, 2L, 1L)); + } + + @Test + public void add_shouldSumImpactsAndTotal() { + ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L)) + .add(ImpactMeasureBuilder.newInstance().setTotal(6L).setSeverity(Severity.HIGH, 3L).setSeverity(Severity.MEDIUM, 2L).setSeverity(Severity.LOW, 1L)); + assertThat(builder.buildAsMap()) + .isEqualTo(getImpactMap(12L, 6L, 4L, 2L)); + } + + @Test + public void add_whenOtherMapHasMissingField_shouldThrowException() { + ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); + assertThatThrownBy(() -> impactMeasureBuilder.add(ImpactMeasureBuilder.newInstance())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Map must contain a total key"); + } + +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java index 34f6f1520aa..11cc56ac454 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java @@ -19,8 +19,6 @@ */ package org.sonar.server.measure.live; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; @@ -34,6 +32,7 @@ import org.sonar.api.rules.RuleType; import org.sonar.db.issue.IssueGroupDto; import org.sonar.db.issue.IssueImpactGroupDto; import org.sonar.db.rule.SeverityUtil; +import org.sonar.server.measure.ImpactMeasureBuilder; import static org.sonar.api.rule.Severity.INFO; import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; @@ -50,7 +49,6 @@ class IssueCounter { private final Count unresolved = new Count(); private final Count highImpactAccepted = new Count(); private final Map> bySoftwareQualityAndSeverity = new EnumMap<>(SoftwareQuality.class); - private final Gson gson = new GsonBuilder().create(); IssueCounter(Collection groups, Collection impactGroups) { for (IssueGroupDto group : groups) { @@ -172,20 +170,18 @@ class IssueCounter { public String getBySoftwareQuality(SoftwareQuality softwareQuality) { Map severityToCount = bySoftwareQualityAndSeverity.get(softwareQuality); - Map impactMap = new HashMap<>(); + ImpactMeasureBuilder impactMeasureBuilder; if (severityToCount != null) { - impactMap.put("total", severityToCount.values().stream().mapToLong(count -> count.absolute).sum()); + impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); for (Severity severity : Severity.values()) { - impactMap.put(severity.name(), Optional.ofNullable(severityToCount.get(severity)).map(count -> count.absolute).orElse(0L)); + impactMeasureBuilder = impactMeasureBuilder.setSeverity(severity, Optional.ofNullable(severityToCount.get(severity)).map(count -> count.absolute).orElse(0L)); } + impactMeasureBuilder = impactMeasureBuilder.setTotal(severityToCount.values().stream().mapToLong(count -> count.absolute).sum()); } else { - impactMap.put("total", 0L); - for (Severity severity : Severity.values()) { - impactMap.put(severity.name(), 0L); - } + impactMeasureBuilder = ImpactMeasureBuilder.createEmpty(); } - return gson.toJson(impactMap); + return impactMeasureBuilder.buildAsString(); } private static class Count { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java index 4b877d8c535..f70214658ae 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java @@ -19,24 +19,18 @@ */ package org.sonar.server.measure.live; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Type; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; import java.util.function.BiConsumer; -import java.util.stream.Collectors; import org.sonar.api.issue.Issue; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; +import org.sonar.server.measure.ImpactMeasureBuilder; import org.sonar.server.measure.Rating; import static java.util.Arrays.asList; @@ -255,9 +249,6 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact private static final Set FORMULA_METRICS = MeasureUpdateFormulaFactory.extractMetrics(FORMULAS); - private static final Gson GSON = new GsonBuilder().create(); - private static final Type MAP_TYPE = new TypeToken>() {}.getType(); - private static double debtDensity(MeasureUpdateFormula.Context context) { double debt = Math.max(context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0D), 0.0D); Optional devCost = context.getText(CoreMetrics.DEVELOPMENT_COST).map(Double::parseDouble); @@ -305,22 +296,12 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact private static class ImpactAddChildren implements BiConsumer { @Override public void accept(MeasureUpdateFormula.Context context, MeasureUpdateFormula formula) { - List> measures = context.getChildrenTextValues().stream() - .map(ImpactAddChildren::toMap) - .collect(Collectors.toList()); - context.getText(formula.getMetric()).ifPresent(value -> measures.add(toMap(value))); - - Map newValue = new HashMap<>(); - newValue.put("total", measures.stream().mapToLong(map -> map.get("total")).sum()); - for (org.sonar.api.issue.impact.Severity severity : org.sonar.api.issue.impact.Severity.values()) { - newValue.put(severity.name(), measures.stream().mapToLong(map -> map.get(severity.name())).sum()); - } - - context.setValue(GSON.toJson(newValue)); - } - - private static Map toMap(String value) { - return GSON.fromJson(value, MAP_TYPE); + ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.createEmpty(); + context.getChildrenTextValues().stream() + .map(ImpactMeasureBuilder::fromString) + .forEach(impactMeasureBuilder::add); + context.getText(formula.getMetric()).ifPresent(value -> impactMeasureBuilder.add(ImpactMeasureBuilder.fromString(value))); + context.setValue(impactMeasureBuilder.buildAsString()); } } -- 2.39.5