001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.batch.sensor.internal;
021
022import com.google.common.annotations.Beta;
023import com.google.common.collect.HashBasedTable;
024import com.google.common.collect.Table;
025import java.io.File;
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.EnumMap;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import javax.annotation.CheckForNull;
035import org.sonar.api.batch.AnalysisMode;
036import org.sonar.api.batch.fs.internal.DefaultFileSystem;
037import org.sonar.api.batch.fs.internal.DefaultTextPointer;
038import org.sonar.api.batch.rule.ActiveRules;
039import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
040import org.sonar.api.batch.sensor.Sensor;
041import org.sonar.api.batch.sensor.SensorContext;
042import org.sonar.api.batch.sensor.coverage.CoverageType;
043import org.sonar.api.batch.sensor.coverage.NewCoverage;
044import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
045import org.sonar.api.batch.sensor.duplication.Duplication;
046import org.sonar.api.batch.sensor.duplication.NewDuplication;
047import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplication;
048import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
049import org.sonar.api.batch.sensor.highlighting.TypeOfText;
050import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
051import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule;
052import org.sonar.api.batch.sensor.issue.Issue;
053import org.sonar.api.batch.sensor.issue.NewIssue;
054import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
055import org.sonar.api.batch.sensor.measure.Measure;
056import org.sonar.api.batch.sensor.measure.NewMeasure;
057import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
058import org.sonar.api.config.Settings;
059import org.sonar.api.measures.Metric;
060
061/**
062 * Utility class to help testing {@link Sensor}.
063 * 
064 * Usage: call {@link #create(File)} to create an "in memory" implementation of {@link SensorContext} then
065 * pass it to your {@link Sensor}. You can then query elements provided by your sensor using methods {@link #allIssues()}, ...
066 * 
067 * @since 5.1
068 */
069@Beta
070public class SensorContextTester implements SensorContext {
071
072  private Settings settings;
073  private DefaultFileSystem fs;
074  private ActiveRules activeRules;
075  private MockAnalysisMode analysisMode;
076  private InMemorySensorStorage sensorStorage;
077
078  private SensorContextTester(File moduleBaseDir) {
079    this.settings = new Settings();
080    this.fs = new DefaultFileSystem(moduleBaseDir);
081    this.activeRules = new ActiveRulesBuilder().build();
082    this.analysisMode = new MockAnalysisMode();
083    this.sensorStorage = new InMemorySensorStorage();
084  }
085
086  public static SensorContextTester create(File moduleBaseDir) {
087    return new SensorContextTester(moduleBaseDir);
088  }
089
090  @Override
091  public Settings settings() {
092    return settings;
093  }
094
095  public void setSettings(Settings settings) {
096    this.settings = settings;
097  }
098
099  @Override
100  public DefaultFileSystem fileSystem() {
101    return fs;
102  }
103
104  public void setFileSystem(DefaultFileSystem fs) {
105    this.fs = fs;
106  }
107
108  @Override
109  public ActiveRules activeRules() {
110    return activeRules;
111  }
112
113  public void setActiveRules(ActiveRules activeRules) {
114    this.activeRules = activeRules;
115  }
116
117  @Override
118  public MockAnalysisMode analysisMode() {
119    return analysisMode;
120  }
121
122  @Override
123  public <G extends Serializable> NewMeasure<G> newMeasure() {
124    return new DefaultMeasure<>(sensorStorage);
125  }
126
127  public Collection<Measure> measures(String componentKey) {
128    return sensorStorage.measuresByComponentAndMetric.row(componentKey).values();
129  }
130
131  public <G extends Serializable> Measure<G> measure(String componetKey, Metric<G> metric) {
132    return measure(componetKey, metric.key());
133  }
134
135  public <G extends Serializable> Measure<G> measure(String componentKey, String metricKey) {
136    return sensorStorage.measuresByComponentAndMetric.row(componentKey).get(metricKey);
137  }
138
139  @Override
140  public NewIssue newIssue() {
141    return new DefaultIssue(sensorStorage);
142  }
143
144  public Collection<Issue> allIssues() {
145    return sensorStorage.allIssues;
146  }
147
148  @CheckForNull
149  public Integer lineHits(String fileKey, CoverageType type, int line) {
150    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
151    if (defaultCoverageByType == null) {
152      return null;
153    }
154    if (defaultCoverageByType.containsKey(type)) {
155      return defaultCoverageByType.get(type).hitsByLine().get(line);
156    }
157    return null;
158  }
159
160  @CheckForNull
161  public Integer conditions(String fileKey, CoverageType type, int line) {
162    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
163    if (defaultCoverageByType == null) {
164      return null;
165    }
166    if (defaultCoverageByType.containsKey(type)) {
167      return defaultCoverageByType.get(type).conditionsByLine().get(line);
168    }
169    return null;
170  }
171
172  @CheckForNull
173  public Integer coveredConditions(String fileKey, CoverageType type, int line) {
174    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
175    if (defaultCoverageByType == null) {
176      return null;
177    }
178    if (defaultCoverageByType.containsKey(type)) {
179      return defaultCoverageByType.get(type).coveredConditionsByLine().get(line);
180    }
181    return null;
182  }
183
184  @Override
185  public NewHighlighting newHighlighting() {
186    return new DefaultHighlighting(sensorStorage);
187  }
188
189  @Override
190  public NewCoverage newCoverage() {
191    return new DefaultCoverage(sensorStorage);
192  }
193
194  public List<TypeOfText> highlightingTypeAt(String componentKey, int line, int lineOffset) {
195    DefaultHighlighting syntaxHighlightingData = sensorStorage.highlightingByComponent.get(componentKey);
196    if (syntaxHighlightingData == null) {
197      return Collections.emptyList();
198    }
199    List<TypeOfText> result = new ArrayList<>();
200    DefaultTextPointer location = new DefaultTextPointer(line, lineOffset);
201    for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.getSyntaxHighlightingRuleSet()) {
202      if (sortedRule.range().start().compareTo(location) <= 0 && sortedRule.range().end().compareTo(location) > 0) {
203        result.add(sortedRule.getTextType());
204      }
205    }
206    return result;
207  }
208
209  @Override
210  public NewDuplication newDuplication() {
211    return new DefaultDuplication(sensorStorage);
212  }
213
214  public Collection<Duplication> duplications() {
215    return sensorStorage.duplications;
216  }
217
218  public static class MockAnalysisMode implements AnalysisMode {
219    private boolean isPreview = false;
220    private boolean isIssues = false;
221
222    @Override
223    public boolean isPreview() {
224      return isPreview;
225    }
226
227    public void setPreview(boolean value) {
228      this.isPreview = value;
229    }
230
231    @Override
232    public boolean isIssues() {
233      return this.isIssues;
234    }
235
236    public void setIssues(boolean issues) {
237      this.isIssues = issues;
238    }
239
240    @Override
241    public boolean isPublish() {
242      return !isPreview && !isIssues;
243    }
244  }
245
246  private static class InMemorySensorStorage implements SensorStorage {
247
248    private Table<String, String, Measure> measuresByComponentAndMetric = HashBasedTable.create();
249
250    private Collection<Issue> allIssues = new ArrayList<>();
251
252    private Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
253    private Map<String, Map<CoverageType, DefaultCoverage>> coverageByComponent = new HashMap<>();
254
255    private List<Duplication> duplications = new ArrayList<>();
256
257    @Override
258    public void store(Measure measure) {
259      measuresByComponentAndMetric.row(measure.inputComponent().key()).put(measure.metric().key(), measure);
260    }
261
262    @Override
263    public void store(Issue issue) {
264      allIssues.add(issue);
265    }
266
267    @Override
268    public void store(Duplication duplication) {
269      duplications.add(duplication);
270    }
271
272    @Override
273    public void store(DefaultHighlighting highlighting) {
274      highlightingByComponent.put(highlighting.inputFile().key(), highlighting);
275    }
276
277    @Override
278    public void store(DefaultCoverage defaultCoverage) {
279      String key = defaultCoverage.inputFile().key();
280      if (!coverageByComponent.containsKey(key)) {
281        coverageByComponent.put(key, new EnumMap<CoverageType, DefaultCoverage>(CoverageType.class));
282      }
283      coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage);
284    }
285
286  }
287
288}