View Javadoc

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