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 }