3 * Copyright (C) 2009-2019 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.measure;
22 import com.google.common.base.Function;
23 import com.google.common.base.Predicate;
24 import com.google.common.collect.ImmutableSetMultimap;
25 import com.google.common.collect.SetMultimap;
26 import java.util.Collection;
27 import java.util.HashMap;
29 import java.util.Objects;
30 import java.util.Optional;
32 import javax.annotation.CheckForNull;
33 import javax.annotation.Nonnull;
34 import javax.annotation.Nullable;
35 import org.junit.rules.ExternalResource;
36 import org.sonar.ce.task.projectanalysis.component.Component;
37 import org.sonar.ce.task.projectanalysis.component.ComponentProvider;
38 import org.sonar.ce.task.projectanalysis.component.NoComponentProvider;
39 import org.sonar.ce.task.projectanalysis.component.TreeComponentProvider;
40 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
41 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderComponentProvider;
42 import org.sonar.ce.task.projectanalysis.metric.Metric;
43 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
45 import static com.google.common.base.Preconditions.checkArgument;
46 import static com.google.common.base.Preconditions.checkState;
47 import static com.google.common.collect.FluentIterable.from;
48 import static com.google.common.collect.Maps.filterKeys;
49 import static java.lang.String.format;
50 import static java.util.Objects.requireNonNull;
53 * An implementation of MeasureRepository as a JUnit rule which provides add methods for raw measures and extra add
54 * methods that takes component ref and metric keys thanks to the integration with various Component and Metric
57 public class MeasureRepositoryRule extends ExternalResource implements MeasureRepository {
58 private final ComponentProvider componentProvider;
60 private final MetricRepositoryRule metricRepositoryRule;
61 private final Map<InternalKey, Measure> baseMeasures = new HashMap<>();
62 private final Map<InternalKey, Measure> rawMeasures = new HashMap<>();
63 private final Map<InternalKey, Measure> initialRawMeasures = new HashMap<>();
64 private Collection<Component> loadedAsRawComponents;
65 private Collection<Metric> loadedAsRawMetrics;
66 private final Predicate<Map.Entry<InternalKey, Measure>> isAddedMeasure = new Predicate<Map.Entry<InternalKey, Measure>>() {
68 public boolean apply(@Nonnull Map.Entry<InternalKey, Measure> input) {
69 return !initialRawMeasures.containsKey(input.getKey())
70 || !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));
74 private MeasureRepositoryRule(ComponentProvider componentProvider, @Nullable MetricRepositoryRule metricRepositoryRule) {
75 this.componentProvider = componentProvider;
76 this.metricRepositoryRule = metricRepositoryRule;
80 protected void after() {
81 componentProvider.reset();
86 public static MeasureRepositoryRule create() {
87 return new MeasureRepositoryRule(NoComponentProvider.INSTANCE, null);
90 public static MeasureRepositoryRule create(TreeRootHolder treeRootHolder, MetricRepositoryRule metricRepositoryRule) {
91 return new MeasureRepositoryRule(new TreeRootHolderComponentProvider(treeRootHolder), requireNonNull(metricRepositoryRule));
94 public static MeasureRepositoryRule create(Component treeRoot, MetricRepositoryRule metricRepositoryRule) {
95 return new MeasureRepositoryRule(new TreeComponentProvider(treeRoot), requireNonNull(metricRepositoryRule));
98 public MeasureRepositoryRule addBaseMeasure(Component component, Metric metric, Measure measure) {
99 checkAndInitProvidersState();
101 InternalKey internalKey = new InternalKey(component, metric);
102 checkState(!baseMeasures.containsKey(internalKey),
103 format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", getRef(component), metric.getKey()));
105 baseMeasures.put(internalKey, measure);
110 public MeasureRepositoryRule addBaseMeasure(int componentRef, String metricKey, Measure measure) {
111 checkAndInitProvidersState();
113 InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
114 checkState(!baseMeasures.containsKey(internalKey), format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", componentRef, metricKey));
116 baseMeasures.put(internalKey, measure);
121 public SetMultimap<String, Measure> getRawMeasures(int componentRef) {
122 return getRawMeasures(componentProvider.getByRef(componentRef));
126 * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
127 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
129 public SetMultimap<String, Measure> getAddedRawMeasures(int componentRef) {
130 checkAndInitProvidersState();
132 return getAddedRawMeasures(componentProvider.getByRef(componentRef));
136 * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
137 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
139 public Optional<Measure> getAddedRawMeasure(Component component, String metricKey) {
140 return getAddedRawMeasure(component.getReportAttributes().getRef(), metricKey);
144 * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
145 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
147 public Optional<Measure> getAddedRawMeasure(int componentRef, String metricKey) {
148 checkAndInitProvidersState();
150 Set<Measure> measures = getAddedRawMeasures(componentProvider.getByRef(componentRef)).get(metricKey);
151 if (measures.isEmpty()) {
152 return Optional.empty();
154 checkArgument(measures.size() == 1, String.format("There is more than one measure on metric '%s' for component '%s'", metricKey, componentRef));
155 return Optional.of(measures.iterator().next());
159 * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
160 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
162 public SetMultimap<String, Measure> getAddedRawMeasures(Component component) {
163 checkAndInitProvidersState();
165 ImmutableSetMultimap.Builder<String, Measure> builder = ImmutableSetMultimap.builder();
166 for (Map.Entry<InternalKey, Measure> entry : from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isAddedMeasure)) {
167 builder.put(entry.getKey().getMetricKey(), entry.getValue());
169 return builder.build();
172 public MeasureRepositoryRule addRawMeasure(int componentRef, String metricKey, Measure measure) {
173 checkAndInitProvidersState();
175 InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
176 checkState(!rawMeasures.containsKey(internalKey), format(
177 "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
178 componentRef, metricKey));
180 rawMeasures.put(internalKey, measure);
181 initialRawMeasures.put(internalKey, measure);
187 public Optional<Measure> getBaseMeasure(Component component, Metric metric) {
188 return Optional.ofNullable(baseMeasures.get(new InternalKey(component, metric)));
191 public Collection<Component> getComponentsLoadedAsRaw() {
192 return loadedAsRawComponents;
195 public Collection<Metric> getMetricsLoadedAsRaw() {
196 return loadedAsRawMetrics;
200 public Optional<Measure> getRawMeasure(Component component, Metric metric) {
201 return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
204 public Optional<Measure> getRawRuleMeasure(Component component, Metric metric, int ruleId) {
205 return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
209 public Set<Measure> getRawMeasures(Component component, Metric metric) {
210 return from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(new MatchMetric(metric)).transform(ToMeasure.INSTANCE).toSet();
214 public SetMultimap<String, Measure> getRawMeasures(Component component) {
215 ImmutableSetMultimap.Builder<String, Measure> builder = ImmutableSetMultimap.builder();
216 for (Map.Entry<InternalKey, Measure> entry : filterKeys(rawMeasures, hasComponentRef(component)).entrySet()) {
217 builder.put(entry.getKey().getMetricKey(), entry.getValue());
219 return builder.build();
222 private HasComponentRefPredicate hasComponentRef(Component component) {
223 return new HasComponentRefPredicate(component);
227 public void add(Component component, Metric metric, Measure measure) {
228 String ref = getRef(component);
229 InternalKey internalKey = new InternalKey(ref, metric.getKey());
230 if (rawMeasures.containsKey(internalKey)) {
231 throw new UnsupportedOperationException(format(
232 "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
233 ref, metric.getKey()));
235 rawMeasures.put(internalKey, measure);
239 public void update(Component component, Metric metric, Measure measure) {
240 String componentRef = getRef(component);
241 InternalKey internalKey = new InternalKey(componentRef, metric.getKey());
242 if (!rawMeasures.containsKey(internalKey)) {
243 throw new UnsupportedOperationException(format(
244 "A measure can only be updated if it has been added first for Component (ref=%s), Metric (key=%s)",
245 componentRef, metric.getKey()));
247 rawMeasures.put(internalKey, measure);
250 private void checkAndInitProvidersState() {
251 checkState(metricRepositoryRule != null, "Can not add a measure by metric key if MeasureRepositoryRule has not been created for a MetricRepository");
252 componentProvider.ensureInitialized();
255 public boolean isEmpty() {
256 return rawMeasures.isEmpty();
259 private static final class InternalKey {
260 private final String componentRef;
261 private final String metricKey;
263 public InternalKey(Component component, Metric metric) {
264 this(getRef(component), metric.getKey());
267 private InternalKey(String componentRef, String metricKey) {
268 this.componentRef = componentRef;
269 this.metricKey = metricKey;
272 public String getComponentRef() {
276 public String getMetricKey() {
281 public boolean equals(@Nullable Object o) {
285 if (o == null || getClass() != o.getClass()) {
288 InternalKey that = (InternalKey) o;
289 return Objects.equals(componentRef, that.componentRef) &&
290 Objects.equals(metricKey, that.metricKey);
294 public int hashCode() {
295 return Objects.hash(componentRef, metricKey);
299 public String toString() {
300 return "InternalKey{" +
301 "component=" + componentRef +
302 ", metric='" + metricKey + '\'' +
307 private static class HasComponentRefPredicate implements Predicate<InternalKey> {
309 private final String componentRef;
311 public HasComponentRefPredicate(Component component) {
312 this.componentRef = getRef(component);
316 public boolean apply(@Nonnull InternalKey input) {
317 return input.getComponentRef().equals(this.componentRef);
321 private static String getRef(Component component) {
322 return component.getType().isReportType() ? String.valueOf(component.getReportAttributes().getRef()) : component.getDbKey();
325 private static class MatchMetric implements Predicate<Map.Entry<InternalKey, Measure>> {
326 private final Metric metric;
328 public MatchMetric(Metric metric) {
329 this.metric = metric;
333 public boolean apply(@Nonnull Map.Entry<InternalKey, Measure> input) {
334 return input.getKey().getMetricKey().equals(metric.getKey());
338 private enum ToMeasure implements Function<Map.Entry<InternalKey, Measure>, Measure> {
343 public Measure apply(@Nonnull Map.Entry<InternalKey, Measure> input) {
344 return input.getValue();