]> source.dussan.org Git - sonarqube.git/blob
f17af1b78f899b45061859dacdbbc866991733fd
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
21
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;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Set;
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;
43
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;
47
48 public class LoadMeasureComputersStep implements ComputationStep {
49
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;
54
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())
61       .map(Metric::getKey)
62       .collect(Collectors.toSet());
63   }
64
65   /**
66    * Constructor override used by the ioc container to instantiate the class when no plugin is defining metrics
67    */
68   @Autowired(required = false)
69   public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, MeasureComputer[] measureComputers) {
70     this(measureComputersHolder, new Metrics[] {}, measureComputers);
71   }
72
73   /**
74    * Constructor override used by the ioc container to instantiate the class when no plugin is defining measure computers
75    */
76   @Autowired(required = false)
77   public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories) {
78     this(measureComputersHolder, metricsRepositories, new MeasureComputer[] {});
79   }
80
81   /**
82    * Constructor override used by the ioc container to instantiate the class when no plugin is defining metrics neither measure computers
83    */
84   @Autowired(required = false)
85   public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder) {
86     this(measureComputersHolder, new Metrics[] {}, new MeasureComputer[] {});
87   }
88
89   @Override
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));
94   }
95
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);
102
103     DirectAcyclicGraph dag = new DirectAcyclicGraph();
104     for (MeasureComputerWrapper computer : wrappers) {
105       dag.add(computer);
106       for (MeasureComputerWrapper dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
107         dag.add(computer, dependency);
108       }
109       for (MeasureComputerWrapper generates : getDependents(computer, toComputerByInputMetricKey)) {
110         dag.add(generates, computer);
111       }
112     }
113     return dag.sort();
114   }
115
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);
121       }
122       for (String inputMetric : computer.getDefinition().getInputMetrics()) {
123         computersByInputMetric.put(inputMetric, computer);
124       }
125     }
126   }
127
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);
133   }
134
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());
141   }
142
143   private static Collection<MeasureComputerWrapper> getDependents(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByInputMetricKey) {
144     return measureComputer.getDefinition().getInputMetrics().stream()
145       .map(toComputerByInputMetricKey)
146       .collect(Collectors.toList());
147   }
148
149   private static class ToComputerByKey implements Function<String, MeasureComputerWrapper> {
150     private final Map<String, MeasureComputerWrapper> computersByMetric;
151
152     private ToComputerByKey(Map<String, MeasureComputerWrapper> computersByMetric) {
153       this.computersByMetric = computersByMetric;
154     }
155
156     @Override
157     public MeasureComputerWrapper apply(@Nonnull String metricKey) {
158       return computersByMetric.get(metricKey);
159     }
160   }
161
162   private enum ToMeasureWrapper implements Function<MeasureComputer, MeasureComputerWrapper> {
163     INSTANCE;
164
165     @Override
166     public MeasureComputerWrapper apply(@Nonnull MeasureComputer measureComputer) {
167       MeasureComputerDefinition def = measureComputer.define(MeasureComputerDefinitionImpl.BuilderImpl::new);
168       return new MeasureComputerWrapper(measureComputer, validateDef(def));
169     }
170
171     private static MeasureComputerDefinition validateDef(MeasureComputerDefinition def) {
172       if (def instanceof MeasureComputerDefinitionImpl) {
173         return def;
174       }
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))
181         .build();
182     }
183   }
184
185   private enum ToInputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
186     INSTANCE;
187
188     @Override
189     public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
190       return input.getDefinition().getInputMetrics();
191     }
192   }
193
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);
197   }
198
199   private enum ToOutputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
200     INSTANCE;
201
202     @Override
203     public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
204       return input.getDefinition().getOutputMetrics();
205     }
206   }
207
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);
211   }
212
213   private static class ValidateUniqueOutputMetric {
214     private final Set<String> allOutputMetrics = new HashSet<>();
215
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);
221       }
222       return true;
223     }
224   }
225
226   @Override
227   public String getDescription() {
228     return "Load measure computers";
229   }
230 }