View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd;
    5
    6   import java.io.IOException;
    7   import java.io.InputStream;
    8   import java.util.Iterator;
    9   import java.util.Map;
    10  import java.util.Properties;
    11  import java.util.StringTokenizer;
    12
    13  import javax.xml.parsers.DocumentBuilder;
    14  import javax.xml.parsers.DocumentBuilderFactory;
    15  import javax.xml.parsers.ParserConfigurationException;
    16
    17  import net.sourceforge.pmd.util.ResourceLoader;
    18
    19  import org.w3c.dom.Document;
    20  import org.w3c.dom.Element;
    21  import org.w3c.dom.Node;
    22  import org.w3c.dom.NodeList;
    23  import org.xml.sax.SAXException;
    24
    25  /**
    26   * RuleSetFactory is responsible for creating RuleSet instances from XML content.
    27   */
    28  public class RuleSetFactory {
    29
    30  	private int minPriority = Rule.LOWEST_PRIORITY;
    31
    32  	/**
    33  	 * Set the minimum rule priority threshold for all Rules which are loaded
    34  	 * from RuleSets via reference.
    35  	 *
    36  	 * @param minPriority The minimum priority.
    37  	 */
    38  	public void setMinimumPriority(int minPriority) {
    39  		this.minPriority = minPriority;
    40  	}
    41
    42  	/**
    43  	 * Returns an Iterator of RuleSet objects loaded from descriptions from the
    44  	 * "rulesets.properties" resource.
    45  	 *
    46  	 * @return An Iterator of RuleSet objects.
    47  	 */
    48  	public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException {
    49  		try {
    50  			Properties props = new Properties();
    51  			props.load(ResourceLoader.loadResourceAsStream("rulesets/rulesets.properties"));
    52  			String rulesetFilenames = props.getProperty("rulesets.filenames");
    53  			return createRuleSets(rulesetFilenames).getRuleSetsIterator();
    54  		} catch (IOException ioe) {
    55  			throw new RuntimeException(
    56  					"Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath.  Here's the current classpath: "
    57  							+ System.getProperty("java.class.path"));
    58  		}
    59  	}
    60
    61  	/**
    62  	 * Create a RuleSets from a list of names.
    63  	 * The ClassLoader of the RuleSetFactory class is used.
    64  	 *
    65  	 * @param ruleSetFileNames  A comma-separated list of rule set files.
    66  	 * @return The new RuleSets.
    67  	 * @throws RuleSetNotFoundException if unable to find a resource.
    68  	 */
    69  	public RuleSets createRuleSets(String ruleSetFileNames) throws RuleSetNotFoundException {
    70  		return createRuleSets(ruleSetFileNames, getClass().getClassLoader());
    71  	}
    72
    73  	/**
    74  	 * Create a RuleSets from a list of names with a specified ClassLoader.
    75  	 *
    76  	 * @param ruleSetFileNames  A comma-separated list of rule set files.
    77  	 * @param classLoader The ClassLoader to load Classes and resources.
    78  	 * @return The new RuleSets.
    79  	 * @throws RuleSetNotFoundException if unable to find a resource.
    80  	 */
    81  	public RuleSets createRuleSets(String ruleSetFileNames, ClassLoader classLoader) throws RuleSetNotFoundException {
    82  		RuleSets ruleSets = new RuleSets();
    83
    84  		for (StringTokenizer st = new StringTokenizer(ruleSetFileNames, ","); st.hasMoreTokens();) {
    85  			RuleSet ruleSet = createSingleRuleSet(st.nextToken().trim(), classLoader);
    86  			ruleSets.addRuleSet(ruleSet);
    87  		}
    88
    89  		return ruleSets;
    90  	}
    91
    92  	/**
    93  	 * Create a ruleset from a name or from a list of names
    94  	 *
    95  	 * @param name        name of rule set file loaded as a resource
    96  	 * @param classLoader the classloader used to load the ruleset and subsequent rules
    97  	 * @return the new ruleset
    98  	 * @throws RuleSetNotFoundException
    99  	 * @deprecated Use createRuleSets instead, because this method puts all rules in one
    100 	 *             single RuleSet object, and thus removes name and language of the
    101 	 *             originating rule set files.
    102 	 */
    103 	public RuleSet createRuleSet(String name, ClassLoader classLoader) throws RuleSetNotFoundException {
    104 		RuleSets ruleSets = createRuleSets(name, classLoader);
    105 		RuleSet result = new RuleSet();
    106 		RuleSet[] allRuleSets = ruleSets.getAllRuleSets();
    107 		for (RuleSet ruleSet : allRuleSets) {
    108 			result.addRuleSet(ruleSet);
    109 		}
    110 		return result;
    111 	}
    112
    113 	/**
    114 	 * Create a RuleSet from a file name resource.
    115 	 * The ClassLoader of the RuleSetFactory class is used.
    116 	 *
    117 	 * @param ruleSetFileName The name of rule set file loaded as a resource.
    118 	 * @return A new RuleSet.
    119 	 * @throws RuleSetNotFoundException if unable to find a resource.
    120 	 */
    121 	public RuleSet createSingleRuleSet(String ruleSetFileName) throws RuleSetNotFoundException {
    122 		return createSingleRuleSet(ruleSetFileName, getClass().getClassLoader());
    123 	}
    124
    125 	/**
    126 	 * Create a RuleSet from a file name resource with a specified ClassLoader.
    127 	 *
    128 	 * @param ruleSetFileName The name of rule set file loaded as a resource.
    129 	 * @param classLoader The ClassLoader to load Classes and resources.
    130 	 * @return A new RuleSet.
    131 	 * @throws RuleSetNotFoundException if unable to find a resource.
    132 	 */
    133 	private RuleSet createSingleRuleSet(String ruleSetFileName, ClassLoader classLoader)
    134 			throws RuleSetNotFoundException {
    135 		return parseRuleSetNode(ruleSetFileName, tryToGetStreamTo(ruleSetFileName, classLoader), classLoader);
    136 	}
    137
    138 	/**
    139 	 * Create a RuleSet from an InputStream.
    140 	 * The ClassLoader of the RuleSetFactory class is used.
    141 	 *
    142 	 * @param inputStream InputStream containing the RuleSet XML configuration.
    143 	 * @return A new RuleSet.
    144 	 */
    145 	public RuleSet createRuleSet(InputStream inputStream) {
    146 		return createRuleSet(inputStream, getClass().getClassLoader());
    147 	}
    148
    149 	/**
    150 	 * Create a RuleSet from an InputStream with a specified ClassLoader.
    151 	 *
    152 	 * @param inputStream InputStream containing the RuleSet XML configuration.
    153 	 * @param classLoader The ClassLoader to load Classes and resources.
    154 	 * @return A new RuleSet.
    155 	 */
    156 	public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) {
    157 		return parseRuleSetNode(null, inputStream, classLoader);
    158 	}
    159
    160 	/**
    161 	 * Try to load a resource with the specified class loader
    162 	 *
    163 	 * @param name A resource name (e.g. a RuleSet description).
    164 	 * @param classLoader The ClassLoader to load Classes and resources.
    165 	 * @return An InputStream to that resource.
    166 	 * @throws RuleSetNotFoundException if unable to find a resource.
    167 	 */
    168 	private InputStream tryToGetStreamTo(String name, ClassLoader classLoader) throws RuleSetNotFoundException {
    169 		InputStream in = ResourceLoader.loadResourceAsStream(name, classLoader);
    170 		if (in == null) {
    171 			throw new RuleSetNotFoundException(
    172 					"Can't find resource "
    173 							+ name
    174 							+ ".  Make sure the resource is a valid file or URL or is on the CLASSPATH.  Here's the current classpath: "
    175 							+ System.getProperty("java.class.path"));
    176 		}
    177 		return in;
    178 	}
    179
    180 	/**
    181 	 * Parse a ruleset node to construct a RuleSet.
    182 	 *
    183 	 * @param inputStream InputStream containing the RuleSet XML configuration.
    184 	 * @param classLoader The ClassLoader to load Classes and resources.
    185 	 * @return The new RuleSet.
    186 	 */
    187 	private RuleSet parseRuleSetNode(String fileName, InputStream inputStream, ClassLoader classLoader) {
    188 		try {
    189 			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    190 			Document document = builder.parse(inputStream);
    191 			Element ruleSetElement = document.getDocumentElement();
    192
    193 			RuleSet ruleSet = new RuleSet();
    194 			ruleSet.setFileName(fileName);
    195 			ruleSet.setName(ruleSetElement.getAttribute("name"));
    196 			ruleSet.setLanguage(Language.getByName(ruleSetElement.getAttribute("language")));
    197
    198 			NodeList nodeList = ruleSetElement.getChildNodes();
    199 			for (int i = 0; i < nodeList.getLength(); i++) {
    200 				Node node = nodeList.item(i);
    201 				if (node.getNodeType() == Node.ELEMENT_NODE) {
    202 					if (node.getNodeName().equals("description")) {
    203 						ruleSet.setDescription(parseTextNode(node));
    204 					} else if (node.getNodeName().equals("include-pattern")) {
    205 						ruleSet.addIncludePattern(parseTextNode(node));
    206 					} else if (node.getNodeName().equals("exclude-pattern")) {
    207 						ruleSet.addExcludePattern(parseTextNode(node));
    208 					} else if (node.getNodeName().equals("rule")) {
    209 						parseRuleNode(ruleSet, node, classLoader);
    210 					}
    211 				}
    212 			}
    213
    214 			return ruleSet;
    215 		} catch (ClassNotFoundException cnfe) {
    216 			cnfe.printStackTrace();
    217 			throw new RuntimeException("Couldn't find that class " + cnfe.getMessage());
    218 		} catch (InstantiationException ie) {
    219 			ie.printStackTrace();
    220 			throw new RuntimeException("Couldn't find that class " + ie.getMessage());
    221 		} catch (IllegalAccessException iae) {
    222 			iae.printStackTrace();
    223 			throw new RuntimeException("Couldn't find that class " + iae.getMessage());
    224 		} catch (ParserConfigurationException pce) {
    225 			pce.printStackTrace();
    226 			throw new RuntimeException("Couldn't find that class " + pce.getMessage());
    227 		} catch (RuleSetNotFoundException rsnfe) {
    228 			rsnfe.printStackTrace();
    229 			throw new RuntimeException("Couldn't find that class " + rsnfe.getMessage());
    230 		} catch (IOException ioe) {
    231 			ioe.printStackTrace();
    232 			throw new RuntimeException("Couldn't find that class " + ioe.getMessage());
    233 		} catch (SAXException se) {
    234 			se.printStackTrace();
    235 			throw new RuntimeException("Couldn't find that class " + se.getMessage());
    236 		}
    237 	}
    238
    239 	/**
    240 	 * Parse a rule node.
    241 	 *
    242 	 * @param ruleSet The RuleSet being constructed.
    243 	 * @param ruleNode Must be a rule element node.
    244 	 * @param classLoader The ClassLoader to load Classes and resources.
    245 	 */
    246 	private void parseRuleNode(RuleSet ruleSet, Node ruleNode, ClassLoader classLoader) throws ClassNotFoundException,
    247 			InstantiationException, IllegalAccessException, RuleSetNotFoundException {
    248 		Element ruleElement = (Element)ruleNode;
    249 		String ref = ruleElement.getAttribute("ref");
    250 		if (ref.endsWith("xml")) {
    251 			parseRuleSetReferenceNode(ruleSet, ruleElement, ref);
    252 		} else if (ref.trim().length() == 0) {
    253 			parseSingleRuleNode(ruleSet, ruleNode, classLoader);
    254 		} else {
    255 			parseRuleReferenceNode(ruleSet, ruleNode, ref);
    256 		}
    257 	}
    258
    259 	/**
    260 	 * Parse a rule node as an RuleSetReference for all Rules.  Every Rule from
    261 	 * the referred to RuleSet will be added as a RuleReference except for those
    262 	 * explicitly excluded.
    263 	 *
    264 	 * @param ruleSet The RuleSet being constructed.
    265 	 * @param ruleElement Must be a rule element node.
    266 	 * @param ref The RuleSet reference.
    267 	 */
    268 	private void parseRuleSetReferenceNode(RuleSet ruleSet, Element ruleElement, String ref)
    269 			throws RuleSetNotFoundException {
    270
    271 		RuleSetReference ruleSetReference = new RuleSetReference();
    272 		ruleSetReference.setAllRules(true);
    273 		ruleSetReference.setRuleSetFileName(ref);
    274 		NodeList excludeNodes = ruleElement.getChildNodes();
    275 		for (int i = 0; i < excludeNodes.getLength(); i++) {
    276 			if ((excludeNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
    277 					&& (excludeNodes.item(i).getNodeName().equals("exclude"))) {
    278 				Element excludeElement = (Element)excludeNodes.item(i);
    279 				ruleSetReference.addExclude(excludeElement.getAttribute("name"));
    280 			}
    281 		}
    282
    283 		RuleSetFactory ruleSetFactory = new RuleSetFactory();
    284 		RuleSet otherRuleSet = ruleSetFactory.createRuleSet(ResourceLoader.loadResourceAsStream(ref));
    285 		for (Rule rule : otherRuleSet.getRules()) {
    286 			if (!ruleSetReference.getExcludes().contains(rule.getName()) && rule.getPriority() <= minPriority) {
    287 				RuleReference ruleReference = new RuleReference();
    288 				ruleReference.setRuleSetReference(ruleSetReference);
    289 				ruleReference.setRule(rule);
    290 				ruleSet.addRule(ruleReference);
    291 			}
    292 		}
    293 	}
    294
    295 	/**
    296 	 * Parse a rule node as a single Rule.  The Rule has been fully defined within
    297 	 * the context of the current RuleSet.
    298 	 *
    299 	 * @param ruleSet The RuleSet being constructed.
    300 	 * @param ruleNode Must be a rule element node.
    301 	 * @param classLoader The ClassLoader to load Classes and resources.
    302 	 */
    303 	private void parseSingleRuleNode(RuleSet ruleSet, Node ruleNode, ClassLoader classLoader)
    304 			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    305 		Element ruleElement = (Element)ruleNode;
    306
    307 		String attribute = ruleElement.getAttribute("class");
    308 		Class<?> c = classLoader.loadClass(attribute);
    309 		Rule rule = (Rule)c.newInstance();
    310
    311 		rule.setName(ruleElement.getAttribute("name"));
    312 		String since = ruleElement.getAttribute("since");
    313 		if (since.length() > 0) {
    314 			rule.setSince(since);
    315 		}
    316 		rule.setMessage(ruleElement.getAttribute("message"));
    317 		rule.setRuleSetName(ruleSet.getName());
    318 		rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
    319
    320 		if (ruleElement.hasAttribute("dfa") && ruleElement.getAttribute("dfa").equals("true")) {
    321 			rule.setUsesDFA();
    322 		}
    323
    324 		if (ruleElement.hasAttribute("typeResolution") && ruleElement.getAttribute("typeResolution").equals("true")) {
    325 			rule.setUsesTypeResolution();
    326 		}
    327
    328 		for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
    329 			Node node = ruleElement.getChildNodes().item(i);
    330 			if (node.getNodeType() == Node.ELEMENT_NODE) {
    331 				if (node.getNodeName().equals("description")) {
    332 					rule.setDescription(parseTextNode(node));
    333 				} else if (node.getNodeName().equals("example")) {
    334 					rule.addExample(parseTextNode(node));
    335 				} else if (node.getNodeName().equals("priority")) {
    336 					rule.setPriority(Integer.parseInt(parseTextNode(node).trim()));
    337 				} else if (node.getNodeName().equals("properties")) {
    338 					Properties p = new Properties();
    339 					parsePropertiesNode(p, node);
    340 					for (Map.Entry<Object, Object> entry : p.entrySet()) {
    341 						rule.addProperty((String)entry.getKey(), (String)entry.getValue());
    342 					}
    343 				}
    344 			}
    345 		}
    346 		if (rule.getPriority() <= minPriority) {
    347 			ruleSet.addRule(rule);
    348 		}
    349 	}
    350
    351 	/**
    352 	 * Parse a rule node as a RuleReference.  A RuleReference is a single Rule
    353 	 * which comes from another RuleSet with some of it's attributes potentially
    354 	 * overridden.
    355 	 *
    356 	 * @param ruleSet The RuleSet being constructed.
    357 	 * @param ruleNode Must be a rule element node.
    358 	 * @param classLoader The ClassLoader to load Classes and resources.
    359 	 * @param ref A reference to a Rule.
    360 	 */
    361 	private void parseRuleReferenceNode(RuleSet ruleSet, Node ruleNode, String ref) throws RuleSetNotFoundException {
    362 		RuleSetFactory ruleSetFactory = new RuleSetFactory();
    363
    364 		ExternalRuleID externalRuleID = new ExternalRuleID(ref);
    365 		RuleSet externalRuleSet = ruleSetFactory.createRuleSet(ResourceLoader.loadResourceAsStream(externalRuleID.getFilename()));
    366 		Rule externalRule = externalRuleSet.getRuleByName(externalRuleID.getRuleName());
    367 		if (externalRule == null) {
    368 			throw new IllegalArgumentException("Unable to find rule " + externalRuleID.getRuleName()
    369 					+ "; perhaps the rule name is mispelled?");
    370 		}
    371
    372 		RuleSetReference ruleSetReference = new RuleSetReference();
    373 		ruleSetReference.setAllRules(false);
    374 		ruleSetReference.setRuleSetFileName(externalRuleID.getFilename());
    375
    376 		RuleReference ruleReference = new RuleReference();
    377 		ruleReference.setRuleSetReference(ruleSetReference);
    378 		ruleReference.setRule(externalRule);
    379
    380 		Element ruleElement = (Element)ruleNode;
    381 		if (ruleElement.hasAttribute("name")) {
    382 			ruleReference.setName(ruleElement.getAttribute("name"));
    383 		}
    384 		if (ruleElement.hasAttribute("message")) {
    385 			ruleReference.setMessage(ruleElement.getAttribute("message"));
    386 		}
    387 		if (ruleElement.hasAttribute("externalInfoUrl")) {
    388 			ruleReference.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
    389 		}
    390 		for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
    391 			Node node = ruleElement.getChildNodes().item(i);
    392 			if (node.getNodeType() == Node.ELEMENT_NODE) {
    393 				if (node.getNodeName().equals("description")) {
    394 					ruleReference.setDescription(parseTextNode(node));
    395 				} else if (node.getNodeName().equals("example")) {
    396 					ruleReference.addExample(parseTextNode(node));
    397 				} else if (node.getNodeName().equals("priority")) {
    398 					ruleReference.setPriority(Integer.parseInt(parseTextNode(node)));
    399 				} else if (node.getNodeName().equals("properties")) {
    400 					Properties p = new Properties();
    401 					parsePropertiesNode(p, node);
    402 					ruleReference.addProperties(p);
    403 				}
    404 			}
    405 		}
    406
    407 		if (externalRule.getPriority() <= minPriority) {
    408 			ruleSet.addRule(ruleReference);
    409 		}
    410 	}
    411
    412 	/**
    413 	 * Parse a properties node.
    414 	 *
    415 	 * @param p The Properties to which the properties should be added.
    416 	 * @param propertiesNode Must be a properties element node.
    417 	 */
    418 	private static void parsePropertiesNode(Properties p, Node propertiesNode) {
    419 		for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
    420 			Node node = propertiesNode.getChildNodes().item(i);
    421 			if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("property")) {
    422 				parsePropertyNode(p, node);
    423 			}
    424 		}
    425 	}
    426
    427 	/**
    428 	 * Parse a property node.
    429 	 *
    430 	 * @param p The Properties to which the property should be added.
    431 	 * @param propertyNode Must be a property element node.
    432 	 */
    433 	private static void parsePropertyNode(Properties p, Node propertyNode) {
    434 		Element propertyElement = (Element)propertyNode;
    435 		String name = propertyElement.getAttribute("name");
    436 		String value = propertyElement.getAttribute("value");
    437 		// TODO String description = propertyElement.getAttribute("description");
    438 		if (value.trim().length() == 0) {
    439 			for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
    440 				Node node = propertyNode.getChildNodes().item(i);
    441 				if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals("value")) {
    442 					value = parseTextNode(node);
    443 				}
    444 			}
    445 		}
    446 		if (propertyElement.hasAttribute("pluginname")) {
    447 			p.setProperty("pluginname", propertyElement.getAttributeNode("pluginname").getNodeValue());
    448 		}
    449 		p.setProperty(name, value);
    450 	}
    451
    452 	/**
    453 	 * Parse a String from a textually type node.
    454 	 *
    455 	 * @param node The node.
    456 	 * @return The String.
    457 	 */
    458 	private static String parseTextNode(Node node) {
    459 		StringBuffer buffer = new StringBuffer();
    460 		for (int i = 0; i < node.getChildNodes().getLength(); i++) {
    461 			Node childNode = node.getChildNodes().item(i);
    462 			if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) {
    463 				buffer.append(childNode.getNodeValue());
    464 			}
    465 		}
    466 		return buffer.toString();
    467 	}
    468 }