blob: d77c98c68885233806d9873b9b92c7330da58d78 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.metrics.hints;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.util.Collection;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.java.hints.BooleanOption;
import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
import org.netbeans.spi.java.hints.Hint;
import org.netbeans.spi.java.hints.HintContext;
import org.netbeans.spi.java.hints.IntegerOption;
import org.netbeans.spi.java.hints.TriggerPattern;
import org.netbeans.spi.java.hints.TriggerPatterns;
import org.netbeans.spi.java.hints.TriggerTreeKind;
import org.netbeans.spi.java.hints.UseOptions;
import org.openide.util.NbBundle;
import static org.netbeans.modules.java.metrics.hints.Bundle.*;
/**
* Inspections based on metrics computed for individual methods.
*
* @author sdedic
*/
@NbBundle.Messages({
"# {0} - method name",
"# {1} - cyclomatic complexity of the method",
"TEXT_MethodTooComplex=The method ''{0}'' is too complex; cyclomatic complexity: {1}",
"# {0} - method name",
"# {1} - maximum depth of statements in method",
"TEXT_MethodTooDeepNesting=Method ''{0}'' contains too deep statement structure: {1}",
"# {0} - method name",
"# {1} - number of lines in method",
"TEXT_MethodTooLongLines=Method ''{0}'' is too long: {1} lines",
"# {0} - method name",
"# {1} - number of lines in method",
"TEXT_MethodTooLongStatements=Method ''{0}'' is too long: {1} statements",
"# {0} - method name",
"# {1} - number of exceptions declared by the method",
"TEXT_MethodTooManyExceptions=Method ''{0}'' declares too many exceptions: {1}",
"# {0} - method name",
"# {1} - number of parameters declared by the method",
"TEXT_MethodTooManyParameters=Method ''{0}'' takes too many parameters: {1}",
"# {0} - method name",
"# {1} - number of return points",
"TEXT_MethodMultipleReturns=Method ''{0}'' has multiple return points: {1}",
"# {0} - method name",
"# {1} - number of negations",
"TEXT_MethodMultipleNegations=Method ''{0}'' contains too many negations: {1}",
"# {0} - method name",
"# {1} - number of loops",
"TEXT_MethodMultipleLoops=Method ''{0}'' contains {1} loops",
"# {0} - method name",
"# {1} - number of dependencies",
"TEXT_MethodTooCoupled=Method ''{0}'' is too coupled. References {1} types",
"# {0} - cyclomatic complexity of the method",
"TEXT_ConstructorTooComplex=Constructor is too complex; cyclomatic complexity: {0}",
"# {0} - maximum depth of statements in method",
"TEXT_ConstructorTooDeepNesting=Constructor contains too deep statement structure: {0}",
"# {0} - number of lines in method",
"TEXT_ConstructorTooLongLines=Constructor is too long: {0} lines",
"# {0} - number of lines in method",
"TEXT_ConstructorTooLongStatements=Constructor is too long: {0} statements",
"# {0} - number of exceptions declared by the method",
"TEXT_ConstructorTooManyExceptions=Constructor declares too many exceptions: {0}",
"# {0} - number of parameters declared by the method",
"TEXT_ConstructorTooManyParameters=Constructor takes too many parameters: {0}",
"# {0} - number of return points",
"TEXT_ConstructorMultipleReturns=Constructor has multiple return points: {0}",
"# {0} - number of negations",
"TEXT_ConstructorMultipleNegations=Constructor contains too many negations: {0}",
"# {0} - number of loops",
"TEXT_ConstructorMultipleLoops=Constructor contains {0} loops",
"# {0 - number of dependencies",
"TEXT_ConstructorTooCoupled=Constructor is too coupled. References {0} types"
})
public class MethodMetrics {
static final int DEFAULT_COMPLEXITY_LIMIT = 10;
static final int DEFAULT_NESTING_LIMIT = 6;
static final int DEFAULT_LINES_LIMIT = 60;
static final int DEFAULT_EXCEPTIONS_LIMIT = 3;
static final int DEFAULT_STATEMENTS_LIMIT = 30;
static final int DEFAULT_METHOD_PARAMETERS_LIMIT = 8;
static final int DEFAULT_CTOR_PARAMETERS_LIMIT = 12;
static final boolean DEFAULT_IGNORE_RETURN_GUARDS = true;
static final boolean DEFAULT_IGNORE_EQUALS = true;
static final int DEFAULT_RETURN_LIMIT = 2;
static final int DEFAULT_NEGATIONS_LIMIT = 3;
static final boolean DEFAULT_NEGATIONS_IGNORE_ASSERT = true;
static final boolean DEFAULT_NEGATIONS_IGNORE_EQUALS = true;
static final boolean DEFAULT_COUPLING_IGNORE_JAVA = true;
static final int DEFAULT_LOOPS_LIMIT = 3;
static final int DEFAULT_COUPLING_LIMIT = 15;
@IntegerOption(
displayName = "#OPTNAME_MethodComplexityLimit",
tooltip = "#OPTDESC_MethodComplexityLimit",
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_COMPLEXITY_LIMIT
)
public static final String OPTION_COMPLEXITY_TRESHOLD = "metrics.method.complexity.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodDepthLimit",
tooltip = "#OPTDESC_MethodDepthLimit",
maxValue = 100,
step = 1,
defaultValue = DEFAULT_NESTING_LIMIT
)
public static final String OPTION_NESTING_LIMIT = "metrics.method.depth.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodLinesLimit",
tooltip = "#OPTDESC_MethodLinesLimit",
maxValue = 60,
step = 50,
defaultValue = DEFAULT_LINES_LIMIT
)
public static final String OPTION_LINES_LIMIT = "metrics.method.lines.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodStatementsLimit",
tooltip = "#OPTDESC_MethodStatementsLimit",
maxValue = 30,
step = 5,
defaultValue = DEFAULT_LINES_LIMIT
)
public static final String OPTION_STATEMENTS_LIMIT = "metrics.method.statements.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodExceptionsLimit",
tooltip = "#OPTDESC_MethodExceptionsLimit",
maxValue = 50,
minValue = 1,
step = 1,
defaultValue = DEFAULT_EXCEPTIONS_LIMIT
)
public static final String OPTION_EXCEPTIONS_LIMIT = "metrics.method.exceptions.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodParametersLimit",
tooltip = "#OPTDESC_MethodParametersLimit",
maxValue = 100,
minValue = 2,
step = 1,
defaultValue = DEFAULT_METHOD_PARAMETERS_LIMIT
)
public static final String OPTION_METHOD_PARAMETERS_LIMIT = "metrics.method.parameters.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_CtorParametersLimit",
tooltip = "#OPTDESC_CtorParametersLimit",
maxValue = 100,
minValue = 2,
step = 1,
defaultValue = DEFAULT_CTOR_PARAMETERS_LIMIT
)
public static final String OPTION_CTOR_PARAMETERS_LIMIT = "metrics.constructor.parameters.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodReturnLimit",
tooltip = "#OPTDESC_MethodReturnLimit",
maxValue = 100,
minValue = 1,
step = 1,
defaultValue = DEFAULT_RETURN_LIMIT
)
public static final String OPTION_RETURN_LIMIT = "metrics.method.return.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_MethodIgnoreReturnGuards",
tooltip = "#OPTDESC_MethodIgnoreReturnGuards",
defaultValue = DEFAULT_IGNORE_RETURN_GUARDS
)
public static final String OPTION_RETURN_IGNORE_GUARDS = "metrics.method.returns.ignoreguards"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_MethodIgnoreReturnEquals",
tooltip = "#OPTDESC_MethodIgnoreReturnEquals",
defaultValue = DEFAULT_IGNORE_EQUALS
)
public static final String OPTION_RETURN_IGNORE_EQUALS = "metrics.method.returns.ignoreequals"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodNegationsLimit",
tooltip = "#OPTDESC_MethodNegationsLimit",
maxValue = 100,
step = 1,
defaultValue = DEFAULT_NEGATIONS_LIMIT
)
public static final String OPTION_NEGATIONS_LIMIT = "metrics.method.negations.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_MethodNegationsIgnoreEquals",
tooltip = "#OPTDESC_MethodNegationsIgnoreEquals",
defaultValue = DEFAULT_NEGATIONS_IGNORE_EQUALS
)
public static final String OPTION_NEGATIONS_IGNORE_EQUALS = "metrics.method.negations.ignoreequals"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_MethodNegationsIgnoreAsserts",
tooltip = "#OPTDESC_MethodNegationsIgnoreAsserts",
defaultValue = DEFAULT_NEGATIONS_IGNORE_ASSERT
)
public static final String OPTION_NEGATIONS_IGNORE_ASSERT = "metrics.method.negations.ignoreassert"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodLoopsLimit",
tooltip = "#OPTDESC_MethodLoopsLimit",
maxValue = 100,
step = 1,
defaultValue = DEFAULT_LOOPS_LIMIT
)
public static final String OPTION_LOOPS_LIMIT = "metrics.method.loops.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_MethodCouplingLimit",
tooltip = "#OPTDESC_MethodCouplingLimit",
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_COUPLING_LIMIT
)
public static final String OPTION_COUPLING_LIMIT = "metrics.method.coupling.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_MethodCouplingIgnoreJava",
tooltip = "#OPTDESC_MethodCouplingIgnoreJava",
defaultValue = DEFAULT_COUPLING_IGNORE_JAVA
)
public static final String OPTION_COUPLING_IGNORE_JAVA = "metrics.method.coupling.nojava"; // NOI18N
public static final String OPTION_COUPLING_IGNORE_LIBS = "metrics.method.coupling.nolibraries"; // NOI18N
private static boolean methodOrConstructor(HintContext ctx) {
Element el = ctx.getInfo().getTrees().getElement(ctx.getPath());
return el.getKind() == ElementKind.CONSTRUCTOR;
}
@Hint(category = "metrics",
displayName = "#DN_MethodTooComplex",
description = "#DESC_MethodTooComplex",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions(value = { OPTION_COMPLEXITY_TRESHOLD })
public static ErrorDescription methodTooComplex(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
CyclomaticComplexityVisitor v = new CyclomaticComplexityVisitor();
v.scan(ctx.getPath(), v);
int complexity = v.getComplexity();
int treshold = ctx.getPreferences().getInt(OPTION_COMPLEXITY_TRESHOLD, DEFAULT_COMPLEXITY_LIMIT);
if (complexity <= treshold) {
return null;
}
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooComplex(complexity) :
TEXT_MethodTooComplex(method.getName().toString(), complexity)
);
}
@Hint(
category = "metrics",
displayName = "#DN_MethodTooDeepNesting",
description = "#DESC_MethodTooDeepNesting",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions(value = { OPTION_NESTING_LIMIT })
public static ErrorDescription tooDeepNesting(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
DepthVisitor v = new DepthVisitor();
v.scan(ctx.getPath(), null);
int depth = v.getDepth();
int treshold = ctx.getPreferences().getInt(OPTION_NESTING_LIMIT, DEFAULT_NESTING_LIMIT);
if (depth <= treshold) {
return null;
}
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooDeepNesting(depth) :
TEXT_MethodTooDeepNesting(method.getName().toString(), depth)
);
}
@Hint(
category = "metrics",
displayName = "#DN_MethodTooLong",
description = "#DESC_MethodTooLong",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions({ OPTION_LINES_LIMIT, OPTION_STATEMENTS_LIMIT })
public static ErrorDescription tooLong(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
NCLOCVisitor v = new NCLOCVisitor(ctx.getInfo().getSnapshot().getText(), ctx.getInfo().getTrees().getSourcePositions());
v.scan(ctx.getPath(), null);
int count = v.getLineCount();
int treshold = ctx.getPreferences().getInt(OPTION_LINES_LIMIT, DEFAULT_LINES_LIMIT);
if (count > treshold) {
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooLongLines(count) :
TEXT_MethodTooLongLines(method.getName().toString(), count)
);
}
count = v.getStatementCount();
treshold = ctx.getPreferences().getInt(OPTION_STATEMENTS_LIMIT, DEFAULT_STATEMENTS_LIMIT);
if (count > treshold) {
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooLongStatements(count) :
TEXT_MethodTooLongStatements(method.getName().toString(), count)
);
} else {
return null;
}
}
@Hint(
category = "metrics",
displayName = "#DN_MethodTooManyExceptions",
description = "#DESC_MethodTooManyExceptions",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@UseOptions(value = { OPTION_EXCEPTIONS_LIMIT })
@TriggerPatterns({
@TriggerPattern("$modifiers$ <$typeParams$> $returnType $name($args$) throws $thrown1, $thrown2$ { $body$; }"),
@TriggerPattern("$modifiers$ <$typeParams$> $name($args$) throws $thrown1, $thrown2$ { $body$; }"),
})
public static ErrorDescription tooManyExceptions(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
Collection<? extends TreePath> exc2 = ctx.getMultiVariables().get("$thrown2$"); // NOI18N
int limit = ctx.getPreferences().getInt(OPTION_EXCEPTIONS_LIMIT, DEFAULT_EXCEPTIONS_LIMIT);
int count = exc2 == null ? 1 :exc2.size() + 1;
if (count <= limit) {
return null;
}
Element el = ctx.getInfo().getTrees().getElement(ctx.getPath());
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooManyExceptions(count) :
TEXT_MethodTooManyExceptions(method.getName().toString(), count)
);
}
@Hint(
category = "metrics",
displayName = "#DN_MethodTooManyParameters",
description = "#DESC_MethodTooManyParameters",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@UseOptions(value = { OPTION_METHOD_PARAMETERS_LIMIT })
@TriggerPattern("$modifiers$ <$typeParams$> $returnType $name($args1, $arg2, $args$) throws $whatever$ { $body$; }")
public static ErrorDescription tooManyParameters(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
Collection<? extends TreePath> args = ctx.getMultiVariables().get("$args$"); // NOI18N
int limit = ctx.getPreferences().getInt(OPTION_METHOD_PARAMETERS_LIMIT, DEFAULT_METHOD_PARAMETERS_LIMIT);
int count = args.size() + 2;
if (count <= limit) {
return null;
}
return ErrorDescriptionFactory.forName(ctx, t,
methodOrConstructor(ctx) ?
TEXT_ConstructorTooManyParameters(count) :
TEXT_MethodTooManyParameters(method.getName().toString(), count)
);
}
@Hint(
category = "metrics",
displayName = "#DN_CtorTooManyParameters",
description = "#DESC_CtorTooManyParameters",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@UseOptions(value = { OPTION_METHOD_PARAMETERS_LIMIT })
@TriggerPattern("$modifiers$ <$typeParams$> $name($args1, $arg2, $args$) throws $whatever$ { $body$; }")
public static ErrorDescription tooManyParametersCtor(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
Collection<? extends TreePath> args = ctx.getMultiVariables().get("$args$"); // NOI18N
int limit = ctx.getPreferences().getInt(OPTION_METHOD_PARAMETERS_LIMIT, DEFAULT_METHOD_PARAMETERS_LIMIT);
int count = args.size() + 2;
if (count <= limit) {
return null;
}
return ErrorDescriptionFactory.forName(ctx, t,
TEXT_MethodTooManyParameters(method.getName().toString(), count)
);
}
@Hint(
category = "metrics",
displayName = "#DN_MethodMultipleReturns",
description = "#DESC_MethodMultipleReturns",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@UseOptions({ OPTION_RETURN_LIMIT, OPTION_RETURN_IGNORE_EQUALS, OPTION_RETURN_IGNORE_GUARDS })
@TriggerTreeKind(Tree.Kind.METHOD)
public static ErrorDescription multipleReturnPoints(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
boolean ignoreEquals = ctx.getPreferences().getBoolean(OPTION_RETURN_IGNORE_EQUALS, DEFAULT_IGNORE_EQUALS);
if (ignoreEquals && method.getName().contentEquals("equals")) { // NOI18N
return null;
}
ReturnCountVisitor v = new ReturnCountVisitor(
ctx.getPreferences().getBoolean(OPTION_RETURN_IGNORE_GUARDS, DEFAULT_IGNORE_RETURN_GUARDS)
);
v.scan(ctx.getPath(), null);
int count = v.getReturnCount();
int limit = ctx.getPreferences().getInt(OPTION_RETURN_LIMIT, DEFAULT_RETURN_LIMIT);
if (count > limit) {
return ErrorDescriptionFactory.forName(ctx, t,
TEXT_MethodMultipleReturns(method.getName().toString(), count)
);
} else {
return null;
}
}
/**
* The visitor will ignore returns, which are the *sole* statement in a if-branch.
* Such branches are considered to be guards, which abort further processing.
*/
private static class ReturnCountVisitor extends ErrorAwareTreePathScanner {
/**
* Suppressed in local classes
*/
private boolean suppress;
private int returnCount;
/**
* If true, ignores guard returns
*/
private final boolean ignoreGuards;
public ReturnCountVisitor(boolean ignoreGuards) {
this.ignoreGuards = ignoreGuards;
}
public int getReturnCount() {
return returnCount;
}
@Override
public Object visitClass(ClassTree node, Object p) {
boolean s = this.suppress;
this.suppress = true;
Object o = super.visitClass(node, p);
this.suppress = s;
return o;
}
@Override
public Object visitReturn(ReturnTree node, Object p) {
TreePath path = getCurrentPath();
TreePath parentPath = path.getParentPath();
if (suppress) {
return super.visitReturn(node, p);
}
if (ignoreGuards && parentPath != null) {
Tree parentTree = parentPath.getLeaf();
TreePath branchPath = path;
while (parentTree.getKind() == Tree.Kind.BLOCK) {
branchPath = parentPath;
parentPath = parentPath.getParentPath();
parentTree = parentPath.getLeaf();
}
if (parentTree.getKind() == Tree.Kind.IF) {
IfTree ifTree = (IfTree)parentTree;
StatementTree trueTree = ifTree.getThenStatement() == branchPath.getLeaf() ?
ifTree.getThenStatement() : ifTree.getElseStatement();
if (trueTree == node) {
return super.visitReturn(node, p);
}
if (trueTree.getKind() == Tree.Kind.BLOCK) {
BlockTree bt = (BlockTree)trueTree;
if (bt.getStatements().size() == 1) {
return super.visitReturn(node, p);
}
}
}
}
returnCount++;
return super.visitReturn(node, p);
}
}
@Hint(
category = "metrics",
displayName = "#DN_MethodMultipleNegations",
description = "#DESC_MethodMultipleNegations",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions(value = { OPTION_NEGATIONS_IGNORE_ASSERT, OPTION_NEGATIONS_IGNORE_EQUALS, OPTION_NEGATIONS_LIMIT })
public static ErrorDescription multipleNegations(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
boolean ignoreEquals = ctx.getPreferences().getBoolean(OPTION_NEGATIONS_IGNORE_EQUALS, DEFAULT_IGNORE_EQUALS);
if (ignoreEquals && method.getName().contentEquals("equals")) { // NOI18N
return null;
}
boolean ignoreAsserts = ctx.getPreferences().getBoolean(OPTION_NEGATIONS_IGNORE_ASSERT, DEFAULT_NEGATIONS_IGNORE_ASSERT);
NegationsVisitor v = new NegationsVisitor(ignoreAsserts);
v.scan(ctx.getPath(), null);
int limit = ctx.getPreferences().getInt(OPTION_NEGATIONS_LIMIT, DEFAULT_NEGATIONS_LIMIT);
int count = v.getNegationsCount();
if (count > limit) {
return ErrorDescriptionFactory.forName(ctx, t,
TEXT_MethodMultipleNegations(method.getName().toString(), count)
);
} else {
return null;
}
}
/**
* Counts number of 'negations' in a Tree. A negation is either unary ! or binary != inequality
* operator.
*/
private static class NegationsVisitor extends ErrorAwareTreePathScanner {
private int negationsCount;
private final boolean ignoreAsserts;
public NegationsVisitor(boolean ignoreAsserts) {
this.ignoreAsserts = ignoreAsserts;
}
public int getNegationsCount() {
return negationsCount;
}
@Override
public Object visitUnary(UnaryTree node, Object p) {
if (node.getKind() == Tree.Kind.LOGICAL_COMPLEMENT) {
negationsCount++;
}
return super.visitUnary(node, p);
}
@Override
public Object visitBinary(BinaryTree node, Object p) {
if (node.getKind() == Tree.Kind.NOT_EQUAL_TO) {
negationsCount++;
}
return super.visitBinary(node, p);
}
@Override
public Object visitAssert(AssertTree node, Object p) {
int saveCount = negationsCount;
Object o = super.visitAssert(node, p);
if (ignoreAsserts) {
this.negationsCount = saveCount;
}
return o;
}
}
/**
* Utility scanner class, which counts all kinds of loops in the scanned subtree
*/
private static class LoopFinder extends ErrorAwareTreePathScanner {
private int loopCount;
public int getLoopCount() {
return loopCount;
}
@Override
public Object visitClass(ClassTree node, Object p) {
int save = loopCount;
Object o = super.visitClass(node, p);
this.loopCount = save;
return o;
}
@Override
public Object visitDoWhileLoop(DoWhileLoopTree node, Object p) {
loopCount++;
return super.visitDoWhileLoop(node, p);
}
@Override
public Object visitWhileLoop(WhileLoopTree node, Object p) {
loopCount++;
return super.visitWhileLoop(node, p);
}
@Override
public Object visitForLoop(ForLoopTree node, Object p) {
loopCount++;
return super.visitForLoop(node, p);
}
@Override
public Object visitEnhancedForLoop(EnhancedForLoopTree node, Object p) {
loopCount++;
return super.visitEnhancedForLoop(node, p);
}
}
@Hint(
category = "metrics",
displayName = "#DN_MethodMultipleLoops",
description = "#DESC_MethodMultipleLoops",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions({ OPTION_LOOPS_LIMIT })
public static ErrorDescription multipleLoops(HintContext ctx) {
Tree t = ctx.getPath().getLeaf();
MethodTree method = (MethodTree)t;
LoopFinder v = new LoopFinder();
v.scan(ctx.getPath(), null);
int count = v.getLoopCount();
int limit = ctx.getPreferences().getInt(OPTION_LOOPS_LIMIT, DEFAULT_LOOPS_LIMIT);
if (count > limit) {
return ErrorDescriptionFactory.forName(ctx, t,
TEXT_MethodMultipleLoops(method.getName().toString(), count)
);
} else {
return null;
}
}
@Hint(
category = "metrics",
displayName = "#DN_MethodCoupled",
description = "#DESC_MethodCoupled",
options = { Hint.Options.QUERY, Hint.Options.HEAVY },
enabled = false
)
@TriggerTreeKind(Tree.Kind.METHOD)
@UseOptions({ OPTION_COUPLING_LIMIT, OPTION_COUPLING_IGNORE_JAVA })
public static ErrorDescription tooManyDependencies(HintContext ctx) {
MethodTree m = (MethodTree)ctx.getPath().getLeaf();
boolean ignoreJava = ctx.getPreferences().getBoolean(OPTION_COUPLING_IGNORE_JAVA, DEFAULT_COUPLING_IGNORE_JAVA);
TypeElement outermost = ctx.getInfo().getElementUtilities().outermostTypeElement(ctx.getInfo().getTrees().getElement(ctx.getPath()));
DependencyCollector col = new DependencyCollector(ctx.getInfo());
col.setIgnoreJavaLibraries(ignoreJava);
col.setOutermostClass(outermost);
/*
left for the case that superclass references should be excluded optionally
ExecutableElement el = (ExecutableElement)ctx.getInfo().getTrees().getElement(ctx.getPath());
Element parent = el.getEnclosingElement();
while (parent != null &&
(parent.getKind() == ElementKind.INTERFACE || parent.getKind() == ElementKind.CLASS || parent.getKind() == ElementKind.ENUM)) {
Element p = parent;
while (true) {
TypeElement tel = (TypeElement)p;
col.addIgnoredQName(tel.getQualifiedName());
TypeMirror tm = tel.getSuperclass();
if (tm.getKind() == TypeKind.DECLARED) {
p = ctx.getInfo().getTypes().asElement(tm);
} else {
break;
}
}
parent = parent.getEnclosingElement();
}*/
col.scan(ctx.getPath(), null);
int deps = col.getSeenQNames().size();
int limit = ctx.getPreferences().getInt(OPTION_COUPLING_LIMIT, DEFAULT_COUPLING_LIMIT);
if (deps > limit) {
return ErrorDescriptionFactory.forName(ctx, m, TEXT_MethodTooCoupled(m.getName().toString(), deps));
} else {
return null;
}
}
}