/*
* 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.ast.visitors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.FileLinesContext;
import org.sonar.java.SonarComponents;
import org.sonar.java.cfg.CFG;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.SyntaxToken;
import org.sonar.plugins.java.api.tree.SyntaxTrivia;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeTree;
import org.sonar.plugins.java.api.tree.VariableTree;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.sonar.plugins.java.api.tree.Tree.Kind.BLOCK;
import static org.sonar.plugins.java.api.tree.Tree.Kind.BOOLEAN_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.CATCH;
import static org.sonar.plugins.java.api.tree.Tree.Kind.CHAR_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.CONSTRUCTOR;
import static org.sonar.plugins.java.api.tree.Tree.Kind.DOUBLE_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.DO_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.FLOAT_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.FOR_EACH_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.FOR_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.INITIALIZER;
import static org.sonar.plugins.java.api.tree.Tree.Kind.INT_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.LAMBDA_EXPRESSION;
import static org.sonar.plugins.java.api.tree.Tree.Kind.LONG_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.METHOD;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NEW_CLASS;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NULL_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.STATIC_INITIALIZER;
import static org.sonar.plugins.java.api.tree.Tree.Kind.STRING_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.TOKEN;
import static org.sonar.plugins.java.api.tree.Tree.Kind.TRY_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.VARIABLE;
import static org.sonar.plugins.java.api.tree.Tree.Kind.WHILE_STATEMENT;
/**
* Saves information about lines directly into Sonar by using {@link FileLinesContext}.
*/
public class FileLinesVisitor extends SubscriptionVisitor {
private final SonarComponents sonarComponents;
private final Set<Integer> linesOfCode = new HashSet<>();
private final Set<Integer> linesOfComments = new HashSet<>();
private final Set<Integer> executableLines = new HashSet<>();
public FileLinesVisitor(SonarComponents sonarComponents) {
this.sonarComponents = sonarComponents;
}
@Override
public List<Tree.Kind> nodesToVisit() {
if(sonarComponents.isSQGreaterThan62()) {
return ImmutableList.of(TOKEN,
METHOD, CONSTRUCTOR,
INITIALIZER, STATIC_INITIALIZER,
VARIABLE,
FOR_EACH_STATEMENT, FOR_STATEMENT, WHILE_STATEMENT, DO_STATEMENT,
LAMBDA_EXPRESSION);
}
return ImmutableList.of(TOKEN);
}
@Override
public void scanFile(JavaFileScannerContext context) {
super.scanFile(context);
File currentFile = context.getFile();
FileLinesContext fileLinesContext = sonarComponents.fileLinesContextFor(currentFile);
int fileLength = sonarComponents.fileLength(currentFile);
for (int line = 1; line <= fileLength; line++) {
fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, linesOfCode.contains(line) ? 1 : 0);
fileLinesContext.setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, line, linesOfComments.contains(line) ? 1 : 0);
if(sonarComponents.isSQGreaterThan62()) {
fileLinesContext.setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, executableLines.contains(line) ? 1 : 0);
}
}
fileLinesContext.save();
linesOfCode.clear();
linesOfComments.clear();
executableLines.clear();
}
@Override
public void visitNode(Tree tree) {
List<? extends Tree> trees = Collections.emptyList();
switch (tree.kind()) {
case INITIALIZER:
case STATIC_INITIALIZER:
trees = ((BlockTree) tree).body();
break;
case VARIABLE:
trees = visitVariable((VariableTree) tree);
break;
case LAMBDA_EXPRESSION:
trees = visitLambda((LambdaExpressionTree) tree);
break;
case METHOD:
case CONSTRUCTOR:
trees = visitMethod((MethodTree) tree);
break;
case FOR_STATEMENT:
case FOR_EACH_STATEMENT:
case WHILE_STATEMENT:
case DO_STATEMENT:
executableLines.add(tree.lastToken().line());
break;
default:
// Do nothing particular
}
computeExecutableLines(trees);
}
private List<? extends Tree> visitVariable(VariableTree variableTree) {
ExpressionTree initializer = variableTree.initializer();
if(initializer != null && !isConstant(variableTree)) {
return Lists.newArrayList(initializer);
}
if(variableTree.parent().is(CATCH)) {
// catch variable are counted as executable lines
new ExecutableLinesTokenVisitor().scanTree(variableTree);
}
return Collections.emptyList();
}
private static List<? extends Tree> visitLambda(LambdaExpressionTree lambda) {
Tree body = lambda.body();
if(body.is(BLOCK)) {
return ((BlockTree) body).body();
}
return Lists.newArrayList(body);
}
private List<? extends Tree> visitMethod(MethodTree tree) {
BlockTree methodBody = tree.block();
if(methodBody != null) {
// get the last
TypeTree returnType = tree.returnType();
if(returnType == null || "void".equals(returnType.firstToken().text())) {
executableLines.add(methodBody.closeBraceToken().line());
}
return methodBody.body();
}
return Collections.emptyList();
}
private void computeExecutableLines(List<? extends Tree> trees) {
if(trees.isEmpty()) {
return;
}
// rely on cfg to get every instructions and get most of the token.
CFG cfg = CFG.buildCFG(trees);
cfg.blocks()
.stream()
.flatMap(b->b.elements().stream())
.forEach(
t -> {
if (t.is(NEW_CLASS)) {
NewClassTree newClassTree = (NewClassTree) t;
new ExecutableLinesTokenVisitor().scanTree(newClassTree.identifier());
executableLines.add(newClassTree.newKeyword().line());
} else if (t.is(TRY_STATEMENT)) {
// add last token of try statements
executableLines.add(t.lastToken().line());
} else {
executableLines.add(t.firstToken().line());
}
}
);
}
@Override
public void visitToken(SyntaxToken syntaxToken) {
linesOfCode.add(syntaxToken.line());
for (SyntaxTrivia trivia : syntaxToken.trivias()) {
int baseLine = trivia.startLine();
String[] lines = trivia.comment().split("(\r)?\n|\r", -1);
for (int i = 0; i < lines.length; i++) {
linesOfComments.add(baseLine + i);
}
}
}
private static boolean isConstant(VariableTree variableTree) {
return ModifiersUtils.hasModifier(variableTree.modifiers(), Modifier.STATIC)
&& ModifiersUtils.hasModifier(variableTree.modifiers(), Modifier.FINAL)
&& variableTree.initializer().is(BOOLEAN_LITERAL,
STRING_LITERAL, LONG_LITERAL,
CHAR_LITERAL, INT_LITERAL, FLOAT_LITERAL, DOUBLE_LITERAL, NULL_LITERAL);
}
/**
* Add lines of token to executable lines only, skips comments and blank lines.
*/
private class ExecutableLinesTokenVisitor extends SubscriptionVisitor {
@Override
public List<Tree.Kind> nodesToVisit() {
return ImmutableList.of(TOKEN);
}
@Override
public void visitToken(SyntaxToken syntaxToken) {
executableLines.add(syntaxToken.line());
}
}
}