/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.javabeans; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; import net.sourceforge.pmd.lang.java.ast.ASTResultType; import net.sourceforge.pmd.lang.java.ast.AccessNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.rule.properties.StringProperty; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; public class BeanMembersShouldSerializeRule extends AbstractJavaRule { private String prefixProperty; private static final StringProperty PREFIX_DESCRIPTOR = new StringProperty("prefix", "A variable prefix to skip, i.e., m_", "", 1.0f); public BeanMembersShouldSerializeRule() { definePropertyDescriptor(PREFIX_DESCRIPTOR); } @Override public Object visit(ASTCompilationUnit node, Object data) { prefixProperty = getProperty(PREFIX_DESCRIPTOR); super.visit(node, data); return data; } private static String[] imagesOf(List<? extends Node> nodes) { String[] imageArray = new String[nodes.size()]; for (int i = 0; i < nodes.size(); i++) { imageArray[i] = nodes.get(i).getImage(); } return imageArray; } @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.isInterface()) { return data; } Map<MethodNameDeclaration, List<NameOccurrence>> methods = node.getScope().getEnclosingScope(ClassScope.class) .getMethodDeclarations(); List<ASTMethodDeclarator> getSetMethList = new ArrayList<>(methods.size()); for (MethodNameDeclaration d : methods.keySet()) { ASTMethodDeclarator mnd = d.getMethodNameDeclaratorNode(); if (isBeanAccessor(mnd)) { getSetMethList.add(mnd); } } String[] methNameArray = imagesOf(getSetMethList); Arrays.sort(methNameArray); Map<VariableNameDeclaration, List<NameOccurrence>> vars = node.getScope() .getDeclarations(VariableNameDeclaration.class); for (VariableNameDeclaration decl : vars.keySet()) { AccessNode accessNodeParent = decl.getAccessNodeParent(); if (vars.get(decl).isEmpty() || accessNodeParent.isTransient() || accessNodeParent.isStatic()) { continue; } String varName = trimIfPrefix(decl.getImage()); varName = varName.substring(0, 1).toUpperCase() + varName.substring(1, varName.length()); boolean hasGetMethod = Arrays.binarySearch(methNameArray, "get" + varName) >= 0 || Arrays.binarySearch(methNameArray, "is" + varName) >= 0; boolean hasSetMethod = Arrays.binarySearch(methNameArray, "set" + varName) >= 0; // Note that a Setter method is not applicable to a final // variable... if (!hasGetMethod || !accessNodeParent.isFinal() && !hasSetMethod) { addViolation(data, decl.getNode(), decl.getImage()); } } return super.visit(node, data); } private String trimIfPrefix(String img) { if (prefixProperty != null && img.startsWith(prefixProperty)) { return img.substring(prefixProperty.length()); } return img; } private boolean isBeanAccessor(ASTMethodDeclarator meth) { String methodName = meth.getImage(); if (methodName.startsWith("get") || methodName.startsWith("set")) { return true; } if (methodName.startsWith("is")) { ASTResultType ret = ((ASTMethodDeclaration) meth.jjtGetParent()).getResultType(); List<ASTPrimitiveType> primitives = ret.findDescendantsOfType(ASTPrimitiveType.class); if (!primitives.isEmpty() && primitives.get(0).isBoolean()) { return true; } } return false; } }