aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-protocol
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-scanner-protocol')
-rw-r--r--sonar-scanner-protocol/pom.xml111
-rw-r--r--sonar-scanner-protocol/src/main/assembly/viewer.xml22
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java35
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java44
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java72
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java120
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java115
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java24
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java78
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java172
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java141
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java24
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java24
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java388
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java457
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java24
-rw-r--r--sonar-scanner-protocol/src/main/protobuf/constants.proto79
-rw-r--r--sonar-scanner-protocol/src/main/protobuf/scanner_input.proto56
-rw-r--r--sonar-scanner-protocol/src/main/protobuf/scanner_report.proto213
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java67
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java74
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java391
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java308
-rw-r--r--sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json21
-rw-r--r--sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json23
-rw-r--r--sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json1
-rw-r--r--sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json1
-rw-r--r--sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json43
28 files changed, 3128 insertions, 0 deletions
diff --git a/sonar-scanner-protocol/pom.xml b/sonar-scanner-protocol/pom.xml
new file mode 100644
index 00000000000..ed015f33bc7
--- /dev/null
+++ b/sonar-scanner-protocol/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonarqube</artifactId>
+ <version>5.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sonar-scanner-protocol</artifactId>
+ <name>SonarQube :: Scanner :: Protocol</name>
+
+ <description>Classes used for communication between scanner and server</description>
+
+ <properties>
+ <!-- Viewer is for our internal use. This is not production code and mostly generated with Eclipse GUI builder -->
+ <sonar.exclusions>target/generated-sources/**/*,src/main/java/org/sonar/scanner/protocol/viewer/**</sonar.exclusions>
+ <sonar.test.exclusions>target/generated-test-sources/**/*</sonar.test.exclusions>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.jpountz.lz4</groupId>
+ <artifactId>lz4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-core</artifactId>
+ </dependency>
+
+ <!-- unit tests -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.javacrumbs.json-unit</groupId>
+ <artifactId>json-unit-assertj</artifactId>
+ <version>0.0.15</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>${skipBatchTests}</skipTests>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <finalName>scanner-report-viewer-${project.version}</finalName>
+ <archive>
+ <manifest>
+ <mainClass>org.sonar.scanner.protocol.viewer.ScannerReportViewerApp</mainClass>
+ </manifest>
+ </archive>
+ <descriptors>
+ <descriptor>src/main/assembly/viewer.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sonar-scanner-protocol/src/main/assembly/viewer.xml b/sonar-scanner-protocol/src/main/assembly/viewer.xml
new file mode 100644
index 00000000000..dcb1ad07c78
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/assembly/viewer.xml
@@ -0,0 +1,22 @@
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+ <id>full</id>
+ <formats>
+ <format>jar</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory>/</outputDirectory>
+ <useProjectArtifact>true</useProjectArtifact>
+ <unpack>true</unpack>
+ <scope>runtime</scope>
+ </dependencySet>
+ </dependencySets>
+ <fileSets>
+ <fileSet>
+ <directory>${project.build.outputDirectory}</directory>
+ </fileSet>
+ </fileSets>
+</assembly> \ No newline at end of file
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java
new file mode 100644
index 00000000000..890db7e52e1
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class GsonHelper {
+
+ private GsonHelper() {
+ // Utility class
+ }
+
+ public static Gson create() {
+ return new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").setPrettyPrinting().create();
+ }
+
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java
new file mode 100644
index 00000000000..be9d993b5c3
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class FileData {
+
+ private final String hash;
+ private final String revision;
+
+ public FileData(@Nullable String hash, @Nullable String revision) {
+ this.hash = hash;
+ this.revision = revision;
+ }
+
+ @CheckForNull
+ public String hash() {
+ return hash;
+ }
+
+ @CheckForNull
+ public String revision() {
+ return revision;
+ }
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java
new file mode 100644
index 00000000000..7bf3bdb643a
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.sonar.scanner.protocol.GsonHelper;
+
+/**
+ * Container for all global data going from server to batch.
+ * This is not an API since server and batch always share the same version.
+ */
+public class GlobalRepositories {
+
+ private long timestamp;
+ private Collection<Metric> metrics = new ArrayList<>();
+ private Map<String, String> globalSettings = new HashMap<>();
+
+ public Map<String, String> globalSettings() {
+ return globalSettings;
+ }
+
+ public GlobalRepositories addGlobalSetting(String key, String value) {
+ globalSettings.put(key, value);
+ return this;
+ }
+
+ public Collection<Metric> metrics() {
+ return metrics;
+ }
+
+ public GlobalRepositories addMetric(Metric metric) {
+ metrics.add(metric);
+ return this;
+ }
+
+ public long timestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String toJson() {
+ return GsonHelper.create().toJson(this);
+ }
+
+ public static GlobalRepositories fromJson(String json) {
+ return GsonHelper.create().fromJson(json, GlobalRepositories.class);
+ }
+
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java
new file mode 100644
index 00000000000..24c68c76414
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class Metric {
+
+ private final int id;
+
+ private final String key;
+
+ private final String valueType;
+
+ private final String description;
+
+ private final int direction;
+
+ private final String name;
+
+ private final boolean qualitative;
+
+ private final boolean userManaged;
+
+ private final Double worstValue;
+
+ private final Double bestValue;
+
+ private final boolean optimizedBestValue;
+
+ public Metric(int id,
+ String key,
+ String valueType,
+ @Nullable String description,
+ int direction,
+ String name,
+ boolean qualitative,
+ boolean userManaged,
+ @Nullable Double worstValue,
+ @Nullable Double bestValue,
+ boolean optimizedBestValue) {
+ this.id = id;
+ this.key = key;
+ this.valueType = valueType;
+ this.description = description;
+ this.direction = direction;
+ this.name = name;
+ this.qualitative = qualitative;
+ this.userManaged = userManaged;
+ this.worstValue = worstValue;
+ this.bestValue = bestValue;
+ this.optimizedBestValue = optimizedBestValue;
+ }
+
+ public int id() {
+ return id;
+ }
+
+ public String key() {
+ return key;
+ }
+
+ public String valueType() {
+ return valueType;
+ }
+
+ @CheckForNull
+ public String description() {
+ return description;
+ }
+
+ public int direction() {
+ return direction;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public boolean isQualitative() {
+ return qualitative;
+ }
+
+ public boolean isUserManaged() {
+ return userManaged;
+ }
+
+ @CheckForNull
+ public Double worstValue() {
+ return worstValue;
+ }
+
+ @CheckForNull
+ public Double bestValue() {
+ return bestValue;
+ }
+
+ public boolean isOptimizedBestValue() {
+ return optimizedBestValue;
+ }
+
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java
new file mode 100644
index 00000000000..d248e2ec290
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.scanner.protocol.GsonHelper;
+
+/**
+ * Container for all project data going from server to batch.
+ * This is not an API since server and batch always share the same version.
+ */
+public class ProjectRepositories {
+
+ private long timestamp;
+ private boolean exists;
+ private Map<String, Map<String, String>> settingsByModule = new HashMap<>();
+ private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<>();
+ private Date lastAnalysisDate;
+
+ public Map<String, String> settings(String moduleKey) {
+ return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap();
+ }
+
+ public Map<String, Map<String, String>> settings() {
+ return settingsByModule;
+ }
+
+ public ProjectRepositories addSettings(String moduleKey, Map<String, String> settings) {
+ Map<String, String> existingSettings = settingsByModule.get(moduleKey);
+ if (existingSettings == null) {
+ existingSettings = new HashMap<>();
+ settingsByModule.put(moduleKey, existingSettings);
+ }
+ existingSettings.putAll(settings);
+ return this;
+ }
+
+ public boolean exists() {
+ return exists;
+ }
+
+ public Map<String, Map<String, FileData>> fileDataByModuleAndPath() {
+ return fileDataByModuleAndPath;
+ }
+
+ public Map<String, FileData> fileDataByPath(String moduleKey) {
+ return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
+ }
+
+ public ProjectRepositories addFileData(String moduleKey, @Nullable String path, FileData fileData) {
+ if (path == null || (fileData.hash() == null && fileData.revision() == null)) {
+ return this;
+ }
+
+ Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey);
+ if (existingFileDataByPath == null) {
+ existingFileDataByPath = new HashMap<>();
+ fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath);
+ }
+ existingFileDataByPath.put(path, fileData);
+ return this;
+ }
+
+ @CheckForNull
+ public FileData fileData(String projectKey, String path) {
+ return fileDataByPath(projectKey).get(path);
+ }
+
+ public long timestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @CheckForNull
+ public Date lastAnalysisDate() {
+ return lastAnalysisDate;
+ }
+
+ public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) {
+ this.lastAnalysisDate = lastAnalysisDate;
+ }
+
+ public String toJson() {
+ return GsonHelper.create().toJson(this);
+ }
+
+ public static ProjectRepositories fromJson(String json) {
+ return GsonHelper.create().fromJson(json, ProjectRepositories.class);
+ }
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java
new file mode 100644
index 00000000000..8df79ed33e1
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
new file mode 100644
index 00000000000..c4d3197cb43
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import java.io.File;
+
+/**
+ * Structure of files in the zipped report
+ */
+public class FileStructure {
+
+ public enum Domain {
+ ISSUES("issues-", Domain.PB),
+ COMPONENT("component-", Domain.PB),
+ MEASURES("measures-", Domain.PB),
+ DUPLICATIONS("duplications-", Domain.PB),
+ CPD_TEXT_BLOCKS("cpd-text-block-", Domain.PB),
+ SYNTAX_HIGHLIGHTINGS("syntax-highlightings-", Domain.PB),
+ CHANGESETS("changesets-", Domain.PB),
+ SYMBOLS("symbols-", Domain.PB),
+ COVERAGES("coverages-", Domain.PB),
+ TESTS("tests-", Domain.PB),
+ COVERAGE_DETAILS("coverage-details-", Domain.PB),
+ SOURCE("source-", ".txt");
+
+ private static final String PB = ".pb";
+ private final String filePrefix;
+ private final String fileSuffix;
+
+ Domain(String filePrefix, String fileSuffix) {
+ this.filePrefix = filePrefix;
+ this.fileSuffix = fileSuffix;
+ }
+ }
+
+ private final File dir;
+
+ public FileStructure(File dir) {
+ if (!dir.exists() || !dir.isDirectory()) {
+ throw new IllegalArgumentException("Directory of analysis report does not exist: " + dir);
+ }
+ this.dir = dir;
+ }
+
+ public File metadataFile() {
+ return new File(dir, "metadata.pb");
+ }
+
+ public File analysisLog() {
+ return new File(dir, "analysis.log");
+ }
+
+ public File activeRules() {
+ return new File(dir, "activerules.pb");
+ }
+
+ public File fileFor(Domain domain, int componentRef) {
+ return new File(dir, domain.filePrefix + componentRef + domain.fileSuffix);
+ }
+
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java
new file mode 100644
index 00000000000..d47b94225ef
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java
@@ -0,0 +1,172 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import java.io.File;
+import javax.annotation.CheckForNull;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.Protobuf;
+
+import static org.sonar.core.util.CloseableIterator.emptyCloseableIterator;
+
+public class ScannerReportReader {
+
+ private final FileStructure fileStructure;
+
+ public ScannerReportReader(File dir) {
+ this.fileStructure = new FileStructure(dir);
+ }
+
+ public ScannerReport.Metadata readMetadata() {
+ File file = fileStructure.metadataFile();
+ if (!fileExists(file)) {
+ throw new IllegalStateException("Metadata file is missing in analysis report: " + file);
+ }
+ return Protobuf.read(file, ScannerReport.Metadata.PARSER);
+ }
+
+ public CloseableIterator<ScannerReport.ActiveRule> readActiveRules() {
+ File file = fileStructure.activeRules();
+ if (!fileExists(file)) {
+ return emptyCloseableIterator();
+ }
+ return Protobuf.readStream(file, ScannerReport.ActiveRule.PARSER);
+ }
+
+ public CloseableIterator<ScannerReport.Measure> readComponentMeasures(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.Measure.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ @CheckForNull
+ public ScannerReport.Changesets readChangesets(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.CHANGESETS, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.read(file, ScannerReport.Changesets.PARSER);
+ }
+ return null;
+ }
+
+ public ScannerReport.Component readComponent(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COMPONENT, componentRef);
+ if (!fileExists(file)) {
+ throw new IllegalStateException("Unable to find report for component #" + componentRef + ". File does not exist: " + file);
+ }
+ return Protobuf.read(file, ScannerReport.Component.PARSER);
+ }
+
+ public CloseableIterator<ScannerReport.Issue> readComponentIssues(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.Issue.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ public CloseableIterator<ScannerReport.Duplication> readComponentDuplications(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.Duplication.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ public CloseableIterator<ScannerReport.CpdTextBlock> readCpdTextBlocks(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.CpdTextBlock.parser());
+ }
+ return emptyCloseableIterator();
+ }
+
+ public CloseableIterator<ScannerReport.Symbol> readComponentSymbols(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SYMBOLS, componentRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.Symbol.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ public boolean hasSyntaxHighlighting(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, componentRef);
+ return file.exists();
+ }
+
+ public CloseableIterator<ScannerReport.SyntaxHighlighting> readComponentSyntaxHighlighting(int fileRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, fileRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.SyntaxHighlighting.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ public boolean hasCoverage(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef);
+ return file.exists();
+ }
+
+ public CloseableIterator<ScannerReport.Coverage> readComponentCoverage(int fileRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, fileRef);
+ if (fileExists(file)) {
+ return Protobuf.readStream(file, ScannerReport.Coverage.PARSER);
+ }
+ return emptyCloseableIterator();
+ }
+
+ @CheckForNull
+ public File readFileSource(int fileRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SOURCE, fileRef);
+ if (fileExists(file)) {
+ return file;
+ }
+ return null;
+ }
+
+ @CheckForNull
+ public File readTests(int testFileRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.TESTS, testFileRef);
+ if (fileExists(file)) {
+ return file;
+ }
+
+ return null;
+ }
+
+ @CheckForNull
+ public File readCoverageDetails(int testFileRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGE_DETAILS, testFileRef);
+ if (fileExists(file)) {
+ return file;
+ }
+
+ return null;
+ }
+
+ private static boolean fileExists(File file) {
+ return file.exists() && file.isFile();
+ }
+
+ public FileStructure getFileStructure() {
+ return fileStructure;
+ }
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
new file mode 100644
index 00000000000..5cbba578c2a
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
@@ -0,0 +1,141 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import org.sonar.core.util.ContextException;
+import org.sonar.core.util.Protobuf;
+
+public class ScannerReportWriter {
+
+ private final FileStructure fileStructure;
+
+ public ScannerReportWriter(File dir) {
+ if (!dir.exists() && !dir.mkdirs()) {
+ throw new IllegalStateException("Unable to create directory: " + dir);
+ }
+ this.fileStructure = new FileStructure(dir);
+ }
+
+ public FileStructure getFileStructure() {
+ return fileStructure;
+ }
+
+ public boolean hasComponentData(FileStructure.Domain domain, int componentRef) {
+ File file = fileStructure.fileFor(domain, componentRef);
+ return file.exists() && file.isFile();
+ }
+
+ /**
+ * Metadata is mandatory
+ */
+ public File writeMetadata(ScannerReport.Metadata metadata) {
+ Protobuf.write(metadata, fileStructure.metadataFile());
+ return fileStructure.metadataFile();
+ }
+
+ public File writeActiveRules(Iterable<ScannerReport.ActiveRule> activeRules) {
+ Protobuf.writeStream(activeRules, fileStructure.activeRules(), false);
+ return fileStructure.metadataFile();
+ }
+
+ public File writeComponent(ScannerReport.Component component) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COMPONENT, component.getRef());
+ Protobuf.write(component, file);
+ return file;
+ }
+
+ public File writeComponentIssues(int componentRef, Iterable<ScannerReport.Issue> issues) {
+ File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef);
+ Protobuf.writeStream(issues, file, false);
+ return file;
+ }
+
+ public void appendComponentIssue(int componentRef, ScannerReport.Issue issue) {
+ File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef);
+ try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) {
+ issue.writeDelimitedTo(out);
+ } catch (Exception e) {
+ throw ContextException.of("Unable to write issue", e).addContext("file", file);
+ }
+ }
+
+ public File writeComponentMeasures(int componentRef, Iterable<ScannerReport.Measure> measures) {
+ File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef);
+ Protobuf.writeStream(measures, file, false);
+ return file;
+ }
+
+ public File writeComponentChangesets(ScannerReport.Changesets changesets) {
+ File file = fileStructure.fileFor(FileStructure.Domain.CHANGESETS, changesets.getComponentRef());
+ Protobuf.write(changesets, file);
+ return file;
+ }
+
+ public File writeComponentDuplications(int componentRef, Iterable<ScannerReport.Duplication> duplications) {
+ File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef);
+ Protobuf.writeStream(duplications, file, false);
+ return file;
+ }
+
+ public File writeCpdTextBlocks(int componentRef, Iterable<ScannerReport.CpdTextBlock> blocks) {
+ File file = fileStructure.fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, componentRef);
+ Protobuf.writeStream(blocks, file, false);
+ return file;
+ }
+
+ public File writeComponentSymbols(int componentRef, Iterable<ScannerReport.Symbol> symbols) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SYMBOLS, componentRef);
+ Protobuf.writeStream(symbols, file, false);
+ return file;
+ }
+
+ public File writeComponentSyntaxHighlighting(int componentRef, Iterable<ScannerReport.SyntaxHighlighting> syntaxHighlightingRules) {
+ File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, componentRef);
+ Protobuf.writeStream(syntaxHighlightingRules, file, false);
+ return file;
+ }
+
+ public File writeComponentCoverage(int componentRef, Iterable<ScannerReport.Coverage> coverageList) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef);
+ Protobuf.writeStream(coverageList, file, false);
+ return file;
+ }
+
+ public File writeTests(int componentRef, Iterable<ScannerReport.Test> tests) {
+ File file = fileStructure.fileFor(FileStructure.Domain.TESTS, componentRef);
+ Protobuf.writeStream(tests, file, false);
+ return file;
+ }
+
+ public File writeCoverageDetails(int componentRef, Iterable<ScannerReport.CoverageDetail> tests) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGE_DETAILS, componentRef);
+ Protobuf.writeStream(tests, file, false);
+ return file;
+ }
+
+ public File getSourceFile(int componentRef) {
+ return fileStructure.fileFor(FileStructure.Domain.SOURCE, componentRef);
+ }
+
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java
new file mode 100644
index 00000000000..7a1b7b47a3e
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java
new file mode 100644
index 00000000000..be3678faecd
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java
new file mode 100644
index 00000000000..3768c1ca0d2
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java
@@ -0,0 +1,388 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.viewer;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Scanner;
+import javax.swing.JEditorPane;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTree;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeSelectionModel;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.protocol.output.ScannerReportReader;
+import org.sonar.scanner.protocol.output.FileStructure.Domain;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Component;
+import org.sonar.scanner.protocol.output.ScannerReport.Metadata;
+
+public class ScannerReportViewerApp {
+
+ private JFrame frame;
+ private ScannerReportReader reader;
+ private Metadata metadata;
+ private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+ private JTree componentTree;
+ private JSplitPane splitPane;
+ private JTabbedPane tabbedPane;
+ private JScrollPane treeScrollPane;
+ private JScrollPane componentDetailsTab;
+ private JScrollPane highlightingTab;
+ private JScrollPane symbolTab;
+ private JEditorPane componentEditor;
+ private JEditorPane highlightingEditor;
+ private JEditorPane symbolEditor;
+ private JScrollPane sourceTab;
+ private JEditorPane sourceEditor;
+ private JScrollPane coverageTab;
+ private JEditorPane coverageEditor;
+ private TextLineNumber textLineNumber;
+ private JScrollPane duplicationTab;
+ private JEditorPane duplicationEditor;
+
+ /**
+ * Create the application.
+ */
+ public ScannerReportViewerApp() {
+ initialize();
+ }
+
+ /**
+ * Launch the application.
+ */
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ScannerReportViewerApp window = new ScannerReportViewerApp();
+ window.frame.setVisible(true);
+
+ window.loadReport();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ });
+ }
+
+ private void loadReport() {
+ final JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Choose scanner report directory");
+ fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fc.setFileHidingEnabled(false);
+ fc.setApproveButtonText("Open scanner report");
+ int returnVal = fc.showOpenDialog(frame);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ File file = fc.getSelectedFile();
+ try {
+ loadReport(file);
+ } catch (Exception e) {
+ JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ exit();
+ }
+ } else {
+ exit();
+ }
+
+ }
+
+ private void exit() {
+ frame.setVisible(false);
+ frame.dispose();
+ }
+
+ private void loadReport(File file) {
+ reader = new ScannerReportReader(file);
+ metadata = reader.readMetadata();
+ updateTitle();
+ loadComponents();
+ }
+
+ private void loadComponents() {
+ int rootComponentRef = metadata.getRootComponentRef();
+ Component component = reader.readComponent(rootComponentRef);
+ DefaultMutableTreeNode project = createNode(component);
+ loadChildren(component, project);
+ getComponentTree().setModel(new DefaultTreeModel(project));
+ }
+
+ private static DefaultMutableTreeNode createNode(Component component) {
+ return new DefaultMutableTreeNode(component) {
+ @Override
+ public String toString() {
+ return getNodeName((Component) getUserObject());
+ }
+ };
+ }
+
+ private static String getNodeName(Component component) {
+ switch (component.getType()) {
+ case PROJECT:
+ case MODULE:
+ return component.getName();
+ case DIRECTORY:
+ case FILE:
+ return component.getPath();
+ default:
+ throw new IllegalArgumentException("Unknow component type: " + component.getType());
+ }
+ }
+
+ private void loadChildren(Component parentComponent, DefaultMutableTreeNode parentNode) {
+ for (int ref : parentComponent.getChildRefList()) {
+ Component child = reader.readComponent(ref);
+ DefaultMutableTreeNode childNode = createNode(child);
+ parentNode.add(childNode);
+ loadChildren(child, childNode);
+ }
+
+ }
+
+ private void updateTitle() {
+ frame.setTitle(metadata.getProjectKey() + (metadata.hasBranch() ? (" (" + metadata.getBranch() + ")") : "") + " " + sdf.format(new Date(metadata.getAnalysisDate())));
+ }
+
+ private void updateDetails(Component component) {
+ componentEditor.setText(component.toString());
+ updateHighlighting(component);
+ updateSymbols(component);
+ updateSource(component);
+ updateCoverage(component);
+ updateDuplications(component);
+ }
+
+ private void updateDuplications(Component component) {
+ duplicationEditor.setText("");
+ if (reader.hasCoverage(component.getRef())) {
+ try (CloseableIterator<ScannerReport.Duplication> it = reader.readComponentDuplications(component.getRef())) {
+ while (it.hasNext()) {
+ ScannerReport.Duplication dup = it.next();
+ duplicationEditor.getDocument().insertString(duplicationEditor.getDocument().getEndPosition().getOffset(), dup.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read duplications for " + getNodeName(component), e);
+ }
+ }
+ }
+
+ private void updateCoverage(Component component) {
+ coverageEditor.setText("");
+ try (CloseableIterator<ScannerReport.Coverage> it = reader.readComponentCoverage(component.getRef())) {
+ while (it.hasNext()) {
+ ScannerReport.Coverage coverage = it.next();
+ coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e);
+ }
+ }
+
+ private void updateSource(Component component) {
+ File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef());
+ sourceEditor.setText("");
+
+ if (sourceFile.exists()) {
+ try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) {
+ if (s.hasNext()) {
+ sourceEditor.setText(s.next());
+ }
+ } catch (IOException ex) {
+ StringWriter errors = new StringWriter();
+ ex.printStackTrace(new PrintWriter(errors));
+ sourceEditor.setText(errors.toString());
+ }
+ }
+ }
+
+ private void updateHighlighting(Component component) {
+ highlightingEditor.setText("");
+ try (CloseableIterator<ScannerReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(component.getRef())) {
+ while (it.hasNext()) {
+ ScannerReport.SyntaxHighlighting rule = it.next();
+ highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e);
+ }
+ }
+
+ private void updateSymbols(Component component) {
+ symbolEditor.setText("");
+ try (CloseableIterator<ScannerReport.Symbol> it = reader.readComponentSymbols(component.getRef())) {
+ while (it.hasNext()) {
+ ScannerReport.Symbol symbol = it.next();
+ symbolEditor.getDocument().insertString(symbolEditor.getDocument().getEndPosition().getOffset(), symbol.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read symbol references for " + getNodeName(component), e);
+ }
+ }
+
+ /**
+ * Initialize the contents of the frame.
+ */
+ private void initialize() {
+ try {
+ for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
+ if ("Nimbus".equals(info.getName())) {
+ UIManager.setLookAndFeel(info.getClassName());
+ break;
+ }
+ }
+ } catch (Exception e) {
+ // If Nimbus is not available, you can set the GUI to another look and feel.
+ }
+ frame = new JFrame();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ splitPane = new JSplitPane();
+ frame.getContentPane().add(splitPane, BorderLayout.CENTER);
+
+ tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+ tabbedPane.setPreferredSize(new Dimension(500, 7));
+ splitPane.setRightComponent(tabbedPane);
+
+ componentDetailsTab = new JScrollPane();
+ tabbedPane.addTab("Component details", null, componentDetailsTab, null);
+
+ componentEditor = new JEditorPane();
+ componentDetailsTab.setViewportView(componentEditor);
+
+ sourceTab = new JScrollPane();
+ tabbedPane.addTab("Source", null, sourceTab, null);
+
+ sourceEditor = createSourceEditor();
+ sourceEditor.setEditable(false);
+ sourceTab.setViewportView(sourceEditor);
+
+ textLineNumber = createTextLineNumber();
+ sourceTab.setRowHeaderView(textLineNumber);
+
+ highlightingTab = new JScrollPane();
+ tabbedPane.addTab("Highlighting", null, highlightingTab, null);
+
+ highlightingEditor = new JEditorPane();
+ highlightingTab.setViewportView(highlightingEditor);
+
+ symbolTab = new JScrollPane();
+ tabbedPane.addTab("Symbol references", null, symbolTab, null);
+
+ symbolEditor = new JEditorPane();
+ symbolTab.setViewportView(symbolEditor);
+
+ coverageTab = new JScrollPane();
+ tabbedPane.addTab("Coverage", null, coverageTab, null);
+
+ coverageEditor = new JEditorPane();
+ coverageTab.setViewportView(coverageEditor);
+
+ duplicationTab = new JScrollPane();
+ tabbedPane.addTab("Duplications", null, duplicationTab, null);
+
+ duplicationEditor = new JEditorPane();
+ duplicationTab.setViewportView(duplicationEditor);
+
+ treeScrollPane = new JScrollPane();
+ treeScrollPane.setPreferredSize(new Dimension(200, 400));
+ splitPane.setLeftComponent(treeScrollPane);
+
+ componentTree = new JTree();
+ componentTree.setModel(new DefaultTreeModel(
+ new DefaultMutableTreeNode("empty") {
+ {
+ }
+ }));
+ treeScrollPane.setViewportView(componentTree);
+ componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ componentTree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent();
+
+ if (node == null) {
+ // Nothing is selected.
+ return;
+ }
+
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ updateDetails((Component) node.getUserObject());
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+
+ }
+
+ });
+ frame.pack();
+ }
+
+ public JTree getComponentTree() {
+ return componentTree;
+ }
+
+ /**
+ * @wbp.factory
+ */
+ public static JPanel createComponentPanel() {
+ JPanel panel = new JPanel();
+ return panel;
+ }
+
+ protected JEditorPane getComponentEditor() {
+ return componentEditor;
+ }
+
+ /**
+ * @wbp.factory
+ */
+ public static JEditorPane createSourceEditor() {
+ JEditorPane editorPane = new JEditorPane();
+ return editorPane;
+ }
+
+ /**
+ * @wbp.factory
+ */
+ public TextLineNumber createTextLineNumber() {
+ TextLineNumber textLineNumber = new TextLineNumber(sourceEditor);
+ return textLineNumber;
+ }
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java
new file mode 100644
index 00000000000..138b8e3a0e5
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java
@@ -0,0 +1,457 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.viewer;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.MatteBorder;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Element;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.Utilities;
+
+/**
+ * This class will display line numbers for a related text component. The text
+ * component must use the same line height for each line. TextLineNumber
+ * supports wrapped lines and will highlight the line number of the current
+ * line in the text component.
+ *
+ * This class was designed to be used as a component added to the row header
+ * of a JScrollPane.
+ */
+public class TextLineNumber extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener {
+ public static final float LEFT = 0.0f;
+ public static final float CENTER = 0.5f;
+ public static final float RIGHT = 1.0f;
+
+ private static final Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
+
+ private static final int HEIGHT = Integer.MAX_VALUE - 1000000;
+
+ // Text component this TextTextLineNumber component is in sync with
+
+ private JTextComponent component;
+
+ // Properties that can be changed
+
+ private boolean updateFont;
+ private int borderGap;
+ private Color currentLineForeground;
+ private float digitAlignment;
+ private int minimumDisplayDigits;
+
+ // Keep history information to reduce the number of times the component
+ // needs to be repainted
+
+ private int lastDigits;
+ private int lastHeight;
+ private int lastLine;
+
+ private HashMap<String, FontMetrics> fonts;
+
+ /**
+ * Create a line number component for a text component. This minimum
+ * display width will be based on 3 digits.
+ *
+ * @param component the related text component
+ */
+ public TextLineNumber(JTextComponent component) {
+ this(component, 3);
+ }
+
+ /**
+ * Create a line number component for a text component.
+ *
+ * @param component the related text component
+ * @param minimumDisplayDigits the number of digits used to calculate
+ * the minimum width of the component
+ */
+ public TextLineNumber(JTextComponent component, int minimumDisplayDigits) {
+ this.component = component;
+
+ setFont(component.getFont());
+
+ setBorderGap(5);
+ setCurrentLineForeground(Color.RED);
+ setDigitAlignment(RIGHT);
+ setMinimumDisplayDigits(minimumDisplayDigits);
+
+ component.getDocument().addDocumentListener(this);
+ component.addCaretListener(this);
+ component.addPropertyChangeListener("font", this);
+ }
+
+ /**
+ * Gets the update font property
+ *
+ * @return the update font property
+ */
+ public boolean getUpdateFont() {
+ return updateFont;
+ }
+
+ /**
+ * Set the update font property. Indicates whether this Font should be
+ * updated automatically when the Font of the related text component
+ * is changed.
+ *
+ * @param updateFont when true update the Font and repaint the line
+ * numbers, otherwise just repaint the line numbers.
+ */
+ public void setUpdateFont(boolean updateFont) {
+ this.updateFont = updateFont;
+ }
+
+ /**
+ * Gets the border gap
+ *
+ * @return the border gap in pixels
+ */
+ public int getBorderGap() {
+ return borderGap;
+ }
+
+ /**
+ * The border gap is used in calculating the left and right insets of the
+ * border. Default value is 5.
+ *
+ * @param borderGap the gap in pixels
+ */
+ public void setBorderGap(int borderGap) {
+ this.borderGap = borderGap;
+ Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
+ setBorder(new CompoundBorder(OUTER, inner));
+ lastDigits = 0;
+ setPreferredWidth();
+ }
+
+ /**
+ * Gets the current line rendering Color
+ *
+ * @return the Color used to render the current line number
+ */
+ public Color getCurrentLineForeground() {
+ return currentLineForeground == null ? getForeground() : currentLineForeground;
+ }
+
+ /**
+ * The Color used to render the current line digits. Default is Coolor.RED.
+ *
+ * @param currentLineForeground the Color used to render the current line
+ */
+ public void setCurrentLineForeground(Color currentLineForeground) {
+ this.currentLineForeground = currentLineForeground;
+ }
+
+ /**
+ * Gets the digit alignment
+ *
+ * @return the alignment of the painted digits
+ */
+ public float getDigitAlignment() {
+ return digitAlignment;
+ }
+
+ /**
+ * Specify the horizontal alignment of the digits within the component.
+ * Common values would be:
+ * <ul>
+ * <li>TextLineNumber.LEFT
+ * <li>TextLineNumber.CENTER
+ * <li>TextLineNumber.RIGHT (default)
+ * </ul>
+ * @param currentLineForeground the Color used to render the current line
+ */
+ public void setDigitAlignment(float digitAlignment) {
+ this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
+ }
+
+ /**
+ * Gets the minimum display digits
+ *
+ * @return the minimum display digits
+ */
+ public int getMinimumDisplayDigits() {
+ return minimumDisplayDigits;
+ }
+
+ /**
+ * Specify the mimimum number of digits used to calculate the preferred
+ * width of the component. Default is 3.
+ *
+ * @param minimumDisplayDigits the number digits used in the preferred
+ * width calculation
+ */
+ public void setMinimumDisplayDigits(int minimumDisplayDigits) {
+ this.minimumDisplayDigits = minimumDisplayDigits;
+ setPreferredWidth();
+ }
+
+ /**
+ * Calculate the width needed to display the maximum line number
+ */
+ private void setPreferredWidth() {
+ Element root = component.getDocument().getDefaultRootElement();
+ int lines = root.getElementCount();
+ int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
+
+ // Update sizes when number of digits in the line number changes
+
+ if (lastDigits != digits) {
+ lastDigits = digits;
+ FontMetrics fontMetrics = getFontMetrics(getFont());
+ int width = fontMetrics.charWidth('0') * digits;
+ Insets insets = getInsets();
+ int preferredWidth = insets.left + insets.right + width;
+
+ Dimension d = getPreferredSize();
+ d.setSize(preferredWidth, HEIGHT);
+ setPreferredSize(d);
+ setSize(d);
+ }
+ }
+
+ /**
+ * Draw the line numbers
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ // Determine the width of the space available to draw the line number
+
+ FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
+ Insets insets = getInsets();
+ int availableWidth = getSize().width - insets.left - insets.right;
+
+ // Determine the rows to draw within the clipped bounds.
+
+ Rectangle clip = g.getClipBounds();
+ int rowStartOffset = component.viewToModel(new Point(0, clip.y));
+ int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
+
+ while (rowStartOffset <= endOffset) {
+ try {
+ if (isCurrentLine(rowStartOffset))
+ g.setColor(getCurrentLineForeground());
+ else
+ g.setColor(getForeground());
+
+ // Get the line number as a string and then determine the
+ // "X" and "Y" offsets for drawing the string.
+
+ String lineNumber = getTextLineNumber(rowStartOffset);
+ int stringWidth = fontMetrics.stringWidth(lineNumber);
+ int x = getOffsetX(availableWidth, stringWidth) + insets.left;
+ int y = getOffsetY(rowStartOffset, fontMetrics);
+ g.drawString(lineNumber, x, y);
+
+ // Move to the next row
+
+ rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
+ } catch (Exception e) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * We need to know if the caret is currently positioned on the line we
+ * are about to paint so the line number can be highlighted.
+ */
+ private boolean isCurrentLine(int rowStartOffset) {
+ int caretPosition = component.getCaretPosition();
+ Element root = component.getDocument().getDefaultRootElement();
+
+ return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition);
+ }
+
+ /*
+ * Get the line number to be drawn. The empty string will be returned
+ * when a line of text has wrapped.
+ */
+ protected String getTextLineNumber(int rowStartOffset) {
+ Element root = component.getDocument().getDefaultRootElement();
+ int index = root.getElementIndex(rowStartOffset);
+ Element line = root.getElement(index);
+
+ if (line.getStartOffset() == rowStartOffset)
+ return String.valueOf(index + 1);
+ else
+ return "";
+ }
+
+ /*
+ * Determine the X offset to properly align the line number when drawn
+ */
+ private int getOffsetX(int availableWidth, int stringWidth) {
+ return (int) ((availableWidth - stringWidth) * digitAlignment);
+ }
+
+ /*
+ * Determine the Y offset for the current row
+ */
+ private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
+ throws BadLocationException {
+ // Get the bounding rectangle of the row
+
+ Rectangle r = component.modelToView(rowStartOffset);
+ int lineHeight = fontMetrics.getHeight();
+ int y = r.y + r.height;
+ int descent = 0;
+
+ // The text needs to be positioned above the bottom of the bounding
+ // rectangle based on the descent of the font(s) contained on the row.
+
+ if (r.height == lineHeight) // default font is being used
+ {
+ descent = fontMetrics.getDescent();
+ } else // We need to check all the attributes for font changes
+ {
+ if (fonts == null)
+ fonts = new HashMap<>();
+
+ Element root = component.getDocument().getDefaultRootElement();
+ int index = root.getElementIndex(rowStartOffset);
+ Element line = root.getElement(index);
+
+ for (int i = 0; i < line.getElementCount(); i++) {
+ Element child = line.getElement(i);
+ AttributeSet as = child.getAttributes();
+ String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
+ Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
+ String key = fontFamily + fontSize;
+
+ FontMetrics fm = fonts.get(key);
+
+ if (fm == null) {
+ Font font = new Font(fontFamily, Font.PLAIN, fontSize);
+ fm = component.getFontMetrics(font);
+ fonts.put(key, fm);
+ }
+
+ descent = Math.max(descent, fm.getDescent());
+ }
+ }
+
+ return y - descent;
+ }
+
+ //
+ // Implement CaretListener interface
+ //
+ @Override
+ public void caretUpdate(CaretEvent e) {
+ // Get the line the caret is positioned on
+
+ int caretPosition = component.getCaretPosition();
+ Element root = component.getDocument().getDefaultRootElement();
+ int currentLine = root.getElementIndex(caretPosition);
+
+ // Need to repaint so the correct line number can be highlighted
+
+ if (lastLine != currentLine) {
+ repaint();
+ lastLine = currentLine;
+ }
+ }
+
+ //
+ // Implement DocumentListener interface
+ //
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ /*
+ * A document change may affect the number of displayed lines of text.
+ * Therefore the lines numbers will also change.
+ */
+ private void documentChanged() {
+ // View of the component has not been updated at the time
+ // the DocumentEvent is fired
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ int endPos = component.getDocument().getLength();
+ Rectangle rect = component.modelToView(endPos);
+
+ if (rect != null && rect.y != lastHeight) {
+ setPreferredWidth();
+ repaint();
+ lastHeight = rect.y;
+ }
+ } catch (BadLocationException ex) {
+ /* nothing to do */
+ }
+ }
+ });
+ }
+
+ //
+ // Implement PropertyChangeListener interface
+ //
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getNewValue() instanceof Font) {
+ if (updateFont) {
+ Font newFont = (Font) evt.getNewValue();
+ setFont(newFont);
+ lastDigits = 0;
+ setPreferredWidth();
+ } else {
+ repaint();
+ }
+ }
+ }
+}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java
new file mode 100644
index 00000000000..17684b0c408
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.viewer;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-scanner-protocol/src/main/protobuf/constants.proto b/sonar-scanner-protocol/src/main/protobuf/constants.proto
new file mode 100644
index 00000000000..862b4afa54c
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/protobuf/constants.proto
@@ -0,0 +1,79 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2015 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.
+
+syntax = "proto2";
+
+option java_package = "org.sonar.scanner.protocol";
+
+option optimize_for = SPEED;
+
+enum Severity {
+ INFO = 0;
+ MINOR = 1;
+ MAJOR = 2;
+ CRITICAL = 3;
+ BLOCKER = 4;
+}
+
+enum ComponentType {
+ PROJECT = 0;
+ MODULE = 1;
+ DIRECTORY = 2;
+ FILE = 3;
+}
+
+enum MeasureValueType {
+ INT = 0;
+ LONG = 1;
+ DOUBLE = 2;
+ BOOLEAN = 3;
+ STRING = 4;
+}
+
+// temporary enum during development of computation stack
+enum EventCategory {
+ ALERT = 0;
+ PROFILE = 1;
+}
+
+enum ComponentLinkType {
+ HOME = 0;
+ SCM = 1;
+ SCM_DEV = 2;
+ ISSUE = 3;
+ CI = 4;
+}
+
+enum HighlightingType {
+ ANNOTATION = 0;
+ CONSTANT = 1;
+ COMMENT = 2;
+ CPP_DOC = 3;
+ STRUCTURED_COMMENT = 4;
+ KEYWORD = 5;
+ HIGHLIGHTING_STRING = 6;
+ KEYWORD_LIGHT = 7;
+ PREPROCESS_DIRECTIVE = 8;
+}
+
+enum TestStatus {
+ OK = 1;
+ FAILURE = 2;
+ ERROR = 3;
+ SKIPPED = 4;
+}
diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto
new file mode 100644
index 00000000000..db66fe50101
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto
@@ -0,0 +1,56 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2015 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.
+
+/*
+Notes
+
+ - "required" fields are not used as recommended by Google to keep forward-compatibility:
+ https://developers.google.com/protocol-buffers/docs/proto#simple
+
+ - this is beta version of specification. It will evolve during next releases and is
+ not forward-compatible yet.
+*/
+
+syntax = "proto2";
+
+import "constants.proto";
+
+option java_package = "org.sonar.scanner.protocol.input";
+option optimize_for = SPEED;
+
+message ServerIssue {
+ optional string key = 1;
+ optional string module_key = 2;
+ optional string path = 3;
+ optional string rule_repository = 4;
+ optional string rule_key = 5;
+ optional int32 line = 6;
+ optional string msg = 7;
+ optional Severity severity = 8;
+ optional bool manual_severity = 9;
+ optional string resolution = 10;
+ optional string status = 11;
+ optional string checksum = 12;
+ optional string assignee_login = 13;
+ optional int64 creation_date = 14;
+}
+
+message User {
+ optional string login = 1;
+ optional string name = 2;
+}
diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
new file mode 100644
index 00000000000..3df7f4343a6
--- /dev/null
+++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
@@ -0,0 +1,213 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2015 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.
+
+/*
+Notes
+
+ - "required" fields are not used as recommended by Google to keep forward-compatibility:
+ https://developers.google.com/protocol-buffers/docs/proto#simple
+
+ - this is beta version of specification. It will evolve during next releases and is
+ not forward-compatible yet.
+*/
+
+syntax = "proto2";
+
+import "constants.proto";
+
+option java_package = "org.sonar.scanner.protocol.output";
+
+option optimize_for = SPEED;
+
+message Metadata {
+ optional int64 analysis_date = 1;
+ // TODO should we keep this project_key here or not ? Because it's a duplication of Component.key
+ optional string project_key = 2;
+ optional string branch = 3;
+ optional int32 root_component_ref = 4;
+ optional bool cross_project_duplication_activated = 5;
+}
+
+message ActiveRule {
+ optional string rule_repository = 1;
+ optional string rule_key = 2;
+ optional Severity severity = 3;
+ repeated ActiveRuleParam param = 4;
+
+ // TODO replace by map
+ message ActiveRuleParam {
+ optional string key = 1;
+ optional string value = 2;
+ }
+}
+
+message ComponentLink {
+ optional ComponentLinkType type = 1;
+ optional string href = 2;
+}
+
+message Component {
+ optional int32 ref = 1;
+ optional string path = 2;
+ optional string name = 3;
+ optional ComponentType type = 4;
+ optional bool is_test = 5;
+ optional string language = 6;
+ repeated int32 child_ref = 7 [packed = true];
+ repeated ComponentLink link = 8;
+ // Only available on PROJECT and MODULE types
+ optional string version = 9;
+ // Only available on PROJECT and MODULE types
+ // TODO rename this property -> batchKey ? moduleKey ?
+ optional string key = 10;
+ // Only available on FILE type
+ optional int32 lines = 11;
+ // Only available on PROJECT and MODULE types
+ optional string description = 12;
+}
+
+message Measure {
+ optional MeasureValueType value_type = 1;
+ // all values may be unset for "new_xxx" measures (only variations are populated)
+ optional bool boolean_value = 2;
+ optional int32 int_value = 3;
+ optional int64 long_value = 4;
+ optional double double_value = 5;
+ optional string string_value = 6;
+ optional string metric_key = 7;
+}
+
+message Issue {
+ optional string rule_repository = 1;
+ optional string rule_key = 2;
+ // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file.
+ //TODO To be removed. Use first line of text_range instead
+ optional int32 line = 3;
+ optional string msg = 4;
+ optional Severity severity = 5;
+ optional double gap = 6;
+ // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file.
+ optional TextRange text_range = 7;
+ repeated Flow flow = 8;
+}
+
+message IssueLocation {
+ optional int32 component_ref = 1;
+ // Only when component is a file. Can be empty for a file if this is an issue global to the file.
+ optional TextRange text_range = 2;
+ optional string msg = 3;
+}
+
+message Flow {
+ repeated IssueLocation location = 1;
+}
+
+message Changesets {
+ optional int32 component_ref = 1;
+ repeated Changeset changeset = 2;
+ // if changesetIndexByLine[5] = 2 then it means that changeset[2] is the last one on line 6
+ repeated int32 changesetIndexByLine = 3 [packed = true];
+
+ message Changeset {
+ optional string revision = 1;
+ optional string author = 2;
+ optional int64 date = 3;
+ }
+}
+
+message Duplicate {
+ // Will be null when duplicate is in the same file
+ optional int32 other_file_ref = 1;
+ optional TextRange range = 2;
+}
+
+message Duplication {
+ // Origin position in current file
+ optional TextRange origin_position = 1;
+ repeated Duplicate duplicate = 2;
+}
+
+// Used for cross project duplication
+message CpdTextBlock {
+ optional string hash = 1;
+ optional int32 start_line = 2;
+ optional int32 end_line = 3;
+ optional int32 start_token_index = 4;
+ optional int32 end_token_index = 5;
+}
+
+// Lines start at 1 and line offsets start at 0
+message TextRange {
+ // Should never be null
+ optional int32 start_line = 1;
+ // End line (inclusive)
+ optional int32 end_line = 2;
+ // If null it means range starts at the first offset of start line
+ optional int32 start_offset = 3;
+ // If null it means range ends at the last offset of end line
+ optional int32 end_offset = 4;
+}
+
+message Symbol {
+ optional TextRange declaration = 1;
+ repeated TextRange reference = 2;
+}
+
+// Only FILE component has coverage information, and only executable lines should contains this information.
+// TODO rename it LineCoverage ?
+message Coverage {
+ optional int32 line = 1;
+
+ // Number of conditions to cover (if set, the value must be greater than 0)
+ optional int32 conditions = 2;
+ // Is the line has been touched by a unit test ? Returning false means that no test has touched this executable line.
+ optional bool ut_hits = 3;
+ // Is the line has been touched by a integration test ? Returning false means that no test has touched this executable line.
+ optional bool it_hits = 4;
+ // Number of conditions covered by unit tests
+ optional int32 ut_covered_conditions = 5;
+ // Number of conditions covered by integration tests
+ optional int32 it_covered_conditions = 6;
+ // Number of conditions covered by overall tests
+ optional int32 overall_covered_conditions = 7;
+}
+
+// Must be sorted by line and start offset
+// TODO rename it SyntaxHighlightingRule ?
+message SyntaxHighlighting {
+ optional TextRange range = 1;
+ optional HighlightingType type = 2;
+}
+
+message Test {
+ optional string name = 1;
+ optional TestStatus status = 2;
+ optional int64 duration_in_ms = 3;
+ optional string stacktrace = 4;
+ optional string msg = 5;
+}
+
+message CoverageDetail {
+ optional string test_name = 1;
+ repeated CoveredFile covered_file = 2;
+
+ message CoveredFile {
+ optional int32 file_ref = 1;
+ repeated int32 covered_line = 2 [packed = true];
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java
new file mode 100644
index 00000000000..703e182ea8d
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.input;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.sonar.scanner.protocol.input.GlobalRepositories;
+import org.sonar.scanner.protocol.input.Metric;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GlobalRepositoriesTest {
+
+ @Test
+ public void to_json() throws Exception {
+ GlobalRepositories ref = new GlobalRepositories();
+ ref.addMetric(new Metric(1, "ncloc", "INT", "Description", -1, "NCLOC", true, false, 2.0, 1.0, true));
+ ref.addGlobalSetting("prop", "value");
+ ref.setTimestamp(10);
+
+ assertThatJson(ref.toJson())
+ .isEqualTo(IOUtils.toString(getClass().getResource("GlobalRepositoriesTest/expected.json")));
+ }
+
+ @Test
+ public void from_json() {
+ GlobalRepositories ref = GlobalRepositories
+ .fromJson(
+ "{timestamp:1,"
+ + "metrics:[{id:1,key:ncloc,valueType:DATA,description:Description,direction:-1,name:NCLOC,qualitative:true,userManaged:false,worstValue:2.0,bestValue:1.0,optimizedBestValue:true}],"
+ + "globalSettings:{prop:value}}");
+
+ assertThat(ref.timestamp()).isEqualTo(1);
+ Metric metric = ref.metrics().iterator().next();
+ assertThat(metric.id()).isEqualTo(1);
+ assertThat(metric.key()).isEqualTo("ncloc");
+ assertThat(metric.valueType()).isEqualTo("DATA");
+ assertThat(metric.description()).isEqualTo("Description");
+ assertThat(metric.direction()).isEqualTo(-1);
+ assertThat(metric.name()).isEqualTo("NCLOC");
+ assertThat(metric.isQualitative()).isTrue();
+ assertThat(metric.isUserManaged()).isFalse();
+ assertThat(metric.worstValue()).isEqualTo(2.0);
+ assertThat(metric.bestValue()).isEqualTo(1.0);
+ assertThat(metric.isOptimizedBestValue()).isTrue();
+
+ assertThat(ref.globalSettings()).containsEntry("prop", "value");
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java
new file mode 100644
index 00000000000..69d5266cf50
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.scanner.protocol.output.FileStructure;
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class FileStructureTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void fail_if_dir_does_not_exist() throws Exception {
+ File dir = temp.newFolder();
+ FileUtils.deleteQuietly(dir);
+ try {
+ new FileStructure(dir);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageContaining("Directory of analysis report does not exist");
+ }
+ }
+
+ @Test
+ public void fail_if_invalid_dir() throws Exception {
+ // not a dir but a file
+ File dir = temp.newFile();
+ try {
+ new FileStructure(dir);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageContaining("Directory of analysis report does not exist");
+ }
+ }
+
+ @Test
+ public void locate_files() throws Exception {
+ File dir = temp.newFolder();
+ FileUtils.write(new File(dir, "metadata.pb"), "metadata content");
+ FileUtils.write(new File(dir, "issues-3.pb"), "issues of component 3");
+ FileUtils.write(new File(dir, "component-42.pb"), "details of component 42");
+
+ FileStructure structure = new FileStructure(dir);
+ assertThat(structure.metadataFile()).exists().isFile();
+ assertThat(structure.fileFor(FileStructure.Domain.COMPONENT, 42)).exists().isFile();
+ assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 3)).exists().isFile();
+ assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 42)).doesNotExist();
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java
new file mode 100644
index 00000000000..ce2a6e86149
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java
@@ -0,0 +1,391 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.protocol.Constants;
+import org.sonar.scanner.protocol.output.ScannerReportReader;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.protocol.output.FileStructure;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScannerReportReaderTest {
+
+ private static int UNKNOWN_COMPONENT_REF = 123;
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ File dir;
+
+ ScannerReportReader underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ dir = temp.newFolder();
+ underTest = new ScannerReportReader(dir);
+ }
+
+ @Test
+ public void read_metadata() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ ScannerReport.Metadata.Builder metadata = ScannerReport.Metadata.newBuilder()
+ .setAnalysisDate(15000000L)
+ .setProjectKey("PROJECT_A")
+ .setRootComponentRef(1)
+ .setCrossProjectDuplicationActivated(true);
+ writer.writeMetadata(metadata.build());
+
+ ScannerReport.Metadata readMetadata = underTest.readMetadata();
+ assertThat(readMetadata.getAnalysisDate()).isEqualTo(15000000L);
+ assertThat(readMetadata.getProjectKey()).isEqualTo("PROJECT_A");
+ assertThat(readMetadata.getRootComponentRef()).isEqualTo(1);
+ assertThat(readMetadata.getCrossProjectDuplicationActivated()).isTrue();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void fail_if_missing_metadata_file() {
+ underTest.readMetadata();
+ }
+
+ @Test
+ public void read_components() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder()
+ .setRef(1)
+ .setPath("src/main/java/Foo.java");
+ writer.writeComponent(component.build());
+
+ assertThat(underTest.readComponent(1).getPath()).isEqualTo("src/main/java/Foo.java");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void fail_if_missing_file_on_component() {
+ underTest.readComponent(UNKNOWN_COMPONENT_REF);
+ }
+
+ @Test
+ public void read_issues() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ ScannerReport.Issue issue = ScannerReport.Issue.newBuilder()
+ .setLine(50)
+ .build();
+ writer.writeComponentIssues(1, asList(issue));
+
+ assertThat(underTest.readComponentIssues(1)).hasSize(1);
+ assertThat(underTest.readComponentIssues(200)).isEmpty();
+ }
+
+ @Test
+ public void empty_list_if_no_issue_found() {
+ assertThat(underTest.readComponentIssues(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_measures() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ ScannerReport.Measure.Builder measure = ScannerReport.Measure.newBuilder()
+ .setStringValue("value_a");
+ writer.writeComponentMeasures(1, asList(measure.build()));
+
+ assertThat(underTest.readComponentMeasures(1)).hasSize(1);
+ }
+
+ @Test
+ public void empty_list_if_no_measure_found() {
+ assertThat(underTest.readComponentMeasures(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_changesets() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ ScannerReport.Changesets.Builder scm = ScannerReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(ScannerReport.Changesets.Changeset.newBuilder().setDate(123_456_789).setAuthor("jack.daniels").setRevision("123-456-789"));
+ writer.writeComponentChangesets(scm.build());
+
+ assertThat(underTest.readChangesets(1).getChangesetList()).hasSize(1);
+ assertThat(underTest.readChangesets(1).getChangeset(0).getDate()).isEqualTo(123_456_789L);
+ }
+
+ @Test
+ public void null_if_no_changeset_found() {
+ assertThat(underTest.readChangesets(UNKNOWN_COMPONENT_REF)).isNull();
+ }
+
+ @Test
+ public void read_duplications() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeMetadata(ScannerReport.Metadata.newBuilder()
+ .setRootComponentRef(1).build());
+ writer.writeComponent(ScannerReport.Component.newBuilder()
+ .setRef(1).build());
+
+ ScannerReport.Duplication duplication = ScannerReport.Duplication.newBuilder()
+ .setOriginPosition(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setEndLine(5)
+ .build())
+ .addDuplicate(ScannerReport.Duplicate.newBuilder()
+ .setOtherFileRef(2)
+ .setRange(ScannerReport.TextRange.newBuilder()
+ .setStartLine(6)
+ .setEndLine(10)
+ .build())
+ .build())
+ .build();
+ writer.writeComponentDuplications(1, asList(duplication));
+
+ ScannerReportReader sut = new ScannerReportReader(dir);
+ assertThat(sut.readComponentDuplications(1)).hasSize(1);
+ }
+
+ @Test
+ public void empty_list_if_no_duplication_found() {
+ assertThat(underTest.readComponentDuplications(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_duplication_blocks() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeMetadata(ScannerReport.Metadata.newBuilder()
+ .setRootComponentRef(1).build());
+ writer.writeComponent(ScannerReport.Component.newBuilder()
+ .setRef(1).build());
+
+ ScannerReport.CpdTextBlock duplicationBlock = ScannerReport.CpdTextBlock.newBuilder()
+ .setHash("abcdefghijklmnop")
+ .setStartLine(1)
+ .setEndLine(2)
+ .setStartTokenIndex(10)
+ .setEndTokenIndex(15)
+ .build();
+ writer.writeCpdTextBlocks(1, singletonList(duplicationBlock));
+
+ ScannerReportReader sut = new ScannerReportReader(dir);
+ assertThat(sut.readCpdTextBlocks(1)).hasSize(1);
+ }
+
+ @Test
+ public void empty_list_if_no_duplication_block_found() {
+ assertThat(underTest.readComponentDuplications(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_syntax_highlighting() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeMetadata(ScannerReport.Metadata.newBuilder()
+ .setRootComponentRef(1)
+ .build());
+ writer.writeComponent(ScannerReport.Component.newBuilder()
+ .setRef(1).build());
+
+ writer.writeComponentSyntaxHighlighting(1, asList(
+ ScannerReport.SyntaxHighlighting.newBuilder()
+ .setRange(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setEndLine(10)
+ .build())
+ .setType(Constants.HighlightingType.ANNOTATION)
+ .build()));
+
+ try (CloseableIterator<ScannerReport.SyntaxHighlighting> it = underTest.readComponentSyntaxHighlighting(1)) {
+ ScannerReport.SyntaxHighlighting syntaxHighlighting = it.next();
+ assertThat(syntaxHighlighting.getRange()).isNotNull();
+ assertThat(syntaxHighlighting.getRange().getStartLine()).isEqualTo(1);
+ assertThat(syntaxHighlighting.getRange().getEndLine()).isEqualTo(10);
+ assertThat(syntaxHighlighting.getType()).isEqualTo(Constants.HighlightingType.ANNOTATION);
+ }
+ }
+
+ @Test
+ public void return_empty_if_no_highlighting_found() {
+ assertThat(underTest.readComponentSyntaxHighlighting(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_symbols() {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeMetadata(ScannerReport.Metadata.newBuilder()
+ .setRootComponentRef(1)
+ .build());
+ writer.writeComponent(ScannerReport.Component.newBuilder()
+ .setRef(1).build());
+
+ writer.writeComponentSymbols(1, asList(ScannerReport.Symbol.newBuilder()
+ .setDeclaration(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setStartOffset(3)
+ .setEndLine(1)
+ .setEndOffset(5)
+ .build())
+ .addReference(ScannerReport.TextRange.newBuilder()
+ .setStartLine(10)
+ .setStartOffset(15)
+ .setEndLine(11)
+ .setEndOffset(2)
+ .build())
+ .build()));
+
+ underTest = new ScannerReportReader(dir);
+ assertThat(underTest.readComponentSymbols(1)).hasSize(1);
+ }
+
+ @Test
+ public void empty_list_if_no_symbol_found() {
+ assertThat(underTest.readComponentSymbols(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_coverage() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeMetadata(ScannerReport.Metadata.newBuilder()
+ .setRootComponentRef(1)
+ .build());
+ writer.writeComponent(ScannerReport.Component.newBuilder()
+ .setRef(1).build());
+
+ writer.writeComponentCoverage(1, asList(
+ ScannerReport.Coverage.newBuilder()
+ .setLine(1)
+ .setConditions(1)
+ .setUtHits(true)
+ .setItHits(false)
+ .setUtCoveredConditions(1)
+ .setItCoveredConditions(1)
+ .setOverallCoveredConditions(1)
+ .build(),
+ ScannerReport.Coverage.newBuilder()
+ .setLine(2)
+ .setConditions(5)
+ .setUtHits(false)
+ .setItHits(false)
+ .setUtCoveredConditions(4)
+ .setItCoveredConditions(5)
+ .setOverallCoveredConditions(5)
+ .build()));
+
+ underTest = new ScannerReportReader(dir);
+ try (CloseableIterator<ScannerReport.Coverage> it = new ScannerReportReader(dir).readComponentCoverage(1)) {
+ ScannerReport.Coverage coverage = it.next();
+ assertThat(coverage.getLine()).isEqualTo(1);
+ assertThat(coverage.getConditions()).isEqualTo(1);
+ assertThat(coverage.getUtHits()).isTrue();
+ assertThat(coverage.getItHits()).isFalse();
+ assertThat(coverage.getUtCoveredConditions()).isEqualTo(1);
+ assertThat(coverage.getItCoveredConditions()).isEqualTo(1);
+ assertThat(coverage.getOverallCoveredConditions()).isEqualTo(1);
+ }
+ }
+
+ @Test
+ public void return_empty_iterator_if_no_coverage_found() {
+ assertThat(underTest.readComponentCoverage(UNKNOWN_COMPONENT_REF)).isEmpty();
+ }
+
+ @Test
+ public void read_source_lines() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ File file = writer.getFileStructure().fileFor(FileStructure.Domain.SOURCE, 1);
+ FileUtils.writeLines(file, Lists.newArrayList("line1", "line2"));
+
+ File sourceFile = new ScannerReportReader(dir).readFileSource(1);
+ assertThat(sourceFile).isEqualTo(file);
+ }
+
+ @Test
+ public void read_tests() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeTests(1, asList(
+ ScannerReport.Test.newBuilder()
+ .setDurationInMs(60_000)
+ .setStacktrace("stacktrace")
+ .setMsg("message")
+ .setStatus(Constants.TestStatus.OK)
+ .build()));
+
+ try (InputStream inputStream = FileUtils.openInputStream(underTest.readTests(1))) {
+ ScannerReport.Test testResult = ScannerReport.Test.PARSER.parseDelimitedFrom(inputStream);
+ assertThat(testResult.getDurationInMs()).isEqualTo(60_000);
+ assertThat(testResult.getStacktrace()).isEqualTo("stacktrace");
+ assertThat(testResult.getMsg()).isEqualTo("message");
+ assertThat(testResult.getStatus()).isEqualTo(Constants.TestStatus.OK);
+ }
+ }
+
+ @Test
+ public void null_if_no_test_found() {
+ assertThat(underTest.readTests(UNKNOWN_COMPONENT_REF)).isNull();
+ }
+
+ @Test
+ public void read_coverage_details() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ writer.writeCoverageDetails(1, asList(
+ ScannerReport.CoverageDetail.newBuilder()
+ .setTestName("test-name")
+ .addCoveredFile(ScannerReport.CoverageDetail.CoveredFile.newBuilder()
+ .addAllCoveredLine(asList(1, 2, 3, 5, 7))
+ .setFileRef(2))
+ .build()));
+
+ try (InputStream inputStream = FileUtils.openInputStream(underTest.readCoverageDetails(1))) {
+ ScannerReport.CoverageDetail coverageDetail = ScannerReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream);
+ assertThat(coverageDetail.getTestName()).isEqualTo("test-name");
+ assertThat(coverageDetail.getCoveredFile(0).getFileRef()).isEqualTo(2);
+ assertThat(coverageDetail.getCoveredFile(0).getCoveredLineList()).containsExactly(1, 2, 3, 5, 7);
+ }
+ }
+
+ @Test
+ public void null_if_no_coverage_detail_found() {
+ assertThat(underTest.readCoverageDetails(UNKNOWN_COMPONENT_REF)).isNull();
+ }
+
+ @Test
+ public void read_file_source() throws Exception {
+ ScannerReportWriter writer = new ScannerReportWriter(dir);
+ try (FileOutputStream outputStream = new FileOutputStream(writer.getSourceFile(1))) {
+ IOUtils.write("line1\nline2", outputStream);
+ }
+
+ try (InputStream inputStream = FileUtils.openInputStream(underTest.readFileSource(1))) {
+ assertThat(IOUtils.readLines(inputStream)).containsOnly("line1", "line2");
+ }
+ }
+
+ @Test
+ public void return_null_when_no_file_source() throws Exception {
+ assertThat(underTest.readFileSource(UNKNOWN_COMPONENT_REF)).isNull();
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java
new file mode 100644
index 00000000000..9a08cd726ac
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java
@@ -0,0 +1,308 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.protocol.output;
+
+import com.google.common.collect.Iterators;
+import java.io.File;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.Protobuf;
+import org.sonar.scanner.protocol.Constants;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.protocol.output.FileStructure;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScannerReportWriterTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ File dir;
+ ScannerReportWriter underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ dir = temp.newFolder();
+ underTest = new ScannerReportWriter(dir);
+ }
+
+ @Test
+ public void create_dir_if_does_not_exist() {
+ FileUtils.deleteQuietly(dir);
+ underTest = new ScannerReportWriter(dir);
+
+ assertThat(dir).isDirectory().exists();
+ }
+
+ @Test
+ public void write_metadata() {
+ ScannerReport.Metadata.Builder metadata = ScannerReport.Metadata.newBuilder()
+ .setAnalysisDate(15000000L)
+ .setProjectKey("PROJECT_A")
+ .setRootComponentRef(1);
+ underTest.writeMetadata(metadata.build());
+
+ ScannerReport.Metadata read = Protobuf.read(underTest.getFileStructure().metadataFile(), ScannerReport.Metadata.PARSER);
+ assertThat(read.getAnalysisDate()).isEqualTo(15000000L);
+ assertThat(read.getProjectKey()).isEqualTo("PROJECT_A");
+ assertThat(read.getRootComponentRef()).isEqualTo(1);
+ }
+
+ @Test
+ public void write_component() {
+ // no data yet
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isFalse();
+
+ // write data
+ ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder()
+ .setRef(1)
+ .setLanguage("java")
+ .setPath("src/Foo.java")
+ .setType(Constants.ComponentType.FILE)
+ .setIsTest(false)
+ .addChildRef(5)
+ .addChildRef(42);
+ underTest.writeComponent(component.build());
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.COMPONENT, 1);
+ assertThat(file).exists().isFile();
+ ScannerReport.Component read = Protobuf.read(file, ScannerReport.Component.PARSER);
+ assertThat(read.getRef()).isEqualTo(1);
+ assertThat(read.getChildRefList()).containsOnly(5, 42);
+ assertThat(read.hasName()).isFalse();
+ assertThat(read.getIsTest()).isFalse();
+ }
+
+ @Test
+ public void write_issues() {
+ // no data yet
+ assertThat(underTest.hasComponentData(FileStructure.Domain.ISSUES, 1)).isFalse();
+
+ // write data
+ ScannerReport.Issue issue = ScannerReport.Issue.newBuilder()
+ .setLine(50)
+ .setMsg("the message")
+ .build();
+
+ underTest.writeComponentIssues(1, asList(issue));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.ISSUES, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.ISSUES, 1);
+ assertThat(file).exists().isFile();
+ try (CloseableIterator<ScannerReport.Issue> read = Protobuf.readStream(file, ScannerReport.Issue.PARSER)) {
+ assertThat(Iterators.size(read)).isEqualTo(1);
+ }
+ }
+
+ @Test
+ public void write_measures() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.MEASURES, 1)).isFalse();
+
+ ScannerReport.Measure measure = ScannerReport.Measure.newBuilder()
+ .setStringValue("text-value")
+ .setDoubleValue(2.5d)
+ .setValueType(Constants.MeasureValueType.DOUBLE)
+ .build();
+
+ underTest.writeComponentMeasures(1, asList(measure));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.MEASURES, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.MEASURES, 1);
+ assertThat(file).exists().isFile();
+ try (CloseableIterator<ScannerReport.Measure> read = Protobuf.readStream(file, ScannerReport.Measure.PARSER)) {
+ assertThat(Iterators.size(read)).isEqualTo(1);
+ }
+ }
+
+ @Test
+ public void write_scm() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.CHANGESETS, 1)).isFalse();
+
+ ScannerReport.Changesets scm = ScannerReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangesetIndexByLine(0)
+ .addChangeset(ScannerReport.Changesets.Changeset.newBuilder()
+ .setRevision("123-456-789")
+ .setAuthor("author")
+ .setDate(123_456_789L))
+ .build();
+
+ underTest.writeComponentChangesets(scm);
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.CHANGESETS, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.CHANGESETS, 1);
+ assertThat(file).exists().isFile();
+ ScannerReport.Changesets read = Protobuf.read(file, ScannerReport.Changesets.PARSER);
+ assertThat(read.getComponentRef()).isEqualTo(1);
+ assertThat(read.getChangesetCount()).isEqualTo(1);
+ assertThat(read.getChangesetList()).hasSize(1);
+ assertThat(read.getChangeset(0).getDate()).isEqualTo(123_456_789L);
+ }
+
+ @Test
+ public void write_duplications() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.DUPLICATIONS, 1)).isFalse();
+
+ ScannerReport.Duplication duplication = ScannerReport.Duplication.newBuilder()
+ .setOriginPosition(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setEndLine(5)
+ .build())
+ .addDuplicate(ScannerReport.Duplicate.newBuilder()
+ .setOtherFileRef(2)
+ .setRange(ScannerReport.TextRange.newBuilder()
+ .setStartLine(6)
+ .setEndLine(10)
+ .build())
+ .build())
+ .build();
+ underTest.writeComponentDuplications(1, asList(duplication));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.DUPLICATIONS, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.DUPLICATIONS, 1);
+ assertThat(file).exists().isFile();
+ try (CloseableIterator<ScannerReport.Duplication> duplications = Protobuf.readStream(file, ScannerReport.Duplication.PARSER)) {
+ ScannerReport.Duplication dup = duplications.next();
+ assertThat(dup.getOriginPosition()).isNotNull();
+ assertThat(dup.getDuplicateList()).hasSize(1);
+ }
+ }
+
+ @Test
+ public void write_duplication_blocks() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.CPD_TEXT_BLOCKS, 1)).isFalse();
+
+ ScannerReport.CpdTextBlock duplicationBlock = ScannerReport.CpdTextBlock.newBuilder()
+ .setHash("abcdefghijklmnop")
+ .setStartLine(1)
+ .setEndLine(2)
+ .setStartTokenIndex(10)
+ .setEndTokenIndex(15)
+ .build();
+ underTest.writeCpdTextBlocks(1, asList(duplicationBlock));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.CPD_TEXT_BLOCKS, 1)).isTrue();
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, 1);
+ assertThat(file).exists().isFile();
+ try (CloseableIterator<ScannerReport.CpdTextBlock> duplicationBlocks = Protobuf.readStream(file, ScannerReport.CpdTextBlock.parser())) {
+ ScannerReport.CpdTextBlock duplicationBlockResult = duplicationBlocks.next();
+ assertThat(duplicationBlockResult.getHash()).isEqualTo("abcdefghijklmnop");
+ assertThat(duplicationBlockResult.getStartLine()).isEqualTo(1);
+ assertThat(duplicationBlockResult.getEndLine()).isEqualTo(2);
+ assertThat(duplicationBlockResult.getStartTokenIndex()).isEqualTo(10);
+ assertThat(duplicationBlockResult.getEndTokenIndex()).isEqualTo(15);
+ }
+ }
+
+ @Test
+ public void write_symbols() {
+ // no data yet
+ assertThat(underTest.hasComponentData(FileStructure.Domain.SYMBOLS, 1)).isFalse();
+
+ // write data
+ ScannerReport.Symbol symbol = ScannerReport.Symbol.newBuilder()
+ .setDeclaration(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setStartOffset(3)
+ .setEndLine(1)
+ .setEndOffset(5)
+ .build())
+ .addReference(ScannerReport.TextRange.newBuilder()
+ .setStartLine(10)
+ .setStartOffset(15)
+ .setEndLine(11)
+ .setEndOffset(2)
+ .build())
+ .build();
+
+ underTest.writeComponentSymbols(1, asList(symbol));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.SYMBOLS, 1)).isTrue();
+
+ File file = underTest.getFileStructure().fileFor(FileStructure.Domain.SYMBOLS, 1);
+ assertThat(file).exists().isFile();
+ try (CloseableIterator<ScannerReport.Symbol> read = Protobuf.readStream(file, ScannerReport.Symbol.PARSER)) {
+ assertThat(read).hasSize(1);
+ }
+ }
+
+ @Test
+ public void write_syntax_highlighting() {
+ // no data yet
+ assertThat(underTest.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, 1)).isFalse();
+
+ underTest.writeComponentSyntaxHighlighting(1, asList(
+ ScannerReport.SyntaxHighlighting.newBuilder()
+ .setRange(ScannerReport.TextRange.newBuilder()
+ .setStartLine(1)
+ .setEndLine(1)
+ .build())
+ .setType(Constants.HighlightingType.ANNOTATION)
+ .build()));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, 1)).isTrue();
+ }
+
+ @Test
+ public void write_coverage() {
+ // no data yet
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGES, 1)).isFalse();
+
+ underTest.writeComponentCoverage(1, asList(
+ ScannerReport.Coverage.newBuilder()
+ .setLine(1)
+ .setConditions(1)
+ .setUtHits(true)
+ .setItHits(false)
+ .setUtCoveredConditions(1)
+ .setItCoveredConditions(1)
+ .setOverallCoveredConditions(1)
+ .build()));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGES, 1)).isTrue();
+ }
+
+ @Test
+ public void write_tests() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.TESTS, 1)).isFalse();
+
+ underTest.writeTests(1, asList(
+ ScannerReport.Test.getDefaultInstance()));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.TESTS, 1)).isTrue();
+
+ }
+
+ @Test
+ public void write_coverage_details() {
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGE_DETAILS, 1)).isFalse();
+
+ underTest.writeCoverageDetails(1, asList(
+ ScannerReport.CoverageDetail.getDefaultInstance()));
+
+ assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGE_DETAILS, 1)).isTrue();
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json
new file mode 100644
index 00000000000..de38ae0cb18
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json
@@ -0,0 +1,21 @@
+{
+ "timestamp": 10,
+ "metrics": [
+ {
+ "id": 1,
+ "key": "ncloc",
+ "valueType": "INT",
+ "description": "Description",
+ "direction": -1,
+ "name": "NCLOC",
+ "qualitative": true,
+ "userManaged": false,
+ "worstValue": 2.0,
+ "bestValue": 1.0,
+ "optimizedBestValue": true
+ }
+ ],
+ "globalSettings": {
+ "prop": "value"
+ }
+}
diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json
new file mode 100644
index 00000000000..ba7489143a6
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json
@@ -0,0 +1,23 @@
+{
+ "timestamp": 10,
+ "settingsByModule": {
+ "foo": {
+ "prop1": "value1",
+ "prop2": "value2",
+ "prop": "value"
+ }
+ },
+ "fileDataByModuleAndPath": {
+ "foo": {
+ "src/main/java/Foo.java": {
+ "hash": "xyz",
+ "needBlame": true
+ },
+ "src/main/java/Foo2.java": {
+ "hash": "xyz",
+ "needBlame": false
+ }
+ }
+ },
+ "lastAnalysisDate": "2014-05-18T15:50:45+0100"
+}
diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json
new file mode 100644
index 00000000000..055fe8b8d63
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json
@@ -0,0 +1 @@
+{"total":3225,"p":30,"ps":500,"rules":[]} \ No newline at end of file
diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json
new file mode 100644
index 00000000000..89350a7e331
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json
@@ -0,0 +1 @@
+{"total":290,"p":1,"ps":2,"rules":[{"key":"squid:S1194","internalKey":"S1194","repo":"squid","name":"\"java.lang.Error\" should not be extended","severity":"MAJOR","lang":"java"},{"key":"squid:ObjectFinalizeOverridenCallsSuperFinalizeCheck","internalKey":"ObjectFinalizeOverridenCallsSuperFinalizeCheck","repo":"squid","name":"super.finalize() should be called at the end of Object.finalize() implementations","severity":"BLOCKER","lang":"java"}]} \ No newline at end of file
diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json
new file mode 100644
index 00000000000..581bbc5ea23
--- /dev/null
+++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json
@@ -0,0 +1,43 @@
+{
+ "analysisDate": "2012-12-12T00:00:00+0100",
+ "root": {
+ "batchId": 1,
+ "id": 11,
+ "snapshotId": 111,
+ "name": "Root project",
+ "type": "PRJ",
+ "children": [
+ {
+ "batchId": 2,
+ "id": 22,
+ "snapshotId": 222,
+ "path": "module1",
+ "name": "Module",
+ "type": "MOD",
+ "children": [
+ {
+ "batchId": 3,
+ "id": 33,
+ "snapshotId": 333,
+ "path": "src",
+ "name": "src",
+ "type": "DIR",
+ "children": [
+ {
+ "batchId": 4,
+ "id": 44,
+ "snapshotId": 444,
+ "path": "Foo.java",
+ "name": "Foo.java",
+ "type": "FIL",
+ "languageKey": "java",
+ "isTest": true,
+ "children": []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+} \ No newline at end of file