blob: 89eca3c0ed4f842c557d058b0e09489f394d3775 [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.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.beans.Expression;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.source.CompilationInfo;
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.*;
/**
* Provides basic class metrics
*
* @author sdedic
*/
@NbBundle.Messages({
"# {0} - class name",
"# {1} - complexity",
"TEXT_ClassTooComplex=Class {0} is too complex. Cyclomatic complexity = {1}",
"# {0} - complexity",
"TEXT_ClassAnonymousTooComplex=Anonymous class is too complex. Cyclomatic complexity = {0}",
"# {0} - class name",
"# {1} - number of referencies",
"TEXT_ClassTooCoupled=Class {0} is too coupled. References {1} other types",
"# {0} - class name",
"# {1} - method count",
"TEXT_ClassManyMethods=Class {0} has too many methods: {1}",
"# {0} - class name",
"# {1} - constructor count",
"TEXT_ClassManyConstructors=Class {0} has too many constructors: {1}",
"# {0} - class name",
"# {1} - field count",
"TEXT_ClassManyFields=Class {0} has too many fields: {1}",
"# {0} - method count",
"TEXT_AnonClassManyMethods=Anonymous class has too many methods: {0}",
})
public class ClassMetrics {
static final int DEFAULT_ANONYMOUS_COMPLEXITY_LIMIT = 5;
static final int DEFAULT_COMPLEXITY_LIMIT = 80;
static final int DEFAULT_COUPLING_LIMIT = 25;
static final int DEFAULT_CLASS_FIELDS_LIMIT = 10;
static final int DEFAULT_CLASS_METHODS_LIMIT = 20;
static final int DEFAULT_ANON_CLASS_METHODS_LIMIT = 3;
static final int DEFAULT_CLASS_CONSTRUCTORS_LIMIT = 5;
static final boolean DEFAULT_COUPLING_IGNORE_JAVA = true;
static final boolean DEFAULT_CLASS_METHODS_IGNORE_ACCESSORS = true;
static final boolean DEFAULT_CLASS_FIELDS_IGNORE_CONSTANTS = true;
static final boolean DEFAULT_CLASS_METHODS_IGNORE_ABSTRACT = true;
@IntegerOption(
displayName = "#OPTNAME_ClassAnonymousComplexityLimit",
tooltip = "#OPTDESC_ClassAnonymousComplexityLimit",
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_ANONYMOUS_COMPLEXITY_LIMIT
)
public static final String OPTION_ANONYMOUS_COMPLEXITY_LIMIT = "metrics.class.anonymous.complexity.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_ClassComplexityLimit",
tooltip = "#OPTDESC_ClassComplexityLimit",
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_COMPLEXITY_LIMIT
)
public static final String OPTION_COMPLEXITY_LIMIT = "metrics.class.complexity.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_ClassCouplingLimit",
tooltip = "#OPTDESC_ClassCouplingLimit",
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_COUPLING_LIMIT
)
public static final String OPTION_COUPLING_LIMIT = "metrics.class.coupling.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_ClassCouplingIgnoreJava",
tooltip = "#OPTDESC_ClassCouplingIgnoreJava",
defaultValue = DEFAULT_COUPLING_IGNORE_JAVA
)
public static final String OPTION_COUPLING_IGNORE_JAVA = "metrics.class.coupling.nojava"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_ClassConstructorsLimit",
tooltip = "#OPTDESC_ClassConstructorsLimit",
minValue = 1,
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_CLASS_CONSTRUCTORS_LIMIT
)
public static final String OPTION_CLASS_CONSTRUCTORS_LIMIT = "metrics.class.constructors.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_ClassMethodsLimit",
tooltip = "#OPTDESC_ClassMethodsLimit",
minValue = 1,
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_CLASS_METHODS_LIMIT
)
public static final String OPTION_CLASS_METHODS_LIMIT = "metrics.class.methods.limit"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_AnonClassMethodsLimit",
tooltip = "#OPTDESC_AnonClassMethodsLimit",
minValue = 1,
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_ANON_CLASS_METHODS_LIMIT
)
public static final String OPTION_ANON_CLASS_METHODS_LIMIT = "metrics.anonclass.methods.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_ClassMethodCountIgnoreAccessors",
tooltip = "#OPTDESC_ClassMethodCountIgnoreAccessors",
defaultValue = DEFAULT_CLASS_METHODS_IGNORE_ACCESSORS
)
public static final String OPTION_CLASS_METHODS_IGNORE_ACCESSORS = "metrics.class.methods.ignoreaccessors"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_ClassMethodCountIgnoreAbstract",
tooltip = "#OPTDESC_ClassMethodCountIgnoreAbstract",
defaultValue = DEFAULT_CLASS_METHODS_IGNORE_ABSTRACT
)
public static final String OPTION_CLASS_METHODS_IGNORE_ABSTRACT = "metrics.class.methods.ignoreabstract"; // NOI18N
@IntegerOption(
displayName = "#OPTNAME_ClassFieldsLimit",
tooltip = "#OPTDESC_ClassFieldsLimit",
minValue = 1,
maxValue = 1000,
step = 1,
defaultValue = DEFAULT_CLASS_METHODS_LIMIT
)
public static final String OPTION_CLASS_FIELDS_LIMIT = "metrics.class.fields.limit"; // NOI18N
@BooleanOption(
displayName = "#OPTNAME_FieldCountIgnoreConstants",
tooltip = "#OPTDESC_FieldCountIgnoreConstants",
defaultValue = DEFAULT_CLASS_FIELDS_IGNORE_CONSTANTS
)
public static final String OPTION_CLASS_FIELDS_IGNORE_CONSTANTS = "metrics.class.fields.ignoreconst"; // NOI18N
@Hint(
displayName = "#DN_ClassAnonymousTooComplex",
description = "#DESC_ClassAnonymousTooComplex",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions(OPTION_ANONYMOUS_COMPLEXITY_LIMIT)
@TriggerPatterns({
@TriggerPattern("new $classname<$tparams$>($params$) { $members$; }"),
@TriggerPattern("$expr.new $classname<$tparams$>($params$) { $members$; }"),
@TriggerPattern("new $classname($params$) { $members$; }"),
@TriggerPattern("$expr.new $classname($params$) { $members$; }"),
})
public static ErrorDescription tooComplexAnonymousClass(HintContext ctx) {
CyclomaticComplexityVisitor v = new CyclomaticComplexityVisitor();
v.scan(ctx.getPath(), null);
int complexity = v.getComplexity();
int limit = ctx.getPreferences().getInt(OPTION_ANONYMOUS_COMPLEXITY_LIMIT,
DEFAULT_ANONYMOUS_COMPLEXITY_LIMIT);
if (complexity > limit) {
CompilationInfo info = ctx.getInfo();
SourcePositions pos = info.getTrees().getSourcePositions();
NewClassTree nct = (NewClassTree)ctx.getPath().getLeaf();
long start = pos.getStartPosition(info.getCompilationUnit(), nct);
long mstart = pos.getStartPosition(info.getCompilationUnit(), nct.getClassBody());
return ErrorDescriptionFactory.forSpan(ctx,
(int)start, (int)mstart,
TEXT_ClassAnonymousTooComplex(complexity));
} else {
return null;
}
}
@Hint(
displayName = "#DN_ClassTooComplex",
description = "#DESC_ClassTooComplex",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions(OPTION_COMPLEXITY_LIMIT)
@TriggerTreeKind(Tree.Kind.CLASS)
public static ErrorDescription tooComplexClass(HintContext ctx) {
ClassTree clazz = (ClassTree)ctx.getPath().getLeaf();
TypeElement e = (TypeElement)ctx.getInfo().getTrees().getElement(ctx.getPath());
if (e.getNestingKind() == NestingKind.ANONYMOUS) {
return null;
}
CyclomaticComplexityVisitor v = new CyclomaticComplexityVisitor();
v.scan(ctx.getPath(), null);
int complexity = v.getComplexity();
int limit = ctx.getPreferences().getInt(OPTION_COMPLEXITY_LIMIT, DEFAULT_COMPLEXITY_LIMIT);
if (complexity > limit) {
return ErrorDescriptionFactory.forName(ctx,
ctx.getPath(),
TEXT_ClassTooComplex(clazz.getSimpleName().toString(), complexity));
} else {
return null;
}
}
@Hint(
displayName = "#DN_ClassTooCoupled",
description = "#DESC_ClassTooCoupled",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions({ OPTION_COUPLING_LIMIT, OPTION_COUPLING_IGNORE_JAVA })
@TriggerTreeKind(Tree.Kind.CLASS)
public static ErrorDescription tooCoupledClass(HintContext ctx) {
ClassTree clazz = (ClassTree)ctx.getPath().getLeaf();
DependencyCollector col = new DependencyCollector(ctx.getInfo());
boolean ignoreJava = ctx.getPreferences().getBoolean(OPTION_COUPLING_IGNORE_JAVA, DEFAULT_COUPLING_IGNORE_JAVA);
col.setIgnoreJavaLibraries(ignoreJava);
col.scan(ctx.getPath(), null);
int coupling = col.getSeenQNames().size();
int limit = ctx.getPreferences().getInt(OPTION_COUPLING_LIMIT, DEFAULT_COUPLING_LIMIT);
if (coupling > limit) {
return ErrorDescriptionFactory.forName(ctx,
ctx.getPath(),
TEXT_ClassTooCoupled(clazz.getSimpleName().toString(), coupling));
} else {
return null;
}
}
@Hint(
displayName = "#DN_ClassConstructorCount",
description = "#DESC_ClassConstructorCount",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions({OPTION_CLASS_CONSTRUCTORS_LIMIT})
@TriggerTreeKind(Tree.Kind.CLASS)
public static ErrorDescription tooManyConstructors(HintContext ctx) {
ClassTree clazz = (ClassTree)ctx.getPath().getLeaf();
int methodCount = 0;
for (Tree member : clazz.getMembers()) {
if (member.getKind() != Tree.Kind.METHOD) {
continue;
}
MethodTree method = (MethodTree)member;
if (method.getReturnType() != null) {
// a constructor ?
continue;
}
methodCount++;
}
int limit = ctx.getPreferences().getInt(OPTION_CLASS_CONSTRUCTORS_LIMIT, DEFAULT_CLASS_CONSTRUCTORS_LIMIT);
if (methodCount <= limit) {
return null;
}
return ErrorDescriptionFactory.forName(ctx,
ctx.getPath(),
TEXT_ClassManyConstructors(clazz.getSimpleName().toString(), methodCount));
}
@Hint(
displayName = "#DN_AnonClassMethodCount",
description = "#DESC_AnonClassMethodCount",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@TriggerPatterns({
@TriggerPattern("new $classname<$tparams$>($params$) { $members$; }"),
@TriggerPattern("$expr.new $classname<$tparams$>($params$) { $members$; }"),
@TriggerPattern("new $classname($params$) { $members$; }"),
@TriggerPattern("$expr.new $classname($params$) { $members$; }"),
})
@UseOptions({ OPTION_ANON_CLASS_METHODS_LIMIT})
public static ErrorDescription anonymousTooManyMethods(HintContext ctx) {
NewClassTree nct = (NewClassTree)ctx.getPath().getLeaf();
return checkTooManyMethods(ctx,
new TreePath(ctx.getPath(), nct.getClassBody()),
ctx.getPreferences().getInt(OPTION_ANON_CLASS_METHODS_LIMIT, DEFAULT_ANON_CLASS_METHODS_LIMIT),
true);
}
@Hint(
displayName = "#DN_ClassMethodCount",
description = "#DESC_ClassMethodCount",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions({ OPTION_CLASS_METHODS_IGNORE_ABSTRACT, OPTION_CLASS_METHODS_IGNORE_ACCESSORS,
OPTION_CLASS_METHODS_LIMIT})
@TriggerTreeKind(Tree.Kind.CLASS)
public static ErrorDescription tooManyMethods(HintContext ctx) {
return checkTooManyMethods(ctx, ctx.getPath(),
ctx.getPreferences().getInt(OPTION_CLASS_METHODS_LIMIT, DEFAULT_CLASS_METHODS_LIMIT),
false);
}
private static ErrorDescription checkTooManyMethods(HintContext ctx, TreePath path, int limit, boolean anon) {
ClassTree clazz = (ClassTree)path.getLeaf();
boolean ignoreAccessors = ctx.getPreferences().getBoolean(OPTION_CLASS_METHODS_IGNORE_ACCESSORS, DEFAULT_CLASS_METHODS_IGNORE_ACCESSORS);
boolean ignoreAbstract = ctx.getPreferences().getBoolean(OPTION_CLASS_METHODS_IGNORE_ABSTRACT, DEFAULT_CLASS_METHODS_IGNORE_ABSTRACT);
int methodCount = 0;
for (Tree member : clazz.getMembers()) {
if (member.getKind() != Tree.Kind.METHOD) {
continue;
}
MethodTree method = (MethodTree)member;
if (method.getReturnType() == null) {
// a constructor ?
continue;
}
TreePath methodPath = new TreePath(path, method);
if (ignoreAccessors && (isSimpleGetter(ctx.getInfo(), methodPath) ||
isSimpleSetter(ctx.getInfo(), methodPath))) {
continue;
}
if (ignoreAbstract) {
ExecutableElement mel = (ExecutableElement)ctx.getInfo().getTrees().getElement(methodPath);
ExecutableElement overriden = ctx.getInfo().getElementUtilities().getOverriddenMethod(mel);
if (overriden != null && overriden.getModifiers().contains(Modifier.ABSTRACT)) {
continue;
}
}
methodCount++;
}
if (methodCount <= limit) {
return null;
}
if (anon) {
CompilationInfo info = ctx.getInfo();
SourcePositions pos = info.getTrees().getSourcePositions();
long start = pos.getStartPosition(info.getCompilationUnit(), path.getParentPath().getLeaf());
long mstart = pos.getStartPosition(info.getCompilationUnit(), path.getLeaf());
return ErrorDescriptionFactory.forSpan(ctx, (int)start, (int)mstart,
TEXT_AnonClassManyMethods(methodCount));
} else {
return ErrorDescriptionFactory.forName(ctx,
path,
TEXT_ClassManyMethods(clazz.getSimpleName().toString(), methodCount));
}
}
@Hint(
displayName = "#DN_ClassFieldCount",
description = "#DESC_ClassFieldCount",
category = "metrics",
options = { Hint.Options.HEAVY, Hint.Options.QUERY },
enabled = false
)
@UseOptions({OPTION_CLASS_FIELDS_IGNORE_CONSTANTS, OPTION_CLASS_FIELDS_LIMIT})
@TriggerTreeKind(Tree.Kind.CLASS)
public static ErrorDescription tooManyFields(HintContext ctx) {
ClassTree clazz = (ClassTree)ctx.getPath().getLeaf();
boolean ignoreConstants = ctx.getPreferences().getBoolean(OPTION_CLASS_FIELDS_IGNORE_CONSTANTS, DEFAULT_CLASS_FIELDS_IGNORE_CONSTANTS);
int fieldCount = 0;
for (Tree member : clazz.getMembers()) {
if (member.getKind() != Tree.Kind.VARIABLE) {
continue;
}
if (ignoreConstants) {
TreePath fieldPath = new TreePath(ctx.getPath(), member);
if (isConstant(ctx.getInfo(), fieldPath)) {
continue;
}
}
fieldCount++;
}
int limit = ctx.getPreferences().getInt(OPTION_CLASS_FIELDS_LIMIT, DEFAULT_CLASS_FIELDS_LIMIT);
if (fieldCount > limit) {
return ErrorDescriptionFactory.forName(ctx,
ctx.getPath(),
TEXT_ClassManyFields(clazz.getSimpleName().toString(), fieldCount));
} else {
return null;
}
}
static boolean isSimpleSetter(CompilationInfo info, TreePath methodPath) {
MethodTree method = (MethodTree)methodPath.getLeaf();
Name mn = method.getName();
if (mn.length() < 4 || !mn.subSequence(0, 3).toString().equals("set")) { // NOI18N
return false;
}
if (method.getParameters().size() != 1) {
return false;
}
TypeMirror retType = info.getTrees().getTypeMirror(new TreePath(methodPath, method.getReturnType()));
if (retType.getKind() != TypeKind.VOID) {
return false;
}
if (method.getBody() == null || method.getBody().getStatements().size() != 1) {
return false;
}
StatementTree st = method.getBody().getStatements().get(0);
if (st.getKind() != Tree.Kind.EXPRESSION_STATEMENT) {
return false;
}
ExpressionTree stEx = ((ExpressionStatementTree)st).getExpression();
if (stEx.getKind() != Tree.Kind.ASSIGNMENT) {
return false;
}
Element e = info.getTrees().getElement(
new TreePath(new TreePath(new TreePath(
new TreePath(methodPath, method.getBody()),
st), stEx),
((AssignmentTree)stEx).getVariable()));
if (!isFieldOfThis(info, e, methodPath)) {
return false;
}
// ensure that the expression is just parenthesized / typecasted the parameter
ExpressionTree expr = ((AssignmentTree)stEx).getExpression();
TreePath exprPath = new TreePath(new TreePath(
new TreePath(methodPath, method.getBody()),
st), expr);
boolean unwrap = true;
while (unwrap) {
switch (expr.getKind()) {
case PARENTHESIZED:
expr = ((ParenthesizedTree)expr).getExpression();
break;
case TYPE_CAST:
expr = ((TypeCastTree)expr).getExpression();
break;
default:
unwrap = false;
break;
}
if (unwrap) {
exprPath = new TreePath(exprPath, expr);
}
}
Name paramName = method.getParameters().get(0).getName();
if (expr.getKind() != Tree.Kind.IDENTIFIER ||
!((IdentifierTree)expr).getName().equals(paramName)) {
return false;
}
return true;
}
/**
* Checks whether the expression at tree path resolves to a field
* on this type or a supertype.
*
* @param info
* @param e
* @param methodPath
* @return
*/
private static boolean isFieldOfThis(CompilationInfo info, Element e, TreePath methodPath) {
if (e == null || e.getKind() != ElementKind.FIELD) {
return false;
}
if (e.getEnclosingElement() == null || e.getEnclosingElement().getKind() != ElementKind.CLASS) {
return false;
}
TypeMirror methodDeclaringType = info.getTrees().getElement(methodPath).getEnclosingElement().asType();
TypeMirror fieldParent = e.getEnclosingElement().asType();
return info.getTypes().isSubtype(methodDeclaringType, fieldParent);
}
/**
* Determines if the method tree represents a simple getter. That is, a no-parameter method, whose sole
* command is return of an instance variable.
*/
static boolean isSimpleGetter(CompilationInfo info, TreePath methodPath) {
MethodTree method = (MethodTree)methodPath.getLeaf();
Name mn = method.getName();
boolean boolProp = false;
String propName;
if (mn.length() < 3) {
return false;
}
if (mn.subSequence(0, 2).equals("is")) { // NOI18N
boolProp = true;
propName = mn.subSequence(2, mn.length()).toString();
} else {
if (mn.length() < 4 || !mn.subSequence(0, 3).toString().equals("get")) { // NOI18N
return false;
}
}
if (!method.getParameters().isEmpty()) {
return false;
}
if (method.getBody() == null) {
return false;
}
List<? extends StatementTree> stmts = method.getBody().getStatements();
if (stmts.size() != 1) {
return false;
}
StatementTree ret = stmts.get(0);
if (ret.getKind() != Tree.Kind.RETURN) {
return false;
}
ExpressionTree expr = ((ReturnTree)ret).getExpression();
TreePath exprPath = new TreePath(new TreePath(
new TreePath(methodPath, method.getBody()),
ret), expr);
boolean unwrap = true;
while (unwrap) {
switch (expr.getKind()) {
case PARENTHESIZED:
expr = ((ParenthesizedTree)expr).getExpression();
break;
case TYPE_CAST:
expr = ((TypeCastTree)expr).getExpression();
break;
default:
unwrap = false;
break;
}
if (unwrap) {
exprPath = new TreePath(exprPath, expr);
}
}
// the field can be accessed either as this.field or field
Name fieldName;
if (expr.getKind() == Tree.Kind.MEMBER_SELECT) {
ExpressionTree selector = ((MemberSelectTree)expr).getExpression();
if (selector.getKind() != Tree.Kind.IDENTIFIER) {
return false;
} else if (!((IdentifierTree)selector).getName().contentEquals("this")) {
return false;
}
fieldName = ((MemberSelectTree)expr).getIdentifier();
} else if (expr.getKind() == Tree.Kind.IDENTIFIER) {
fieldName = ((IdentifierTree)expr).getName();
}
Element e = info.getTrees().getElement(exprPath);
return isFieldOfThis(info, e, methodPath);
}
private static final Collection<Modifier> CONSTANT_MODS = EnumSet.of(
Modifier.STATIC, Modifier.FINAL);
/**
* Determines whether a field is 'constant'. The field must be static final,
* and its type must be primitive, wrapper, String or Class.
*/
static boolean isConstant(CompilationInfo info, TreePath fieldPath) {
VariableTree var = (VariableTree)fieldPath.getLeaf();
if (!var.getModifiers().getFlags().containsAll(CONSTANT_MODS)) {
return false;
}
TypeMirror tm = info.getTrees().getTypeMirror(fieldPath);
switch (tm.getKind()) {
case BOOLEAN:
case BYTE: case CHAR: case DOUBLE: case FLOAT:
case INT: case LONG: case SHORT:
return true;
case DECLARED:
break;
default:
return false;
}
Element e = info.getTypes().asElement(tm);
if (!(e instanceof TypeElement)) {
return false;
}
String fqn = ((TypeElement)e).getQualifiedName().toString();
if (!fqn.startsWith("java.lang.")) {
return false;
}
String sn = ((TypeElement)e).getSimpleName().toString();
switch (sn) {
case "Boolean": case "Byte": case "Char": case "Double": case "Float":
case "Integer": case "Long": case "Short":
case "String":
case "Class":
return true;
}
return false;
}
}