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.

ComponentTreeSort.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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.ws;
  21. import com.google.common.base.Function;
  22. import com.google.common.collect.ImmutableMap;
  23. import com.google.common.collect.Maps;
  24. import com.google.common.collect.Ordering;
  25. import com.google.common.collect.Table;
  26. import java.util.EnumSet;
  27. import java.util.HashMap;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Optional;
  31. import java.util.Set;
  32. import javax.annotation.Nonnull;
  33. import javax.annotation.Nullable;
  34. import org.jetbrains.annotations.NotNull;
  35. import org.sonar.api.measures.Metric;
  36. import org.sonar.api.measures.Metric.ValueType;
  37. import org.sonar.db.component.ComponentDto;
  38. import org.sonar.db.metric.MetricDto;
  39. import org.sonar.server.exceptions.BadRequestException;
  40. import org.sonar.server.measure.ImpactMeasureBuilder;
  41. import static java.lang.String.CASE_INSENSITIVE_ORDER;
  42. import static java.lang.String.format;
  43. import static org.sonar.api.measures.Metric.ValueType.BOOL;
  44. import static org.sonar.api.measures.Metric.ValueType.FLOAT;
  45. import static org.sonar.api.measures.Metric.ValueType.INT;
  46. import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
  47. import static org.sonar.api.measures.Metric.ValueType.PERCENT;
  48. import static org.sonar.api.measures.Metric.ValueType.RATING;
  49. import static org.sonar.api.measures.Metric.ValueType.STRING;
  50. import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
  51. import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_PERIOD_SORT;
  52. import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_SORT;
  53. import static org.sonar.server.measure.ws.ComponentTreeAction.NAME_SORT;
  54. import static org.sonar.server.measure.ws.ComponentTreeAction.PATH_SORT;
  55. import static org.sonar.server.measure.ws.ComponentTreeAction.QUALIFIER_SORT;
  56. public class ComponentTreeSort {
  57. private static final Set<ValueType> NUMERIC_VALUE_TYPES = EnumSet.of(BOOL, FLOAT, INT, MILLISEC, WORK_DUR, PERCENT, RATING);
  58. private static final Set<ValueType> TEXTUAL_VALUE_TYPES = EnumSet.of(STRING);
  59. private ComponentTreeSort() {
  60. // static method only
  61. }
  62. public static List<ComponentDto> sortComponents(List<ComponentDto> components, ComponentTreeRequest wsRequest, List<MetricDto> metrics,
  63. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  64. List<String> sortParameters = wsRequest.getSort();
  65. if (sortParameters == null || sortParameters.isEmpty()) {
  66. return components;
  67. }
  68. boolean isAscending = wsRequest.getAsc();
  69. Map<String, Ordering<ComponentDto>> orderingsBySortField = ImmutableMap.<String, Ordering<ComponentDto>>builder()
  70. .put(NAME_SORT, componentNameOrdering(isAscending))
  71. .put(QUALIFIER_SORT, componentQualifierOrdering(isAscending))
  72. .put(PATH_SORT, componentPathOrdering(isAscending))
  73. .put(METRIC_SORT, metricValueOrdering(wsRequest, metrics, measuresByComponentUuidAndMetric))
  74. .put(METRIC_PERIOD_SORT, metricPeriodOrdering(wsRequest, metrics, measuresByComponentUuidAndMetric))
  75. .build();
  76. String firstSortParameter = sortParameters.get(0);
  77. Ordering<ComponentDto> primaryOrdering = orderingsBySortField.get(firstSortParameter);
  78. if (sortParameters.size() > 1) {
  79. for (int i = 1; i < sortParameters.size(); i++) {
  80. String secondarySortParameter = sortParameters.get(i);
  81. Ordering<ComponentDto> secondaryOrdering = orderingsBySortField.get(secondarySortParameter);
  82. primaryOrdering = primaryOrdering.compound(secondaryOrdering);
  83. }
  84. }
  85. primaryOrdering = primaryOrdering.compound(componentNameOrdering(true));
  86. return primaryOrdering.immutableSortedCopy(components);
  87. }
  88. private static Ordering<ComponentDto> componentNameOrdering(boolean isAscending) {
  89. return stringOrdering(isAscending, ComponentDto::name);
  90. }
  91. private static Ordering<ComponentDto> componentQualifierOrdering(boolean isAscending) {
  92. return stringOrdering(isAscending, ComponentDto::qualifier);
  93. }
  94. private static Ordering<ComponentDto> componentPathOrdering(boolean isAscending) {
  95. return stringOrdering(isAscending, ComponentDto::path);
  96. }
  97. private static Ordering<ComponentDto> stringOrdering(boolean isAscending, Function<ComponentDto, String> function) {
  98. Ordering<String> ordering = Ordering.from(CASE_INSENSITIVE_ORDER);
  99. if (!isAscending) {
  100. ordering = ordering.reverse();
  101. }
  102. return ordering.nullsLast().onResultOf(function);
  103. }
  104. private static Ordering<ComponentDto> metricValueOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics,
  105. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  106. boolean isAscending = Optional.ofNullable(wsRequest.getAsc()).orElse(false);
  107. if (wsRequest.getMetricSort() == null) {
  108. return componentNameOrdering(isAscending);
  109. }
  110. Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
  111. MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());
  112. ValueType metricValueType = ValueType.valueOf(metric.getValueType());
  113. if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
  114. return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
  115. } else if (TEXTUAL_VALUE_TYPES.contains(metricValueType)) {
  116. return stringOrdering(isAscending, new ComponentDtoToTextualMeasureValue(metric, measuresByComponentUuidAndMetric));
  117. } else if (ValueType.LEVEL.equals(ValueType.valueOf(metric.getValueType()))) {
  118. return levelMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
  119. } else if (ValueType.DATA.equals(ValueType.valueOf(metric.getValueType()))
  120. && DataSupportedMetrics.IMPACTS_SUPPORTED_METRICS.contains(metric.getKey())) {
  121. return totalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
  122. }
  123. throw new IllegalStateException("Unrecognized metric value type: " + metric.getValueType());
  124. }
  125. private static Ordering<ComponentDto> metricPeriodOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics,
  126. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  127. boolean isAscending = Optional.ofNullable(wsRequest.getAsc()).orElse(false);
  128. if (wsRequest.getMetricSort() == null || wsRequest.getMetricPeriodSort() == null) {
  129. return componentNameOrdering(isAscending);
  130. }
  131. Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
  132. MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());
  133. ValueType metricValueType = ValueType.valueOf(metric.getValueType());
  134. if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
  135. return numericalMetricPeriodOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
  136. } else if (ValueType.DATA.equals(metricValueType)
  137. && DataSupportedMetrics.IMPACTS_SUPPORTED_METRICS.contains(metric.getKey())) {
  138. return totalNewPeriodMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
  139. }
  140. throw BadRequestException.create(format("Impossible to sort metric '%s' by measure period.", metric.getKey()));
  141. }
  142. private static Ordering<ComponentDto> numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric,
  143. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  144. Ordering<Double> ordering = getOrdering(isAscending);
  145. return ordering.onResultOf(new ComponentDtoToNumericalMeasureValue(metric, measuresByComponentUuidAndMetric));
  146. }
  147. @NotNull
  148. private static <T extends Comparable<T>> Ordering<T> getOrdering(boolean isAscending) {
  149. Ordering<T> ordering = Ordering.natural();
  150. if (!isAscending) {
  151. ordering = ordering.reverse();
  152. }
  153. return ordering.nullsLast();
  154. }
  155. private static Ordering<ComponentDto> totalMetricOrdering(boolean isAscending, MetricDto metric,
  156. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  157. Ordering<Long> ordering = getOrdering(isAscending);
  158. return ordering.onResultOf(new ComponentDtoToTotalImpactMeasureValue(metric, measuresByComponentUuidAndMetric, false));
  159. }
  160. private static Ordering<ComponentDto> totalNewPeriodMetricOrdering(boolean isAscending, MetricDto metric,
  161. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  162. Ordering<Long> ordering = getOrdering(isAscending);
  163. return ordering.onResultOf(new ComponentDtoToTotalImpactMeasureValue(metric, measuresByComponentUuidAndMetric, true));
  164. }
  165. private static Ordering<ComponentDto> numericalMetricPeriodOrdering(boolean isAscending, @Nullable MetricDto metric,
  166. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  167. Ordering<Double> ordering = getOrdering(isAscending);
  168. return ordering.onResultOf(new ComponentDtoToMeasureVariationValue(metric, measuresByComponentUuidAndMetric));
  169. }
  170. private static Ordering<ComponentDto> levelMetricOrdering(boolean isAscending, @Nullable MetricDto metric,
  171. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  172. // inverse the order of org.sonar.api.measures.Metric.Level enum
  173. Ordering<Integer> ordering = getOrdering(!isAscending);
  174. return ordering.onResultOf(new ComponentDtoToLevelIndex(metric, measuresByComponentUuidAndMetric));
  175. }
  176. private static class ComponentDtoToNumericalMeasureValue implements Function<ComponentDto, Double> {
  177. private final MetricDto metric;
  178. private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
  179. private ComponentDtoToNumericalMeasureValue(@Nullable MetricDto metric,
  180. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  181. this.metric = metric;
  182. this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
  183. }
  184. @Override
  185. public Double apply(@Nonnull ComponentDto input) {
  186. ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
  187. if (measure == null || !measure.isValueSet()) {
  188. return null;
  189. }
  190. return measure.getValue();
  191. }
  192. }
  193. private static class ComponentDtoToLevelIndex implements Function<ComponentDto, Integer> {
  194. private final MetricDto metric;
  195. private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
  196. private ComponentDtoToLevelIndex(@Nullable MetricDto metric,
  197. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  198. this.metric = metric;
  199. this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
  200. }
  201. @Override
  202. public Integer apply(@Nonnull ComponentDto input) {
  203. ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
  204. if (measure == null || measure.getData() == null) {
  205. return null;
  206. }
  207. return Metric.Level.names().indexOf(measure.getData());
  208. }
  209. }
  210. private static class ComponentDtoToMeasureVariationValue implements Function<ComponentDto, Double> {
  211. private final MetricDto metric;
  212. private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
  213. private ComponentDtoToMeasureVariationValue(@Nullable MetricDto metric,
  214. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  215. this.metric = metric;
  216. this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
  217. }
  218. @Override
  219. public Double apply(@Nonnull ComponentDto input) {
  220. ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
  221. if (measure == null || !metric.getKey().startsWith("new_")) {
  222. return null;
  223. }
  224. return measure.getValue();
  225. }
  226. }
  227. private static class ComponentDtoToTextualMeasureValue implements Function<ComponentDto, String> {
  228. private final MetricDto metric;
  229. private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
  230. private ComponentDtoToTextualMeasureValue(@Nullable MetricDto metric,
  231. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
  232. this.metric = metric;
  233. this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
  234. }
  235. @Override
  236. public String apply(@Nonnull ComponentDto input) {
  237. ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
  238. if (measure == null || measure.getData() == null) {
  239. return null;
  240. }
  241. return measure.getData();
  242. }
  243. }
  244. private static class ComponentDtoToTotalImpactMeasureValue implements Function<ComponentDto, Long> {
  245. private final MetricDto metric;
  246. private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
  247. private final boolean onlyNewPeriodMeasures;
  248. //Store the total value for each component to avoid multiple deserialization of the same measure
  249. Map<String, Long> totalByComponentUuid = new HashMap<>();
  250. private ComponentDtoToTotalImpactMeasureValue(@Nullable MetricDto metric,
  251. Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric, boolean onlyNewPeriodMeasures) {
  252. this.metric = metric;
  253. this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
  254. this.onlyNewPeriodMeasures = onlyNewPeriodMeasures;
  255. }
  256. @Override
  257. public Long apply(@Nonnull ComponentDto input) {
  258. if (onlyNewPeriodMeasures && metric != null && !metric.getKey().startsWith("new_")) {
  259. return null;
  260. }
  261. return totalByComponentUuid.computeIfAbsent(input.uuid(),
  262. k -> {
  263. ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
  264. return Optional.ofNullable(measure).map(ComponentTreeData.Measure::getData)
  265. .map(data -> ImpactMeasureBuilder.fromString(measure.getData()).getTotal())
  266. .orElse(null);
  267. });
  268. }
  269. }
  270. }