View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.rules.design;
    5
    6   import java.util.ArrayList;
    7   import java.util.List;
    8   import java.util.concurrent.atomic.AtomicLong;
    9   import java.util.regex.Pattern;
    10
    11  import net.sourceforge.pmd.AbstractJavaRule;
    12  import net.sourceforge.pmd.PropertyDescriptor;
    13  import net.sourceforge.pmd.RuleContext;
    14  import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
    15  import net.sourceforge.pmd.ast.ASTCompilationUnit;
    16  import net.sourceforge.pmd.ast.ASTImportDeclaration;
    17  import net.sourceforge.pmd.ast.SimpleNode;
    18  import net.sourceforge.pmd.properties.StringProperty;
    19  import net.sourceforge.pmd.rules.regex.RegexHelper;
    20
    21  import org.jaxen.JaxenException;
    22
    23  /**
    24   * <p>A generic rule that can be configured to "count" classes of certain
    25   * type based on either their name (full name, prefix, suffixes anything can
    26   * be matched with a regex), and/or
    27   * their type.</p>
    28   *
    29   * <p>Example of configurations:
    30   * 		<!-- Property order is MANDATORY !!! -->
    31   * 		<!-- Several regexes may be provided to ensure a match... -->
    32   * 		<property 	name="nameMatch" description="a regex on which to match"
    33   * 					value="^Abstract.*Bean*$,^*EJB*$"/>
    34   * 		<!-- An operand to refine match strategy TODO: Not implemented yet !!! -->
    35   * 		<property 	name"operand"	description=""
    36   * 					value="and"/> <!-- possible values are and/or -->
    37   * 		<!-- Must be a full name to ensure type control !!! -->
    38   * 		<property 	name="typeMatch" description="a regex to match on implements/extends classname"
    39   * 					value="javax.servlet.Filter"/>
    40   * 		<!-- Define after how many occurences one should log a violation -->
    41   * 		<property 	name="threshold"	description="Defines how many occurences are legal"
    42   * 					value="2"/>
    43   * 		<!-- TODO: Add a parameter to allow "ignore" pattern based on name -->
    44   * </p>
    45   *
    46   * @author Ryan Gutafson, rgustav@users.sourceforge.net
    47   * @author Romain PELISSE, belaran@gmail.com
    48   *
    49   */
    50  public class GenericClassCounterRule extends AbstractJavaRule {
    51
    52
    53  	private static final PropertyDescriptor nameMatchDescriptor = new StringProperty("nameMatch",
    54  			"A series of regex, separeted by ',' to match on the classname", new String[] {""},1.0f,',');
    55
    56  	private static final PropertyDescriptor operandDescriptor = new StringProperty("operand",
    57  			"or/and value to refined match criteria",new String(),2.0f);
    58
    59  	private static final PropertyDescriptor typeMatchDescriptor = new StringProperty("typeMatch",
    60  			"A series of regex, separeted by ',' to match on implements/extends classname",new String[]{""},3.0f,',');
    61
    62  	private static final PropertyDescriptor thresholdDescriptor = new StringProperty("threshold",
    63  			"Defines how many occurences are legal",new String(),4.0f);
    64
    65
    66  	private List<Pattern> namesMatch = new ArrayList<Pattern>(0);
    67  	private List<Pattern> typesMatch = new ArrayList<Pattern>(0);
    68  	private List<SimpleNode> matches = new ArrayList<SimpleNode>(0);
    69  	private List<String> simpleClassname = new ArrayList<String>(0);
    70
    71
    72  	@SuppressWarnings("PMD") // When the rule is finished, this field will be used.
    73  	private String operand;
    74  	private int threshold;
    75
    76  	private static String COUNTER_LABEL;
    77
    78  	/**
    79  	 *	Default empty constructor
    80  	 */
    81  	public GenericClassCounterRule() {
    82  		super();
    83  	}
    84
    85  	private List<String> arrayAsList(String[] array) {
    86  		List<String> list = new ArrayList<String>(array.length);
    87  		int nbItem = 0;
    88  		while (nbItem < array.length )
    89  			list.add(array[nbItem++]);
    90  		return list;
    91  	}
    92
    93  	protected void init(){
    94  		// Creating the attribute name for the rule context
    95  		COUNTER_LABEL = this.getClass().getSimpleName() + ".number of match";
    96  		// Constructing the request from the input parameters
    97  		this.namesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getStringProperties(nameMatchDescriptor)));
    98  		this.operand = getStringProperty(operandDescriptor);
    99  		this.typesMatch = RegexHelper.compilePatternsFromList(arrayAsList(getStringProperties(typeMatchDescriptor)));
    100 		String thresholdAsString = getStringProperty(thresholdDescriptor);
    101 		this.threshold = Integer.valueOf(thresholdAsString);
    102 		// Initializing list of match
    103 		this.matches = new ArrayList<SimpleNode>();
    104
    105 	}
    106
    107 	 @Override
    108      public void start(RuleContext ctx) {
    109 		 // Adding the proper attribute to the context
    110          ctx.setAttribute(COUNTER_LABEL, new AtomicLong());
    111          super.start(ctx);
    112      }
    113
    114      @Override
    115      public Object visit(ASTCompilationUnit node, Object data) {
    116     	 init();
    117     	 return super.visit(node,data);
    118      }
    119
    120      @Override
    121      public Object visit(ASTImportDeclaration node, Object data) {
    122     	 // Is there any imported types that match ?
    123     	 for (Pattern pattern : this.typesMatch) {
    124     		 if ( RegexHelper.isMatch(pattern,node.getImportedName())) {
    125     			 if ( simpleClassname == null )
    126     				 simpleClassname = new ArrayList<String>(1);
    127     			 simpleClassname.add(node.getImportedName());
    128     		 }
    129     		 // FIXME: use type resolution framework to deal with star import ?
    130     	 }
    131          return super.visit(node, data);
    132      }
    133
    134 	@Override
    135 	public Object visit(ASTClassOrInterfaceType classType,Object data) {
    136 		// Is extends/implements list using one of the previous match on import ?
    137 		// FIXME: use type resolution framework to deal with star import ?
    138 		for (String matchType : simpleClassname) {
    139 			if ( searchForAMatch(matchType,classType)) {
    140 				addAMatch(classType, data);
    141 			}
    142 		}
    143 		// TODO: implements the "operand" functionnality
    144 		// Is there any names that actually match ?
    145 		for (Pattern pattern : this.namesMatch)
    146 			if ( RegexHelper.isMatch(pattern, classType.getImage()))
    147 				addAMatch(classType, data);
    148 		return super.visit(classType, data);
    149 	}
    150
    151 	private void addAMatch(SimpleNode node,Object data) {
    152 		// We have a match, we increment
    153 		RuleContext ctx = (RuleContext)data;
    154 		AtomicLong total = (AtomicLong)ctx.getAttribute(COUNTER_LABEL);
    155 		total.incrementAndGet();
    156 		// And we keep a ref on the node for the report generation
    157 		this.matches.add(node);
    158 	}
    159
    160 	@SuppressWarnings("unchecked")
    161     private boolean searchForAMatch(String matchType,SimpleNode node) {
    162 		boolean status = false;
    163     	 String xpathQuery = "//ClassOrInterfaceDeclaration[" +
    164 							"(./ExtendsList/ClassOrInterfaceType[@Image = '" + matchType + "'])" +
    165 							"or" +
    166 							"(./ImplementsList/ClassOrInterfaceType[@Image = '" + matchType + "'])" +
    167 							"]";
    168 		try
    169 		{
    170 			List list = node.findChildNodesWithXPath(xpathQuery);
    171 			if ( list != null && list.size() > 0 ) {
    172 				// We got a match !
    173 				status = true;
    174 			}
    175 		}
    176 		catch (JaxenException e) {
    177 			// Most likely, a should never happen exception...
    178 			e.printStackTrace();
    179 		}
    180 		return status;
    181 	}
    182
    183 	@Override
    184     public void end(RuleContext ctx) {
    185 		AtomicLong total = (AtomicLong)ctx.getAttribute(COUNTER_LABEL);
    186         // Do we have a violation ?
    187         if ( total.get() > this.threshold )
    188         	for (SimpleNode node : this.matches)
    189         		addViolation(ctx,node , new Object[] { total });
    190 		// Cleaning the context for the others rules
    191 		ctx.removeAttribute(COUNTER_LABEL);
    192 		super.start(ctx);
    193      }
    194 }