3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.step;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
28 import java.util.Objects;
30 import java.util.function.Function;
31 import java.util.stream.Collectors;
32 import javax.annotation.Nonnull;
33 import org.sonar.api.ce.measure.MeasureComputer;
34 import org.sonar.api.measures.CoreMetrics;
35 import org.sonar.api.measures.Metric;
36 import org.sonar.api.measures.Metrics;
37 import org.sonar.api.utils.dag.DirectAcyclicGraph;
38 import org.sonar.ce.task.projectanalysis.api.measurecomputer.MeasureComputerDefinitionImpl;
39 import org.sonar.ce.task.projectanalysis.api.measurecomputer.MeasureComputerWrapper;
40 import org.sonar.ce.task.projectanalysis.measure.MutableMeasureComputersHolder;
41 import org.sonar.ce.task.step.ComputationStep;
42 import org.springframework.beans.factory.annotation.Autowired;
44 import static com.google.common.base.Preconditions.checkState;
45 import static com.google.common.collect.FluentIterable.from;
46 import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition;
48 public class LoadMeasureComputersStep implements ComputationStep {
50 private static final Set<String> CORE_METRIC_KEYS = CoreMetrics.getMetrics().stream().map(Metric::getKey).collect(Collectors.toSet());
51 private final Set<String> pluginMetricKeys;
52 private final MutableMeasureComputersHolder measureComputersHolder;
53 private final MeasureComputer[] measureComputers;
55 @Autowired(required = false)
56 public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories, MeasureComputer[] measureComputers) {
57 this.measureComputersHolder = measureComputersHolder;
58 this.measureComputers = measureComputers;
59 this.pluginMetricKeys = Arrays.stream(metricsRepositories)
60 .flatMap(m -> m.getMetrics().stream())
62 .collect(Collectors.toSet());
66 * Constructor override used by the ioc container to instantiate the class when no plugin is defining metrics
68 @Autowired(required = false)
69 public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, MeasureComputer[] measureComputers) {
70 this(measureComputersHolder, new Metrics[] {}, measureComputers);
74 * Constructor override used by the ioc container to instantiate the class when no plugin is defining measure computers
76 @Autowired(required = false)
77 public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories) {
78 this(measureComputersHolder, metricsRepositories, new MeasureComputer[] {});
82 * Constructor override used by the ioc container to instantiate the class when no plugin is defining metrics neither measure computers
84 @Autowired(required = false)
85 public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder) {
86 this(measureComputersHolder, new Metrics[] {}, new MeasureComputer[] {});
90 public void execute(Context context) {
91 List<MeasureComputerWrapper> wrappers = Arrays.stream(measureComputers).map(ToMeasureWrapper.INSTANCE).collect(Collectors.toList());
92 validateMetrics(wrappers);
93 measureComputersHolder.setMeasureComputers(sortComputers(wrappers));
96 private static Iterable<MeasureComputerWrapper> sortComputers(List<MeasureComputerWrapper> wrappers) {
97 Map<String, MeasureComputerWrapper> computersByOutputMetric = new HashMap<>();
98 Map<String, MeasureComputerWrapper> computersByInputMetric = new HashMap<>();
99 feedComputersByMetric(wrappers, computersByOutputMetric, computersByInputMetric);
100 ToComputerByKey toComputerByOutputMetricKey = new ToComputerByKey(computersByOutputMetric);
101 ToComputerByKey toComputerByInputMetricKey = new ToComputerByKey(computersByInputMetric);
103 DirectAcyclicGraph dag = new DirectAcyclicGraph();
104 for (MeasureComputerWrapper computer : wrappers) {
106 for (MeasureComputerWrapper dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
107 dag.add(computer, dependency);
109 for (MeasureComputerWrapper generates : getDependents(computer, toComputerByInputMetricKey)) {
110 dag.add(generates, computer);
116 private static void feedComputersByMetric(List<MeasureComputerWrapper> wrappers, Map<String, MeasureComputerWrapper> computersByOutputMetric,
117 Map<String, MeasureComputerWrapper> computersByInputMetric) {
118 for (MeasureComputerWrapper computer : wrappers) {
119 for (String outputMetric : computer.getDefinition().getOutputMetrics()) {
120 computersByOutputMetric.put(outputMetric, computer);
122 for (String inputMetric : computer.getDefinition().getInputMetrics()) {
123 computersByInputMetric.put(inputMetric, computer);
128 private void validateMetrics(List<MeasureComputerWrapper> wrappers) {
129 wrappers.stream().flatMap(s -> ToInputMetrics.INSTANCE.apply(s).stream()).forEach(this::validateInputMetric);
130 wrappers.stream().flatMap(s -> ToOutputMetrics.INSTANCE.apply(s).stream()).forEach(this::validateOutputMetric);
131 ValidateUniqueOutputMetric validateUniqueOutputMetric = new ValidateUniqueOutputMetric();
132 wrappers.forEach(validateUniqueOutputMetric::validate);
135 private static Collection<MeasureComputerWrapper> getDependencies(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByOutputMetricKey) {
136 // Remove null computer because a computer can depend on a metric that is only generated by a sensor or on a core metrics
137 return measureComputer.getDefinition().getInputMetrics().stream()
138 .map(toComputerByOutputMetricKey)
139 .filter(Objects::nonNull)
140 .collect(Collectors.toList());
143 private static Collection<MeasureComputerWrapper> getDependents(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByInputMetricKey) {
144 return measureComputer.getDefinition().getInputMetrics().stream()
145 .map(toComputerByInputMetricKey)
146 .collect(Collectors.toList());
149 private static class ToComputerByKey implements Function<String, MeasureComputerWrapper> {
150 private final Map<String, MeasureComputerWrapper> computersByMetric;
152 private ToComputerByKey(Map<String, MeasureComputerWrapper> computersByMetric) {
153 this.computersByMetric = computersByMetric;
157 public MeasureComputerWrapper apply(@Nonnull String metricKey) {
158 return computersByMetric.get(metricKey);
162 private enum ToMeasureWrapper implements Function<MeasureComputer, MeasureComputerWrapper> {
166 public MeasureComputerWrapper apply(@Nonnull MeasureComputer measureComputer) {
167 MeasureComputerDefinition def = measureComputer.define(MeasureComputerDefinitionImpl.BuilderImpl::new);
168 return new MeasureComputerWrapper(measureComputer, validateDef(def));
171 private static MeasureComputerDefinition validateDef(MeasureComputerDefinition def) {
172 if (def instanceof MeasureComputerDefinitionImpl) {
175 // If the computer has not been created by the builder, we recreate it to make sure it's valid
176 Set<String> inputMetrics = def.getInputMetrics();
177 Set<String> outputMetrics = def.getOutputMetrics();
178 return new MeasureComputerDefinitionImpl.BuilderImpl()
179 .setInputMetrics(from(inputMetrics).toArray(String.class))
180 .setOutputMetrics(from(outputMetrics).toArray(String.class))
185 private enum ToInputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
189 public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
190 return input.getDefinition().getInputMetrics();
194 private void validateInputMetric(String metric) {
195 checkState(pluginMetricKeys.contains(metric) || CORE_METRIC_KEYS.contains(metric),
196 "Metric '%s' cannot be used as an input metric as it's not a core metric and no plugin declare this metric", metric);
199 private enum ToOutputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
203 public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
204 return input.getDefinition().getOutputMetrics();
208 private void validateOutputMetric(String metric) {
209 checkState(!CORE_METRIC_KEYS.contains(metric), "Metric '%s' cannot be used as an output metric because it's a core metric", metric);
210 checkState(pluginMetricKeys.contains(metric), "Metric '%s' cannot be used as an output metric because no plugins declare this metric", metric);
213 private static class ValidateUniqueOutputMetric {
214 private final Set<String> allOutputMetrics = new HashSet<>();
216 public boolean validate(@Nonnull MeasureComputerWrapper wrapper) {
217 for (String outputMetric : wrapper.getDefinition().getOutputMetrics()) {
218 checkState(!allOutputMetrics.contains(outputMetric),
219 "Output metric '%s' is already defined by another measure computer '%s'", outputMetric, wrapper.getComputer());
220 allOutputMetrics.add(outputMetric);
227 public String getDescription() {
228 return "Load measure computers";