/*
* SonarQube Java
* Copyright (C) 2012-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.java;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.sonar.sslr.api.RecognitionException;
import org.sonar.api.SonarProduct;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.rule.CheckFactory;
import org.sonar.api.batch.rule.Checks;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonar.api.measures.FileLinesContext;
import org.sonar.api.measures.FileLinesContextFactory;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Version;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonar.squidbridge.api.CodeVisitor;
import org.sonarsource.api.sonarlint.SonarLintSide;
import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@BatchSide
@SonarLintSide
public class SonarComponents {
private static final Version SQ_6_0 = Version.create(6, 0);
private static final Version SQ_6_2 = Version.create(6, 2);
private final FileLinesContextFactory fileLinesContextFactory;
private final JavaTestClasspath javaTestClasspath;
private final CheckFactory checkFactory;
private final FileSystem fs;
private final JavaClasspath javaClasspath;
private final List<Checks<JavaCheck>> checks;
private final List<Checks<JavaCheck>> testChecks;
private final List<Checks<JavaCheck>> allChecks;
private SensorContext context;
public SonarComponents(FileLinesContextFactory fileLinesContextFactory, FileSystem fs,
JavaClasspath javaClasspath, JavaTestClasspath javaTestClasspath,
CheckFactory checkFactory) {
this(fileLinesContextFactory, fs, javaClasspath, javaTestClasspath, checkFactory, null);
}
public SonarComponents(FileLinesContextFactory fileLinesContextFactory, FileSystem fs,
JavaClasspath javaClasspath, JavaTestClasspath javaTestClasspath, CheckFactory checkFactory,
@Nullable CheckRegistrar[] checkRegistrars) {
this.fileLinesContextFactory = fileLinesContextFactory;
this.fs = fs;
this.javaClasspath = javaClasspath;
this.javaTestClasspath = javaTestClasspath;
this.checkFactory = checkFactory;
this.checks = new ArrayList<>();
this.testChecks = new ArrayList<>();
this.allChecks = new ArrayList<>();
if (checkRegistrars != null) {
CheckRegistrar.RegistrarContext registrarContext = new CheckRegistrar.RegistrarContext();
for (CheckRegistrar checkClassesRegister : checkRegistrars) {
checkClassesRegister.register(registrarContext);
Iterable<Class<? extends JavaCheck>> checkClasses = registrarContext.checkClasses();
Iterable<Class<? extends JavaCheck>> testCheckClasses = registrarContext.testCheckClasses();
registerCheckClasses(registrarContext.repositoryKey(), Lists.newArrayList(checkClasses != null ? checkClasses : new ArrayList<>()));
registerTestCheckClasses(registrarContext.repositoryKey(), Lists.newArrayList(testCheckClasses != null ? testCheckClasses : new ArrayList<>()));
}
}
}
public void setSensorContext(SensorContext context) {
this.context = context;
}
public InputFile inputFromIOFile(File file) {
return fs.inputFile(fs.predicates().is(file));
}
public int fileLength(File file) {
return inputFromIOFile(file).lines();
}
private InputPath inputPathFromIOFile(File file) {
if (file.isDirectory()) {
return fs.inputDir(file);
} else {
return inputFromIOFile(file);
}
}
public FileLinesContext fileLinesContextFor(File file) {
return fileLinesContextFactory.createFor(inputFromIOFile(file));
}
public NewSymbolTable symbolizableFor(File file) {
return context.newSymbolTable().onFile(inputFromIOFile(file));
}
public NewHighlighting highlightableFor(File file) {
Preconditions.checkNotNull(context);
return context.newHighlighting().onFile(inputFromIOFile(file));
}
public List<File> getJavaClasspath() {
if (javaClasspath == null) {
return Lists.newArrayList();
}
return javaClasspath.getElements();
}
public List<File> getJavaTestClasspath() {
return javaTestClasspath.getElements();
}
public void registerCheckClasses(String repositoryKey, Iterable<Class<? extends JavaCheck>> checkClasses) {
Checks<JavaCheck> createdChecks = checkFactory.<JavaCheck>create(repositoryKey).addAnnotatedChecks(checkClasses);
checks.add(createdChecks);
allChecks.add(createdChecks);
}
public CodeVisitor[] checkClasses() {
return checks.stream().flatMap(ce -> ce.all().stream()).toArray(CodeVisitor[]::new);
}
public Iterable<Checks<JavaCheck>> checks() {
return allChecks;
}
public void registerTestCheckClasses(String repositoryKey, Iterable<Class<? extends JavaCheck>> checkClasses) {
Checks<JavaCheck> createdChecks = checkFactory.<JavaCheck>create(repositoryKey).addAnnotatedChecks(checkClasses);
testChecks.add(createdChecks);
allChecks.add(createdChecks);
}
public Collection<JavaCheck> testCheckClasses() {
List<JavaCheck> visitors = Lists.newArrayList();
for (Checks<JavaCheck> checksElement : testChecks) {
Collection<JavaCheck> checksCollection = checksElement.all();
if (!checksCollection.isEmpty()) {
visitors.addAll(checksCollection);
}
}
return visitors;
}
public FileSystem getFileSystem() {
return fs;
}
public RuleKey getRuleKey(JavaCheck check) {
for (Checks<JavaCheck> sonarChecks : checks()) {
RuleKey ruleKey = sonarChecks.ruleKey(check);
if (ruleKey != null) {
return ruleKey;
}
}
return null;
}
public void addIssue(File file, JavaCheck check, int line, String message, @Nullable Integer cost) {
reportIssue(new AnalyzerMessage(check, file, line, message, cost != null ? cost.intValue() : 0));
}
public void reportIssue(AnalyzerMessage analyzerMessage) {
JavaCheck check = analyzerMessage.getCheck();
Preconditions.checkNotNull(check);
Preconditions.checkNotNull(analyzerMessage.getMessage());
RuleKey key = getRuleKey(check);
if (key == null) {
return;
}
File file = analyzerMessage.getFile();
InputPath inputPath = inputPathFromIOFile(file);
if (inputPath == null) {
return;
}
Double cost = analyzerMessage.getCost();
reportIssue(analyzerMessage, key, inputPath, cost);
}
@VisibleForTesting
void reportIssue(AnalyzerMessage analyzerMessage, RuleKey key, InputPath inputPath, Double cost) {
Preconditions.checkNotNull(context);
JavaIssue issue = JavaIssue.create(context, key, cost);
AnalyzerMessage.TextSpan textSpan = analyzerMessage.primaryLocation();
if (textSpan == null) {
// either an issue at file or folder level
issue.setPrimaryLocationOnFile(inputPath, analyzerMessage.getMessage());
} else {
if (!textSpan.onLine()) {
Preconditions.checkState(!textSpan.isEmpty(), "Issue location should not be empty");
}
issue.setPrimaryLocation((InputFile) inputPath, analyzerMessage.getMessage(), textSpan.startLine, textSpan.startCharacter, textSpan.endLine, textSpan.endCharacter);
}
issue.addFlow(inputFromIOFile(analyzerMessage.getFile()), analyzerMessage.flows).save();
}
public boolean reportAnalysisError(RecognitionException re, File file) {
if (context.getSonarQubeVersion().isGreaterThanOrEqual(SQ_6_0)) {
context.newAnalysisError()
.onFile(inputFromIOFile(file))
.message(re.getMessage())
.save();
return context.runtime().getProduct() == SonarProduct.SONARLINT;
}
return false;
}
public boolean isSQGreaterThan62() {
return context.getSonarQubeVersion().isGreaterThanOrEqual(SQ_6_2);
}
}