3 * Copyright (C) 2009-2024 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.Predicate;
23 import java.util.HashMap;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.stream.Collectors;
28 import javax.annotation.CheckForNull;
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import org.junit.rules.ExternalResource;
32 import org.sonar.ce.task.projectanalysis.component.Component;
33 import org.sonar.ce.task.projectanalysis.component.ComponentProvider;
34 import org.sonar.ce.task.projectanalysis.component.NoComponentProvider;
35 import org.sonar.ce.task.projectanalysis.component.TreeComponentProvider;
36 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
37 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderComponentProvider;
38 import org.sonar.ce.task.projectanalysis.metric.Metric;
39 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
41 import static com.google.common.base.Preconditions.checkState;
42 import static com.google.common.collect.FluentIterable.from;
43 import static com.google.common.collect.Maps.filterKeys;
44 import static java.lang.String.format;
45 import static java.util.Objects.requireNonNull;
48 * An implementation of MeasureRepository as a JUnit rule which provides add methods for raw measures and extra add
49 * methods that takes component ref and metric keys thanks to the integration with various Component and Metric
52 public class MeasureRepositoryRule extends ExternalResource implements MeasureRepository {
53 private final ComponentProvider componentProvider;
55 private final MetricRepositoryRule metricRepositoryRule;
56 private final Map<InternalKey, Measure> baseMeasures = new HashMap<>();
57 private final Map<InternalKey, Measure> rawMeasures = new HashMap<>();
58 private final Map<InternalKey, Measure> initialRawMeasures = new HashMap<>();
59 private final Predicate<Map.Entry<InternalKey, Measure>> isAddedMeasure = input -> !initialRawMeasures.containsKey(input.getKey())
60 || !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));
62 private MeasureRepositoryRule(ComponentProvider componentProvider, @Nullable MetricRepositoryRule metricRepositoryRule) {
63 this.componentProvider = componentProvider;
64 this.metricRepositoryRule = metricRepositoryRule;
68 protected void after() {
69 componentProvider.reset();
74 public static MeasureRepositoryRule create() {
75 return new MeasureRepositoryRule(NoComponentProvider.INSTANCE, null);
78 public static MeasureRepositoryRule create(TreeRootHolder treeRootHolder, MetricRepositoryRule metricRepositoryRule) {
79 return new MeasureRepositoryRule(new TreeRootHolderComponentProvider(treeRootHolder), requireNonNull(metricRepositoryRule));
82 public static MeasureRepositoryRule create(Component treeRoot, MetricRepositoryRule metricRepositoryRule) {
83 return new MeasureRepositoryRule(new TreeComponentProvider(treeRoot), requireNonNull(metricRepositoryRule));
86 public MeasureRepositoryRule addBaseMeasure(int componentRef, String metricKey, Measure measure) {
87 checkAndInitProvidersState();
89 InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
90 checkState(!baseMeasures.containsKey(internalKey), format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", componentRef, metricKey));
92 baseMeasures.put(internalKey, measure);
97 public Map<String, Measure> getRawMeasures(int componentRef) {
98 return getRawMeasures(componentProvider.getByRef(componentRef));
102 * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
103 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
105 public Map<String, Measure> getAddedRawMeasures(int componentRef) {
106 checkAndInitProvidersState();
108 return getAddedRawMeasures(componentProvider.getByRef(componentRef));
112 * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
113 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
115 public Optional<Measure> getAddedRawMeasure(Component component, String metricKey) {
116 return getAddedRawMeasure(component.getReportAttributes().getRef(), metricKey);
120 * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
121 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
123 public Optional<Measure> getAddedRawMeasure(int componentRef, String metricKey) {
124 checkAndInitProvidersState();
126 Measure measure = getAddedRawMeasures(componentProvider.getByRef(componentRef)).get(metricKey);
127 return Optional.ofNullable(measure);
131 * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
132 * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
134 public Map<String, Measure> getAddedRawMeasures(Component component) {
135 checkAndInitProvidersState();
137 Map<String, Measure> builder = new HashMap<>();
138 for (Map.Entry<InternalKey, Measure> entry : from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isAddedMeasure)) {
139 builder.put(entry.getKey().getMetricKey(), entry.getValue());
144 public MeasureRepositoryRule addRawMeasure(int componentRef, String metricKey, Measure measure) {
145 checkAndInitProvidersState();
147 InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
148 checkState(!rawMeasures.containsKey(internalKey), format(
149 "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
150 componentRef, metricKey));
152 rawMeasures.put(internalKey, measure);
153 initialRawMeasures.put(internalKey, measure);
159 public Optional<Measure> getBaseMeasure(Component component, Metric metric) {
160 return Optional.ofNullable(baseMeasures.get(new InternalKey(component, metric)));
164 public Optional<Measure> getRawMeasure(Component component, Metric metric) {
165 return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
169 public Map<String, Measure> getRawMeasures(Component component) {
170 return filterKeys(rawMeasures, hasComponentRef(component)).entrySet().stream()
171 .collect(Collectors.toMap(k -> k.getKey().getMetricKey(), Map.Entry::getValue));
174 private HasComponentRefPredicate hasComponentRef(Component component) {
175 return new HasComponentRefPredicate(component);
179 public void add(Component component, Metric metric, Measure measure) {
180 String ref = getRef(component);
181 InternalKey internalKey = new InternalKey(ref, metric.getKey());
182 if (rawMeasures.containsKey(internalKey)) {
183 throw new UnsupportedOperationException(format(
184 "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
185 ref, metric.getKey()));
187 rawMeasures.put(internalKey, measure);
191 public void update(Component component, Metric metric, Measure measure) {
192 String componentRef = getRef(component);
193 InternalKey internalKey = new InternalKey(componentRef, metric.getKey());
194 if (!rawMeasures.containsKey(internalKey)) {
195 throw new UnsupportedOperationException(format(
196 "A measure can only be updated if it has been added first for Component (ref=%s), Metric (key=%s)",
197 componentRef, metric.getKey()));
199 rawMeasures.put(internalKey, measure);
202 private void checkAndInitProvidersState() {
203 checkState(metricRepositoryRule != null, "Can not add a measure by metric key if MeasureRepositoryRule has not been created for a MetricRepository");
204 componentProvider.ensureInitialized();
207 public boolean isEmpty() {
208 return rawMeasures.isEmpty();
211 private static final class InternalKey {
212 private final String componentRef;
213 private final String metricKey;
215 public InternalKey(Component component, Metric metric) {
216 this(getRef(component), metric.getKey());
219 private InternalKey(String componentRef, String metricKey) {
220 this.componentRef = componentRef;
221 this.metricKey = metricKey;
224 public String getComponentRef() {
228 public String getMetricKey() {
233 public boolean equals(@Nullable Object o) {
237 if (o == null || getClass() != o.getClass()) {
240 InternalKey that = (InternalKey) o;
241 return Objects.equals(componentRef, that.componentRef) &&
242 Objects.equals(metricKey, that.metricKey);
246 public int hashCode() {
247 return Objects.hash(componentRef, metricKey);
251 public String toString() {
252 return "InternalKey{" +
253 "component=" + componentRef +
254 ", metric='" + metricKey + '\'' +
259 private static class HasComponentRefPredicate implements Predicate<InternalKey> {
261 private final String componentRef;
263 public HasComponentRefPredicate(Component component) {
264 this.componentRef = getRef(component);
268 public boolean apply(@Nonnull InternalKey input) {
269 return input.getComponentRef().equals(this.componentRef);
273 private static String getRef(Component component) {
274 return component.getType().isReportType() ? String.valueOf(component.getReportAttributes().getRef()) : component.getKey();