diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-06-19 23:25:55 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-06-19 23:26:57 +0200 |
commit | 369f12960fba6653e630042ab977c97e479a3076 (patch) | |
tree | c3896b2a5f2caf4fdc8037567a862e810b43b38f /sonar-batch | |
parent | 71a6a922a2d07ae8db9f13036a9653e772380762 (diff) | |
download | sonarqube-369f12960fba6653e630042ab977c97e479a3076.tar.gz sonarqube-369f12960fba6653e630042ab977c97e479a3076.zip |
SONAR-5389 Add support of FileLinesContextFactory + publish analysis
Diffstat (limited to 'sonar-batch')
24 files changed, 678 insertions, 40 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java index cd9498b7c35..98c5ef1dc78 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java @@ -19,7 +19,6 @@ */ package org.sonar.batch; -import com.google.common.annotations.Beta; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; @@ -36,10 +35,6 @@ import org.sonar.api.utils.KeyValueFormat.Converter; import java.util.Map; -/** - * @since 2.14 - */ -@Beta public class DefaultFileLinesContext implements FileLinesContext { private final SonarIndex index; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/AnalyzerMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/AnalyzerMediumTester.java index d69b371f57d..c35ccff3216 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/AnalyzerMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/AnalyzerMediumTester.java @@ -24,7 +24,6 @@ import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.io.IOUtils; -import org.junit.rules.ExternalResource; import org.sonar.api.SonarPlugin; import org.sonar.api.batch.analyzer.issue.AnalyzerIssue; import org.sonar.api.batch.analyzer.measure.AnalyzerMeasure; @@ -64,7 +63,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; -public class AnalyzerMediumTester extends ExternalResource { +public class AnalyzerMediumTester { private Batch batch; @@ -144,13 +143,11 @@ public class AnalyzerMediumTester extends ExternalResource { } - @Override - protected void before() throws Throwable { + public void start() throws Throwable { batch.start(); } - @Override - protected void after() { + public void stop() { batch.stop(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/package-info.java new file mode 100644 index 00000000000..40991844d12 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.mediumtest; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerIssueCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerIssueCache.java index 613507de041..4adc383a00d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerIssueCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerIssueCache.java @@ -29,24 +29,28 @@ import org.sonar.batch.index.Caches; */ public class AnalyzerIssueCache implements BatchComponent { - // component key -> issue key -> issue + // project key -> resource key -> issue key -> issue private final Cache<DefaultAnalyzerIssue> cache; public AnalyzerIssueCache(Caches caches) { cache = caches.createCache("issues"); } - public Iterable<DefaultAnalyzerIssue> byComponent(String resourceKey) { - return cache.values(resourceKey); + public Iterable<DefaultAnalyzerIssue> byComponent(String projectKey, String resourceKey) { + return cache.values(projectKey, resourceKey); } public Iterable<DefaultAnalyzerIssue> all() { return cache.values(); } - public AnalyzerIssueCache put(String resourceKey, DefaultAnalyzerIssue issue) { - cache.put(resourceKey, issue.key(), issue); + public AnalyzerIssueCache put(String projectKey, String resourceKey, DefaultAnalyzerIssue issue) { + cache.put(projectKey, resourceKey, issue.key(), issue); return this; } + public Iterable<DefaultAnalyzerIssue> byModule(String projectKey) { + return cache.values(projectKey); + } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java index 07d313f2249..030ff3e6ccc 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java @@ -31,6 +31,7 @@ import org.sonar.batch.index.Caches; */ public class AnalyzerMeasureCache implements BatchComponent { + // project key -> component key -> metric key -> measure private final Cache<DefaultAnalyzerMeasure> cache; public AnalyzerMeasureCache(Caches caches) { @@ -41,21 +42,27 @@ public class AnalyzerMeasureCache implements BatchComponent { return cache.entries(); } - public DefaultAnalyzerMeasure<?> byMetric(String resourceKey, String metricKey) { - return cache.get(resourceKey, metricKey); + public Iterable<DefaultAnalyzerMeasure> byModule(String projectKey) { + return cache.values(projectKey); } - public AnalyzerMeasureCache put(String resourceKey, DefaultAnalyzerMeasure<?> measure) { + public DefaultAnalyzerMeasure<?> byMetric(String projectKey, String resourceKey, String metricKey) { + return cache.get(projectKey, resourceKey, metricKey); + } + + public AnalyzerMeasureCache put(String projectKey, String resourceKey, DefaultAnalyzerMeasure<?> measure) { + Preconditions.checkNotNull(projectKey); Preconditions.checkNotNull(resourceKey); Preconditions.checkNotNull(measure); - cache.put(resourceKey, measure.metric().key(), measure); + cache.put(projectKey, resourceKey, measure.metric().key(), measure); return this; } - public boolean contains(String resourceKey, DefaultAnalyzerMeasure<?> measure) { + public boolean contains(String projectKey, String resourceKey, DefaultAnalyzerMeasure<?> measure) { + Preconditions.checkNotNull(projectKey); Preconditions.checkNotNull(resourceKey); Preconditions.checkNotNull(measure); - return cache.containsKey(resourceKey, measure.metric().key()); + return cache.containsKey(projectKey, resourceKey, measure.metric().key()); } public Iterable<DefaultAnalyzerMeasure> all() { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzisPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzisPublisher.java new file mode 100644 index 00000000000..2c2e29d9067 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzisPublisher.java @@ -0,0 +1,189 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.analyzer.issue.AnalyzerIssue; +import org.sonar.api.batch.analyzer.measure.AnalyzerMeasure; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.ZipUtils; +import org.sonar.api.utils.text.JsonWriter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Properties; + +public final class AnalyzisPublisher { + + public static final Logger LOG = LoggerFactory.getLogger(AnalyzisPublisher.class); + private final Settings settings; + private final FileSystem fs; + private final AnalyzerMeasureCache measureCache; + private final ProjectDefinition def; + private AnalyzerIssueCache issueCache; + + public AnalyzisPublisher(ProjectDefinition def, Settings settings, FileSystem fs, AnalyzerMeasureCache measureCache, AnalyzerIssueCache analyzerIssueCache) { + this.def = def; + this.settings = settings; + this.fs = fs; + this.measureCache = measureCache; + this.issueCache = analyzerIssueCache; + } + + public void execute() { + if (settings.getBoolean("sonar.skipPublish")) { + LOG.debug("Publishing of results is skipped"); + return; + } + File exportDir = prepareExportDir(); + + exportAnalysisProperties(exportDir); + + exportSourceFiles(exportDir); + + exportMeasures(exportDir); + + exportIssues(exportDir); + + createZip(exportDir); + + } + + private void createZip(File exportDir) { + File exportZip = new File(fs.workDir(), def.getKey() + "-export.zip"); + try { + ZipUtils.zipDir(exportDir, exportZip); + FileUtils.deleteDirectory(exportDir); + } catch (IOException e) { + throw unableToExport(e); + } + LOG.info("Results packaged in " + exportZip); + } + + private IllegalStateException unableToExport(IOException e) { + return new IllegalStateException("Unable to export result of analyzis", e); + } + + private void exportIssues(File exportDir) { + File issuesFile = new File(exportDir, "issues.json"); + FileWriter issueWriter = null; + try { + issueWriter = new FileWriter(issuesFile); + JsonWriter jsonWriter = JsonWriter.of(issueWriter); + jsonWriter + .beginObject().name("issues") + .beginArray(); + for (AnalyzerIssue issue : issueCache.byModule(def.getKey())) { + jsonWriter.beginObject() + .prop("repository", issue.ruleKey().repository()) + .prop("rule", issue.ruleKey().rule()); + if (issue.inputFile() != null) { + jsonWriter.prop("filePath", issue.inputFile().relativePath()); + } + jsonWriter.prop("message", issue.message()) + .prop("effortToFix", issue.effortToFix()) + .prop("line", issue.line()) + .endObject(); + } + jsonWriter.endArray() + .endObject() + .close(); + } catch (IOException e) { + + } finally { + IOUtils.closeQuietly(issueWriter); + } + } + + private void exportMeasures(File exportDir) { + File measuresFile = new File(exportDir, "measures.json"); + FileWriter measureWriter = null; + try { + measureWriter = new FileWriter(measuresFile); + JsonWriter jsonWriter = JsonWriter.of(measureWriter); + jsonWriter + .beginObject().name("measures") + .beginArray(); + for (AnalyzerMeasure<?> measure : measureCache.byModule(def.getKey())) { + jsonWriter.beginObject() + .prop("metricKey", measure.metric().key()); + if (measure.inputFile() != null) { + jsonWriter.prop("filePath", measure.inputFile().relativePath()); + } + jsonWriter.prop("value", String.valueOf(measure.value())) + .endObject(); + } + jsonWriter.endArray() + .endObject() + .close(); + } catch (IOException e) { + + } finally { + IOUtils.closeQuietly(measureWriter); + } + } + + private void exportSourceFiles(File exportDir) { + File sourceDir = new File(exportDir, "sources"); + for (InputFile inputFile : fs.inputFiles(fs.predicates().all())) { + File dest = new File(sourceDir, inputFile.relativePath()); + try { + FileUtils.copyFile(inputFile.file(), dest); + } catch (IOException e) { + throw unableToExport(e); + } + } + } + + private void exportAnalysisProperties(File exportDir) { + File propsFile = new File(exportDir, "analysis.properties"); + Properties props = new Properties(); + props.putAll(settings.getProperties()); + FileWriter writer = null; + try { + writer = new FileWriter(propsFile); + props.store(writer, "SonarQube batch"); + } catch (IOException e) { + throw unableToExport(e); + } finally { + IOUtils.closeQuietly(writer); + } + } + + private File prepareExportDir() { + File exportDir = new File(fs.workDir(), "export"); + try { + if (exportDir.exists()) { + FileUtils.forceDelete(exportDir); + } + FileUtils.forceMkdir(exportDir); + } catch (IOException e) { + throw unableToExport(e); + } + return exportDir; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java index cb46ec2204c..8e16ccd1f97 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultAnalyzerContext.java @@ -83,30 +83,30 @@ public class DefaultAnalyzerContext implements AnalyzerContext { @Override public AnalyzerMeasure getMeasure(String metricKey) { - return measureCache.byMetric(def.getKey(), metricKey); + return measureCache.byMetric(def.getKey(), def.getKey(), metricKey); } @Override public <G extends Serializable> AnalyzerMeasure<G> getMeasure(Metric<G> metric) { - return (AnalyzerMeasure<G>) measureCache.byMetric(def.getKey(), metric.key()); + return (AnalyzerMeasure<G>) measureCache.byMetric(def.getKey(), def.getKey(), metric.key()); } @Override public AnalyzerMeasure getMeasure(InputFile file, String metricKey) { - return measureCache.byMetric(ComponentKeys.createEffectiveKey(def.getKey(), file), metricKey); + return measureCache.byMetric(def.getKey(), ComponentKeys.createEffectiveKey(def.getKey(), file), metricKey); } @Override public <G extends Serializable> AnalyzerMeasure<G> getMeasure(InputFile file, Metric<G> metric) { - return (AnalyzerMeasure<G>) measureCache.byMetric(ComponentKeys.createEffectiveKey(def.getKey(), file), metric.key()); + return (AnalyzerMeasure<G>) measureCache.byMetric(def.getKey(), ComponentKeys.createEffectiveKey(def.getKey(), file), metric.key()); } @Override public void addMeasure(AnalyzerMeasure<?> measure) { if (measure.inputFile() != null) { - measureCache.put(ComponentKeys.createEffectiveKey(def.getKey(), measure.inputFile()), (DefaultAnalyzerMeasure) measure); + measureCache.put(def.getKey(), ComponentKeys.createEffectiveKey(def.getKey(), measure.inputFile()), (DefaultAnalyzerMeasure) measure); } else { - measureCache.put(def.getKey(), (DefaultAnalyzerMeasure) measure); + measureCache.put(def.getKey(), def.getKey(), (DefaultAnalyzerMeasure) measure); } } @@ -125,7 +125,7 @@ public class DefaultAnalyzerContext implements AnalyzerContext { } if (issueFilters.accept(AnalyzerContextAdaptor.toDefaultIssue(def.getKey(), resourceKey, issue), null)) { - issueCache.put(resourceKey, (DefaultAnalyzerIssue) issue); + issueCache.put(def.getKey(), resourceKey, (DefaultAnalyzerIssue) issue); } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContext.java new file mode 100644 index 00000000000..729a4076d3d --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContext.java @@ -0,0 +1,158 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import org.sonar.api.batch.analyzer.measure.AnalyzerMeasure; +import org.sonar.api.batch.analyzer.measure.internal.DefaultAnalyzerMeasureBuilder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.MetricFinder; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.api.utils.KeyValueFormat.Converter; +import org.sonar.core.component.ComponentKeys; + +import java.util.Map; + +public class DefaultFileLinesContext implements FileLinesContext { + + private final AnalyzerMeasureCache measureCache; + private final InputFile inputFile; + + /** + * metric key -> line -> value + */ + private final Map<String, Map<Integer, Object>> map = Maps.newHashMap(); + private String projectKey; + private MetricFinder metricFinder; + + public DefaultFileLinesContext(MetricFinder metricFinder, AnalyzerMeasureCache measureCache, String projectKey, InputFile inputFile) { + this.metricFinder = metricFinder; + this.projectKey = projectKey; + Preconditions.checkNotNull(measureCache); + this.measureCache = measureCache; + this.inputFile = inputFile; + } + + public void setIntValue(String metricKey, int line, int value) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + setValue(metricKey, line, value); + } + + public Integer getIntValue(String metricKey, int line) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + Map lines = map.get(metricKey); + if (lines == null) { + // not in memory, so load + lines = loadData(metricKey, KeyValueFormat.newIntegerConverter()); + map.put(metricKey, lines); + } + return (Integer) lines.get(line); + } + + public void setStringValue(String metricKey, int line, String value) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + Preconditions.checkNotNull(value); + + setValue(metricKey, line, value); + } + + public String getStringValue(String metricKey, int line) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + Map lines = map.get(metricKey); + if (lines == null) { + // not in memory, so load + lines = loadData(metricKey, KeyValueFormat.newStringConverter()); + map.put(metricKey, lines); + } + return (String) lines.get(line); + } + + private Map<Integer, Object> getOrCreateLines(String metricKey) { + Map<Integer, Object> lines = map.get(metricKey); + if (lines == null) { + lines = Maps.newHashMap(); + map.put(metricKey, lines); + } + return lines; + } + + private void setValue(String metricKey, int line, Object value) { + getOrCreateLines(metricKey).put(line, value); + } + + public void save() { + for (Map.Entry<String, Map<Integer, Object>> entry : map.entrySet()) { + String metricKey = entry.getKey(); + Metric metric = metricFinder.findByKey(metricKey); + if (metric == null) { + throw new IllegalStateException("Unable to find metric with key: " + metricKey); + } + Map<Integer, Object> lines = entry.getValue(); + if (shouldSave(lines)) { + String data = KeyValueFormat.format(lines); + measureCache.put(projectKey, ComponentKeys.createEffectiveKey(projectKey, inputFile), new DefaultAnalyzerMeasureBuilder<String>() + .forMetric(metric) + .onFile(inputFile) + .withValue(data) + .build()); + entry.setValue(ImmutableMap.copyOf(lines)); + } + } + } + + private Map loadData(String metricKey, Converter converter) { + AnalyzerMeasure measure = measureCache.byMetric(projectKey, ComponentKeys.createEffectiveKey(projectKey, inputFile), metricKey); + if (measure == null) { + // no such measure + return ImmutableMap.of(); + } + return ImmutableMap.copyOf(KeyValueFormat.parse((String) measure.value(), KeyValueFormat.newIntegerConverter(), converter)); + } + + /** + * Checks that measure was not saved. + * + * @see #loadData(String, Converter) + * @see #save() + */ + private boolean shouldSave(Map<Integer, Object> lines) { + return !(lines instanceof ImmutableMap); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("map", map) + .toString(); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContextFactory.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContextFactory.java new file mode 100644 index 00000000000..9dd70c7f3e8 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultFileLinesContextFactory.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.api.measures.MetricFinder; +import org.sonar.api.resources.Resource; +import org.sonar.batch.scan.filesystem.InputFileCache; + +public class DefaultFileLinesContextFactory implements FileLinesContextFactory { + + private final AnalyzerMeasureCache measureCache; + private final MetricFinder metricFinder; + private final ProjectDefinition def; + private InputFileCache fileCache; + + public DefaultFileLinesContextFactory(InputFileCache fileCache, FileSystem fs, MetricFinder metricFinder, AnalyzerMeasureCache measureCache, ProjectDefinition def) { + this.fileCache = fileCache; + this.metricFinder = metricFinder; + this.measureCache = measureCache; + this.def = def; + } + + @Override + public FileLinesContext createFor(Resource model) { + throw new UnsupportedOperationException(); + } + + @Override + public FileLinesContext createFor(InputFile inputFile) { + if (fileCache.get(def.getKey(), inputFile.relativePath()) == null) { + throw new IllegalStateException("InputFile is not indexed: " + inputFile); + } + return new DefaultFileLinesContext(metricFinder, measureCache, def.getKey(), inputFile); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java index 3ec356536ad..6309146221c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java @@ -112,13 +112,18 @@ public class ModuleScanContainer extends ComponentContainer { IssuableFactory.class, ModuleIssues.class, + // Measures + DefaultFileLinesContextFactory.class, + // issue exclusions IssueInclusionPatternInitializer.class, IssueExclusionPatternInitializer.class, IssueExclusionsRegexpScanner.class, IssueExclusionsLoader.class, EnforceIssuesFilter.class, - IgnoreIssuesFilter.class); + IgnoreIssuesFilter.class, + + AnalyzisPublisher.class); } private void addExtensions() { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanExecutor.java index 55423ee2eb5..db5406afad4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanExecutor.java @@ -19,11 +19,10 @@ */ package org.sonar.batch.scan2; -import org.sonar.api.batch.analyzer.AnalyzerContext; - import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.api.batch.analyzer.AnalyzerContext; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; import org.sonar.batch.phases.SensorsExecutor; @@ -44,16 +43,19 @@ public final class ModuleScanExecutor { private final QProfileVerifier profileVerifier; private final IssueExclusionsLoader issueExclusionsLoader; + private AnalyzisPublisher analyzisPublisher; + public ModuleScanExecutor(AnalyzersExecutor analyzersExecutor, AnalyzerContext analyzerContext, FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, - IssueExclusionsLoader issueExclusionsLoader) { + IssueExclusionsLoader issueExclusionsLoader, AnalyzisPublisher analyzisPublisher) { this.analyzersExecutor = analyzersExecutor; this.analyzerContext = analyzerContext; this.fsLogger = fsLogger; this.fs = fs; this.profileVerifier = profileVerifier; this.issueExclusionsLoader = issueExclusionsLoader; + this.analyzisPublisher = analyzisPublisher; } public static Collection<Class> getPhaseClasses() { @@ -76,5 +78,9 @@ public final class ModuleScanExecutor { issueExclusionsLoader.execute(); analyzersExecutor.execute(analyzerContext); + + // Export results + analyzisPublisher.execute(); + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/tasks/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/tasks/package-info.java new file mode 100644 index 00000000000..0f2b4e035bb --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/tasks/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.tasks; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/XooMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/XooMediumTest.java index 0b3cc9d77ef..6a9fdb2403f 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/XooMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/XooMediumTest.java @@ -21,6 +21,8 @@ package org.sonar.batch.mediumtest.xoo; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.analyzer.issue.AnalyzerIssue; @@ -43,7 +45,6 @@ public class XooMediumTest { @org.junit.Rule public TemporaryFolder temp = new TemporaryFolder(); - @org.junit.Rule public AnalyzerMediumTester tester = AnalyzerMediumTester.builder() // .registerPlugin("xoo", new File("target/sonar-xoo-plugin-2.0-SNAPSHOT.jar")) .registerPlugin("xoo", new XooPlugin()) @@ -53,21 +54,31 @@ public class XooMediumTest { .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) .build(); + @Before + public void prepare() throws Throwable { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + @Test - public void mediumTestOfSample() throws Exception { - File projectDir = new File(XooMediumTest.class.getResource("/org/sonar/batch/mediumtest/xoo/sample").toURI()); + public void mediumTestOfSampleProject() throws Exception { + File projectDir = new File(XooMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); TaskResult result = tester .newScanTask(new File(projectDir, "sonar-project.properties")) .start(); - assertThat(result.measures()).hasSize(13); + assertThat(result.measures()).hasSize(19); assertThat(result.issues()).hasSize(24); } @Test public void testIssueExclusion() throws Exception { - File projectDir = new File(XooMediumTest.class.getResource("/org/sonar/batch/mediumtest/xoo/sample").toURI()); + File projectDir = new File(XooMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); TaskResult result = tester .newScanTask(new File(projectDir, "sonar-project.properties")) @@ -75,12 +86,12 @@ public class XooMediumTest { .property("sonar.issue.ignore.allfile.1.fileRegexp", "object") .start(); - assertThat(result.measures()).hasSize(13); + assertThat(result.measures()).hasSize(19); assertThat(result.issues()).hasSize(19); } @Test - public void mediumTest() throws IOException { + public void testMeasuresAndIssues() throws IOException { File baseDir = temp.newFolder(); File srcDir = new File(baseDir, "src"); @@ -124,4 +135,52 @@ public class XooMediumTest { assertThat(foundIssueAtLine1).isTrue(); } + @Test + public void testScmActivityAnalyzer() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooMeasureFile = new File(srcDir, "sample.xoo.measures"); + File xooScmFile = new File(srcDir, "sample.xoo.scm"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + FileUtils.write(xooMeasureFile, "lines:5"); + FileUtils.write(xooScmFile, + // revision,author,dateTime + "1,julien,2013-01-04\n" + + "1,julien,2013-01-04\n" + + "2,julien,2013-02-03\n" + + "2,julien,2013-02-03\n" + + "3,simon,2013-03-04\n" + ); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.measures()).hasSize(4); + + assertThat(result.measures()).contains(new DefaultAnalyzerMeasureBuilder<Integer>() + .forMetric(CoreMetrics.LINES) + .onFile(new DefaultInputFile("src/sample.xoo")) + .withValue(5) + .build()); + + assertThat(result.measures()).contains(new DefaultAnalyzerMeasureBuilder<String>() + .forMetric(CoreMetrics.SCM_AUTHORS_BY_LINE) + .onFile(new DefaultInputFile("src/sample.xoo")) + .withValue("1=julien;2=julien;3=julien;4=julien;5=simon") + .build()); + } + } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java index a93cdbf6b22..ff113906138 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java @@ -22,6 +22,7 @@ package org.sonar.batch.mediumtest.xoo.plugin; import org.sonar.api.SonarPlugin; import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; import org.sonar.batch.mediumtest.xoo.plugin.lang.MeasureAnalyzer; +import org.sonar.batch.mediumtest.xoo.plugin.lang.ScmActivityAnalyzer; import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssuePerLineAnalyzer; import java.util.Arrays; @@ -34,6 +35,7 @@ public final class XooPlugin extends SonarPlugin { return Arrays.asList( // language MeasureAnalyzer.class, + ScmActivityAnalyzer.class, Xoo.class, // rules diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivityAnalyzer.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivityAnalyzer.java new file mode 100644 index 00000000000..346036a52ff --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivityAnalyzer.java @@ -0,0 +1,112 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.xoo.plugin.lang; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.analyzer.Analyzer; +import org.sonar.api.batch.analyzer.AnalyzerContext; +import org.sonar.api.batch.analyzer.AnalyzerDescriptor; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.api.utils.DateUtils; +import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.List; + +public class ScmActivityAnalyzer implements Analyzer { + + private static final Logger LOG = LoggerFactory.getLogger(ScmActivityAnalyzer.class); + + private static final String SCM_EXTENSION = ".scm"; + + private final FileSystem fs; + private final FileLinesContextFactory fileLinesContextFactory; + + public ScmActivityAnalyzer(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem) { + this.fs = fileSystem; + this.fileLinesContextFactory = fileLinesContextFactory; + } + + @Override + public void describe(AnalyzerDescriptor descriptor) { + descriptor + .name(this.getClass().getSimpleName()) + .provides(CoreMetrics.SCM_AUTHORS_BY_LINE, + CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, + CoreMetrics.SCM_REVISIONS_BY_LINE) + .workOnLanguages(Xoo.KEY); + } + + @Override + public void analyse(AnalyzerContext context) { + for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(Xoo.KEY))) { + processFile(inputFile); + } + + } + + @VisibleForTesting + protected void processFile(InputFile inputFile) { + File ioFile = inputFile.file(); + File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION); + if (!scmDataFile.exists()) { + LOG.debug("Skipping SCM data injection for " + inputFile.relativePath()); + return; + } + + FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile); + try { + List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name()); + int lineNumber = 0; + for (String line : lines) { + lineNumber++; + if (StringUtils.isNotBlank(line)) { + // revision,author,dateTime + String[] fields = StringUtils.split(line, ','); + if (fields.length < 3) { + throw new IllegalStateException("Not enough fields on line " + lineNumber); + } + String revision = fields[0]; + String author = fields[1]; + // Will throw an exception, when date is not in format "yyyy-MM-dd" + Date date = DateUtils.parseDate(fields[2]); + + fileLinesContext.setStringValue(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY, lineNumber, revision); + fileLinesContext.setStringValue(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY, lineNumber, author); + fileLinesContext.setStringValue(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, lineNumber, DateUtils.formatDateTime(date)); + } + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + fileLinesContext.save(); + } +} diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/sonar-project.properties b/sonar-batch/src/test/resources/mediumtest/xoo/sample/sonar-project.properties index 8810e376701..8810e376701 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/sonar-project.properties +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/sonar-project.properties diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo index 8c0967e496f..8c0967e496f 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures index 23b08dc0e0e..23b08dc0e0e 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm index 2cec35b8a72..2cec35b8a72 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo index 1d9c60d56b7..1d9c60d56b7 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures index 388d08b58a8..388d08b58a8 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm index 03a9de2f486..03a9de2f486 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/helloscala.xoo b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo index 53cb085156c..53cb085156c 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/helloscala.xoo +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo diff --git a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures index c47948fc955..c47948fc955 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures +++ b/sonar-batch/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures |