You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MeasureMatrix.java 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.measure.live;
  21. import com.google.common.collect.ArrayTable;
  22. import com.google.common.collect.Table;
  23. import java.math.BigDecimal;
  24. import java.math.RoundingMode;
  25. import java.util.Arrays;
  26. import java.util.Collection;
  27. import java.util.HashMap;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Objects;
  31. import java.util.Optional;
  32. import java.util.Set;
  33. import java.util.function.Consumer;
  34. import java.util.stream.Collectors;
  35. import java.util.stream.Stream;
  36. import javax.annotation.Nullable;
  37. import org.sonar.db.component.ComponentDto;
  38. import org.sonar.db.measure.LiveMeasureDto;
  39. import org.sonar.db.metric.MetricDto;
  40. import org.sonar.server.measure.Rating;
  41. import static com.google.common.base.Preconditions.checkArgument;
  42. import static java.util.Objects.requireNonNull;
  43. /**
  44. * Keep the measures in memory during refresh of live measures:
  45. * <ul>
  46. * <li>the values of last analysis, restricted to the needed metrics</li>
  47. * <li>the refreshed values</li>
  48. * </ul>
  49. */
  50. class MeasureMatrix {
  51. // component uuid -> metric key -> measure
  52. private final Table<String, String, MeasureCell> table;
  53. private final Map<String, MetricDto> metricsByKeys = new HashMap<>();
  54. private final Map<String, MetricDto> metricsByUuids = new HashMap<>();
  55. MeasureMatrix(Collection<ComponentDto> components, Collection<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
  56. this(components.stream().map(ComponentDto::uuid).collect(Collectors.toSet()), metrics, dbMeasures);
  57. }
  58. MeasureMatrix(Set<String> componentUuids, Collection<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
  59. for (MetricDto metric : metrics) {
  60. this.metricsByKeys.put(metric.getKey(), metric);
  61. this.metricsByUuids.put(metric.getUuid(), metric);
  62. }
  63. this.table = ArrayTable.create(componentUuids, metricsByKeys.keySet());
  64. for (LiveMeasureDto dbMeasure : dbMeasures) {
  65. table.put(dbMeasure.getComponentUuid(), metricsByUuids.get(dbMeasure.getMetricUuid()).getKey(), new MeasureCell(dbMeasure));
  66. }
  67. }
  68. MetricDto getMetricByUuid(String uuid) {
  69. return requireNonNull(metricsByUuids.get(uuid), () -> String.format("Metric with uuid %s not found", uuid));
  70. }
  71. private MetricDto getMetric(String key) {
  72. return requireNonNull(metricsByKeys.get(key), () -> String.format("Metric with key %s not found", key));
  73. }
  74. Optional<LiveMeasureDto> getMeasure(ComponentDto component, String metricKey) {
  75. checkArgument(table.containsColumn(metricKey), "Metric with key %s is not registered", metricKey);
  76. MeasureCell cell = table.get(component.uuid(), metricKey);
  77. return cell == null ? Optional.empty() : Optional.of(cell.measure);
  78. }
  79. void setValue(ComponentDto component, String metricKey, double value) {
  80. changeCell(component, metricKey, m -> m.setValue(scale(getMetric(metricKey), value)));
  81. }
  82. void setValue(ComponentDto component, String metricKey, Rating value) {
  83. changeCell(component, metricKey, m -> {
  84. m.setData(value.name());
  85. m.setValue((double) value.getIndex());
  86. });
  87. }
  88. void setValue(ComponentDto component, String metricKey, @Nullable String data) {
  89. changeCell(component, metricKey, m -> m.setData(data));
  90. }
  91. Stream<LiveMeasureDto> getChanged() {
  92. return table.values().stream()
  93. .filter(Objects::nonNull)
  94. .filter(MeasureCell::isChanged)
  95. .map(MeasureCell::getMeasure);
  96. }
  97. private void changeCell(ComponentDto component, String metricKey, Consumer<LiveMeasureDto> changer) {
  98. MeasureCell cell = table.get(component.uuid(), metricKey);
  99. if (cell == null) {
  100. LiveMeasureDto measure = new LiveMeasureDto()
  101. .setComponentUuid(component.uuid())
  102. .setProjectUuid(component.branchUuid())
  103. .setMetricUuid(metricsByKeys.get(metricKey).getUuid());
  104. cell = new MeasureCell(measure);
  105. table.put(component.uuid(), metricKey, cell);
  106. }
  107. changer.accept(cell.getMeasure());
  108. }
  109. /**
  110. * Round a measure value by applying the scale defined on the metric.
  111. * Example: scale(0.1234) returns 0.12 if metric scale is 2
  112. */
  113. private static double scale(MetricDto metric, double value) {
  114. if (metric.getDecimalScale() == null) {
  115. return value;
  116. }
  117. BigDecimal bd = BigDecimal.valueOf(value);
  118. return bd.setScale(metric.getDecimalScale(), RoundingMode.HALF_UP).doubleValue();
  119. }
  120. private static class MeasureCell {
  121. private final LiveMeasureDto measure;
  122. private final Double initialValue;
  123. private final byte[] initialData;
  124. private final String initialTextValue;
  125. private MeasureCell(LiveMeasureDto measure) {
  126. this.measure = measure;
  127. this.initialValue = measure.getValue();
  128. this.initialData = measure.getData();
  129. this.initialTextValue = measure.getTextValue();
  130. }
  131. public LiveMeasureDto getMeasure() {
  132. return measure;
  133. }
  134. public boolean isChanged() {
  135. return !Objects.equals(initialValue, measure.getValue()) || !Arrays.equals(initialData, measure.getData()) || !Objects.equals(initialTextValue, measure.getTextValue());
  136. }
  137. }
  138. }