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.highlighting.NewHighlighting;
046import org.sonar.api.batch.sensor.highlighting.TypeOfText;
047import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
048import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule;
049import org.sonar.api.batch.sensor.issue.Issue;
050import org.sonar.api.batch.sensor.issue.NewIssue;
051import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
052import org.sonar.api.batch.sensor.measure.Measure;
053import org.sonar.api.batch.sensor.measure.NewMeasure;
054import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
055import org.sonar.api.config.Settings;
056import org.sonar.api.measures.Metric;
057
058/**
059 * Utility class to help testing {@link Sensor}.
060 * 
061 * Usage: call {@link #create(File)} to create an "in memory" implementation of {@link SensorContext} then
062 * pass it to your {@link Sensor}. You can then query elements provided by your sensor using methods {@link #allIssues()}, ...
063 * 
064 * @since 5.1
065 */
066@Beta
067public class SensorContextTester implements SensorContext {
068
069  private Settings settings;
070  private DefaultFileSystem fs;
071  private ActiveRules activeRules;
072  private MockAnalysisMode analysisMode;
073  private InMemorySensorStorage sensorStorage;
074
075  private SensorContextTester(File moduleBaseDir) {
076    this.settings = new Settings();
077    this.fs = new DefaultFileSystem(moduleBaseDir);
078    this.activeRules = new ActiveRulesBuilder().build();
079    this.analysisMode = new MockAnalysisMode();
080    this.sensorStorage = new InMemorySensorStorage();
081  }
082
083  public static SensorContextTester create(File moduleBaseDir) {
084    return new SensorContextTester(moduleBaseDir);
085  }
086
087  @Override
088  public Settings settings() {
089    return settings;
090  }
091
092  public void setSettings(Settings settings) {
093    this.settings = settings;
094  }
095
096  @Override
097  public DefaultFileSystem fileSystem() {
098    return fs;
099  }
100
101  public void setFileSystem(DefaultFileSystem fs) {
102    this.fs = fs;
103  }
104
105  @Override
106  public ActiveRules activeRules() {
107    return activeRules;
108  }
109
110  public void setActiveRules(ActiveRules activeRules) {
111    this.activeRules = activeRules;
112  }
113
114  @Override
115  public MockAnalysisMode analysisMode() {
116    return analysisMode;
117  }
118
119  @Override
120  public <G extends Serializable> NewMeasure<G> newMeasure() {
121    return new DefaultMeasure<>(sensorStorage);
122  }
123
124  public Collection<Measure> measures(String componentKey) {
125    return sensorStorage.measuresByComponentAndMetric.row(componentKey).values();
126  }
127
128  public <G extends Serializable> Measure<G> measure(String componetKey, Metric<G> metric) {
129    return measure(componetKey, metric.key());
130  }
131
132  public <G extends Serializable> Measure<G> measure(String componentKey, String metricKey) {
133    return sensorStorage.measuresByComponentAndMetric.row(componentKey).get(metricKey);
134  }
135
136  @Override
137  public NewIssue newIssue() {
138    return new DefaultIssue(sensorStorage);
139  }
140
141  public Collection<Issue> allIssues() {
142    return sensorStorage.allIssues;
143  }
144
145  @CheckForNull
146  public Integer lineHits(String fileKey, CoverageType type, int line) {
147    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
148    if (defaultCoverageByType == null) {
149      return null;
150    }
151    if (defaultCoverageByType.containsKey(type)) {
152      return defaultCoverageByType.get(type).hitsByLine().get(line);
153    }
154    return null;
155  }
156
157  @CheckForNull
158  public Integer conditions(String fileKey, CoverageType type, int line) {
159    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
160    if (defaultCoverageByType == null) {
161      return null;
162    }
163    if (defaultCoverageByType.containsKey(type)) {
164      return defaultCoverageByType.get(type).conditionsByLine().get(line);
165    }
166    return null;
167  }
168
169  @CheckForNull
170  public Integer coveredConditions(String fileKey, CoverageType type, int line) {
171    Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey);
172    if (defaultCoverageByType == null) {
173      return null;
174    }
175    if (defaultCoverageByType.containsKey(type)) {
176      return defaultCoverageByType.get(type).coveredConditionsByLine().get(line);
177    }
178    return null;
179  }
180
181  @Override
182  public NewHighlighting newHighlighting() {
183    return new DefaultHighlighting(sensorStorage);
184  }
185
186  @Override
187  public NewCoverage newCoverage() {
188    return new DefaultCoverage(sensorStorage);
189  }
190
191  public List<TypeOfText> highlightingTypeAt(String componentKey, int line, int lineOffset) {
192    DefaultHighlighting syntaxHighlightingData = sensorStorage.highlightingByComponent.get(componentKey);
193    if (syntaxHighlightingData == null) {
194      return Collections.emptyList();
195    }
196    List<TypeOfText> result = new ArrayList<>();
197    DefaultTextPointer location = new DefaultTextPointer(line, lineOffset);
198    for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.getSyntaxHighlightingRuleSet()) {
199      if (sortedRule.range().start().compareTo(location) <= 0 && sortedRule.range().end().compareTo(location) > 0) {
200        result.add(sortedRule.getTextType());
201      }
202    }
203    return result;
204  }
205
206  public static class MockAnalysisMode implements AnalysisMode {
207    private boolean isPreview = false;
208    private boolean isIssues = false;
209
210    @Override
211    public boolean isPreview() {
212      return isPreview;
213    }
214
215    public void setPreview(boolean value) {
216      this.isPreview = value;
217    }
218
219    @Override
220    public boolean isIssues() {
221      return this.isIssues;
222    }
223
224    public void setIssues(boolean issues) {
225      this.isIssues = issues;
226    }
227
228    @Override
229    public boolean isPublish() {
230      return !isPreview && !isIssues;
231    }
232  }
233
234  private static class InMemorySensorStorage implements SensorStorage {
235
236    private Table<String, String, Measure> measuresByComponentAndMetric = HashBasedTable.create();
237
238    private Collection<Issue> allIssues = new ArrayList<>();
239
240    private Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
241    private Map<String, Map<CoverageType, DefaultCoverage>> coverageByComponent = new HashMap<>();
242
243    @Override
244    public void store(Measure measure) {
245      measuresByComponentAndMetric.row(measure.inputComponent().key()).put(measure.metric().key(), measure);
246    }
247
248    @Override
249    public void store(Issue issue) {
250      allIssues.add(issue);
251    }
252
253    @Override
254    public void store(DefaultHighlighting highlighting) {
255      highlightingByComponent.put(highlighting.inputFile().key(), highlighting);
256    }
257
258    @Override
259    public void store(DefaultCoverage defaultCoverage) {
260      String key = defaultCoverage.inputFile().key();
261      if (!coverageByComponent.containsKey(key)) {
262        coverageByComponent.put(key, new EnumMap<CoverageType, DefaultCoverage>(CoverageType.class));
263      }
264      coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage);
265    }
266
267  }
268
269}