001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd;
004:
005: import java.io.IOException;
006: import java.io.InputStream;
007: import java.util.Iterator;
008: import java.util.Map;
009: import java.util.Properties;
010: import java.util.StringTokenizer;
011:
012: import javax.xml.parsers.DocumentBuilder;
013: import javax.xml.parsers.DocumentBuilderFactory;
014: import javax.xml.parsers.ParserConfigurationException;
015:
016: import net.sourceforge.pmd.util.ResourceLoader;
017:
018: import org.w3c.dom.Document;
019: import org.w3c.dom.Element;
020: import org.w3c.dom.Node;
021: import org.w3c.dom.NodeList;
022: import org.xml.sax.SAXException;
023:
024: /**
025: * RuleSetFactory is responsible for creating RuleSet instances from XML content.
026: */
027: public class RuleSetFactory {
028:
029: private int minPriority = Rule.LOWEST_PRIORITY;
030:
031: /**
032: * Set the minimum rule priority threshold for all Rules which are loaded
033: * from RuleSets via reference.
034: *
035: * @param minPriority The minimum priority.
036: */
037: public void setMinimumPriority(int minPriority) {
038: this .minPriority = minPriority;
039: }
040:
041: /**
042: * Returns an Iterator of RuleSet objects loaded from descriptions from the
043: * "rulesets.properties" resource.
044: *
045: * @return An Iterator of RuleSet objects.
046: */
047: public Iterator<RuleSet> getRegisteredRuleSets()
048: throws RuleSetNotFoundException {
049: try {
050: Properties props = new Properties();
051: props
052: .load(ResourceLoader
053: .loadResourceAsStream("rulesets/rulesets.properties"));
054: String rulesetFilenames = props
055: .getProperty("rulesets.filenames");
056: return createRuleSets(rulesetFilenames)
057: .getRuleSetsIterator();
058: } catch (IOException ioe) {
059: throw new RuntimeException(
060: "Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath. Here's the current classpath: "
061: + System.getProperty("java.class.path"));
062: }
063: }
064:
065: /**
066: * Create a RuleSets from a list of names.
067: * The ClassLoader of the RuleSetFactory class is used.
068: *
069: * @param ruleSetFileNames A comma-separated list of rule set files.
070: * @return The new RuleSets.
071: * @throws RuleSetNotFoundException if unable to find a resource.
072: */
073: public RuleSets createRuleSets(String ruleSetFileNames)
074: throws RuleSetNotFoundException {
075: return createRuleSets(ruleSetFileNames, getClass()
076: .getClassLoader());
077: }
078:
079: /**
080: * Create a RuleSets from a list of names with a specified ClassLoader.
081: *
082: * @param ruleSetFileNames A comma-separated list of rule set files.
083: * @param classLoader The ClassLoader to load Classes and resources.
084: * @return The new RuleSets.
085: * @throws RuleSetNotFoundException if unable to find a resource.
086: */
087: public RuleSets createRuleSets(String ruleSetFileNames,
088: ClassLoader classLoader) throws RuleSetNotFoundException {
089: RuleSets ruleSets = new RuleSets();
090:
091: for (StringTokenizer st = new StringTokenizer(ruleSetFileNames,
092: ","); st.hasMoreTokens();) {
093: RuleSet ruleSet = createSingleRuleSet(
094: st.nextToken().trim(), classLoader);
095: ruleSets.addRuleSet(ruleSet);
096: }
097:
098: return ruleSets;
099: }
100:
101: /**
102: * Create a ruleset from a name or from a list of names
103: *
104: * @param name name of rule set file loaded as a resource
105: * @param classLoader the classloader used to load the ruleset and subsequent rules
106: * @return the new ruleset
107: * @throws RuleSetNotFoundException
108: * @deprecated Use createRuleSets instead, because this method puts all rules in one
109: * single RuleSet object, and thus removes name and language of the
110: * originating rule set files.
111: */
112: public RuleSet createRuleSet(String name, ClassLoader classLoader)
113: throws RuleSetNotFoundException {
114: RuleSets ruleSets = createRuleSets(name, classLoader);
115: RuleSet result = new RuleSet();
116: RuleSet[] allRuleSets = ruleSets.getAllRuleSets();
117: for (RuleSet ruleSet : allRuleSets) {
118: result.addRuleSet(ruleSet);
119: }
120: return result;
121: }
122:
123: /**
124: * Create a RuleSet from a file name resource.
125: * The ClassLoader of the RuleSetFactory class is used.
126: *
127: * @param ruleSetFileName The name of rule set file loaded as a resource.
128: * @return A new RuleSet.
129: * @throws RuleSetNotFoundException if unable to find a resource.
130: */
131: public RuleSet createSingleRuleSet(String ruleSetFileName)
132: throws RuleSetNotFoundException {
133: return createSingleRuleSet(ruleSetFileName, getClass()
134: .getClassLoader());
135: }
136:
137: /**
138: * Create a RuleSet from a file name resource with a specified ClassLoader.
139: *
140: * @param ruleSetFileName The name of rule set file loaded as a resource.
141: * @param classLoader The ClassLoader to load Classes and resources.
142: * @return A new RuleSet.
143: * @throws RuleSetNotFoundException if unable to find a resource.
144: */
145: private RuleSet createSingleRuleSet(String ruleSetFileName,
146: ClassLoader classLoader) throws RuleSetNotFoundException {
147: return parseRuleSetNode(ruleSetFileName, tryToGetStreamTo(
148: ruleSetFileName, classLoader), classLoader);
149: }
150:
151: /**
152: * Create a RuleSet from an InputStream.
153: * The ClassLoader of the RuleSetFactory class is used.
154: *
155: * @param inputStream InputStream containing the RuleSet XML configuration.
156: * @return A new RuleSet.
157: */
158: public RuleSet createRuleSet(InputStream inputStream) {
159: return createRuleSet(inputStream, getClass().getClassLoader());
160: }
161:
162: /**
163: * Create a RuleSet from an InputStream with a specified ClassLoader.
164: *
165: * @param inputStream InputStream containing the RuleSet XML configuration.
166: * @param classLoader The ClassLoader to load Classes and resources.
167: * @return A new RuleSet.
168: */
169: public RuleSet createRuleSet(InputStream inputStream,
170: ClassLoader classLoader) {
171: return parseRuleSetNode(null, inputStream, classLoader);
172: }
173:
174: /**
175: * Try to load a resource with the specified class loader
176: *
177: * @param name A resource name (e.g. a RuleSet description).
178: * @param classLoader The ClassLoader to load Classes and resources.
179: * @return An InputStream to that resource.
180: * @throws RuleSetNotFoundException if unable to find a resource.
181: */
182: private InputStream tryToGetStreamTo(String name,
183: ClassLoader classLoader) throws RuleSetNotFoundException {
184: InputStream in = ResourceLoader.loadResourceAsStream(name,
185: classLoader);
186: if (in == null) {
187: throw new RuleSetNotFoundException(
188: "Can't find resource "
189: + name
190: + ". Make sure the resource is a valid file or URL or is on the CLASSPATH. Here's the current classpath: "
191: + System.getProperty("java.class.path"));
192: }
193: return in;
194: }
195:
196: /**
197: * Parse a ruleset node to construct a RuleSet.
198: *
199: * @param inputStream InputStream containing the RuleSet XML configuration.
200: * @param classLoader The ClassLoader to load Classes and resources.
201: * @return The new RuleSet.
202: */
203: private RuleSet parseRuleSetNode(String fileName,
204: InputStream inputStream, ClassLoader classLoader) {
205: try {
206: DocumentBuilder builder = DocumentBuilderFactory
207: .newInstance().newDocumentBuilder();
208: Document document = builder.parse(inputStream);
209: Element ruleSetElement = document.getDocumentElement();
210:
211: RuleSet ruleSet = new RuleSet();
212: ruleSet.setFileName(fileName);
213: ruleSet.setName(ruleSetElement.getAttribute("name"));
214: ruleSet.setLanguage(Language.getByName(ruleSetElement
215: .getAttribute("language")));
216:
217: NodeList nodeList = ruleSetElement.getChildNodes();
218: for (int i = 0; i < nodeList.getLength(); i++) {
219: Node node = nodeList.item(i);
220: if (node.getNodeType() == Node.ELEMENT_NODE) {
221: if (node.getNodeName().equals("description")) {
222: ruleSet.setDescription(parseTextNode(node));
223: } else if (node.getNodeName().equals(
224: "include-pattern")) {
225: ruleSet.addIncludePattern(parseTextNode(node));
226: } else if (node.getNodeName().equals(
227: "exclude-pattern")) {
228: ruleSet.addExcludePattern(parseTextNode(node));
229: } else if (node.getNodeName().equals("rule")) {
230: parseRuleNode(ruleSet, node, classLoader);
231: }
232: }
233: }
234:
235: return ruleSet;
236: } catch (ClassNotFoundException cnfe) {
237: cnfe.printStackTrace();
238: throw new RuntimeException("Couldn't find that class "
239: + cnfe.getMessage());
240: } catch (InstantiationException ie) {
241: ie.printStackTrace();
242: throw new RuntimeException("Couldn't find that class "
243: + ie.getMessage());
244: } catch (IllegalAccessException iae) {
245: iae.printStackTrace();
246: throw new RuntimeException("Couldn't find that class "
247: + iae.getMessage());
248: } catch (ParserConfigurationException pce) {
249: pce.printStackTrace();
250: throw new RuntimeException("Couldn't find that class "
251: + pce.getMessage());
252: } catch (RuleSetNotFoundException rsnfe) {
253: rsnfe.printStackTrace();
254: throw new RuntimeException("Couldn't find that class "
255: + rsnfe.getMessage());
256: } catch (IOException ioe) {
257: ioe.printStackTrace();
258: throw new RuntimeException("Couldn't find that class "
259: + ioe.getMessage());
260: } catch (SAXException se) {
261: se.printStackTrace();
262: throw new RuntimeException("Couldn't find that class "
263: + se.getMessage());
264: }
265: }
266:
267: /**
268: * Parse a rule node.
269: *
270: * @param ruleSet The RuleSet being constructed.
271: * @param ruleNode Must be a rule element node.
272: * @param classLoader The ClassLoader to load Classes and resources.
273: */
274: private void parseRuleNode(RuleSet ruleSet, Node ruleNode,
275: ClassLoader classLoader) throws ClassNotFoundException,
276: InstantiationException, IllegalAccessException,
277: RuleSetNotFoundException {
278: Element ruleElement = (Element) ruleNode;
279: String ref = ruleElement.getAttribute("ref");
280: if (ref.endsWith("xml")) {
281: parseRuleSetReferenceNode(ruleSet, ruleElement, ref);
282: } else if (ref.trim().length() == 0) {
283: parseSingleRuleNode(ruleSet, ruleNode, classLoader);
284: } else {
285: parseRuleReferenceNode(ruleSet, ruleNode, ref);
286: }
287: }
288:
289: /**
290: * Parse a rule node as an RuleSetReference for all Rules. Every Rule from
291: * the referred to RuleSet will be added as a RuleReference except for those
292: * explicitly excluded.
293: *
294: * @param ruleSet The RuleSet being constructed.
295: * @param ruleElement Must be a rule element node.
296: * @param ref The RuleSet reference.
297: */
298: private void parseRuleSetReferenceNode(RuleSet ruleSet,
299: Element ruleElement, String ref)
300: throws RuleSetNotFoundException {
301:
302: RuleSetReference ruleSetReference = new RuleSetReference();
303: ruleSetReference.setAllRules(true);
304: ruleSetReference.setRuleSetFileName(ref);
305: NodeList excludeNodes = ruleElement.getChildNodes();
306: for (int i = 0; i < excludeNodes.getLength(); i++) {
307: if ((excludeNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
308: && (excludeNodes.item(i).getNodeName()
309: .equals("exclude"))) {
310: Element excludeElement = (Element) excludeNodes.item(i);
311: ruleSetReference.addExclude(excludeElement
312: .getAttribute("name"));
313: }
314: }
315:
316: RuleSetFactory ruleSetFactory = new RuleSetFactory();
317: RuleSet otherRuleSet = ruleSetFactory
318: .createRuleSet(ResourceLoader.loadResourceAsStream(ref));
319: for (Rule rule : otherRuleSet.getRules()) {
320: if (!ruleSetReference.getExcludes()
321: .contains(rule.getName())
322: && rule.getPriority() <= minPriority) {
323: RuleReference ruleReference = new RuleReference();
324: ruleReference.setRuleSetReference(ruleSetReference);
325: ruleReference.setRule(rule);
326: ruleSet.addRule(ruleReference);
327: }
328: }
329: }
330:
331: /**
332: * Parse a rule node as a single Rule. The Rule has been fully defined within
333: * the context of the current RuleSet.
334: *
335: * @param ruleSet The RuleSet being constructed.
336: * @param ruleNode Must be a rule element node.
337: * @param classLoader The ClassLoader to load Classes and resources.
338: */
339: private void parseSingleRuleNode(RuleSet ruleSet, Node ruleNode,
340: ClassLoader classLoader) throws ClassNotFoundException,
341: InstantiationException, IllegalAccessException {
342: Element ruleElement = (Element) ruleNode;
343:
344: String attribute = ruleElement.getAttribute("class");
345: Class<?> c = classLoader.loadClass(attribute);
346: Rule rule = (Rule) c.newInstance();
347:
348: rule.setName(ruleElement.getAttribute("name"));
349: String since = ruleElement.getAttribute("since");
350: if (since.length() > 0) {
351: rule.setSince(since);
352: }
353: rule.setMessage(ruleElement.getAttribute("message"));
354: rule.setRuleSetName(ruleSet.getName());
355: rule.setExternalInfoUrl(ruleElement
356: .getAttribute("externalInfoUrl"));
357:
358: if (ruleElement.hasAttribute("dfa")
359: && ruleElement.getAttribute("dfa").equals("true")) {
360: rule.setUsesDFA();
361: }
362:
363: if (ruleElement.hasAttribute("typeResolution")
364: && ruleElement.getAttribute("typeResolution").equals(
365: "true")) {
366: rule.setUsesTypeResolution();
367: }
368:
369: for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
370: Node node = ruleElement.getChildNodes().item(i);
371: if (node.getNodeType() == Node.ELEMENT_NODE) {
372: if (node.getNodeName().equals("description")) {
373: rule.setDescription(parseTextNode(node));
374: } else if (node.getNodeName().equals("example")) {
375: rule.addExample(parseTextNode(node));
376: } else if (node.getNodeName().equals("priority")) {
377: rule.setPriority(Integer.parseInt(parseTextNode(
378: node).trim()));
379: } else if (node.getNodeName().equals("properties")) {
380: Properties p = new Properties();
381: parsePropertiesNode(p, node);
382: for (Map.Entry<Object, Object> entry : p.entrySet()) {
383: rule.addProperty((String) entry.getKey(),
384: (String) entry.getValue());
385: }
386: }
387: }
388: }
389: if (rule.getPriority() <= minPriority) {
390: ruleSet.addRule(rule);
391: }
392: }
393:
394: /**
395: * Parse a rule node as a RuleReference. A RuleReference is a single Rule
396: * which comes from another RuleSet with some of it's attributes potentially
397: * overridden.
398: *
399: * @param ruleSet The RuleSet being constructed.
400: * @param ruleNode Must be a rule element node.
401: * @param classLoader The ClassLoader to load Classes and resources.
402: * @param ref A reference to a Rule.
403: */
404: private void parseRuleReferenceNode(RuleSet ruleSet, Node ruleNode,
405: String ref) throws RuleSetNotFoundException {
406: RuleSetFactory ruleSetFactory = new RuleSetFactory();
407:
408: ExternalRuleID externalRuleID = new ExternalRuleID(ref);
409: RuleSet externalRuleSet = ruleSetFactory
410: .createRuleSet(ResourceLoader
411: .loadResourceAsStream(externalRuleID
412: .getFilename()));
413: Rule externalRule = externalRuleSet
414: .getRuleByName(externalRuleID.getRuleName());
415: if (externalRule == null) {
416: throw new IllegalArgumentException("Unable to find rule "
417: + externalRuleID.getRuleName()
418: + "; perhaps the rule name is mispelled?");
419: }
420:
421: RuleSetReference ruleSetReference = new RuleSetReference();
422: ruleSetReference.setAllRules(false);
423: ruleSetReference.setRuleSetFileName(externalRuleID
424: .getFilename());
425:
426: RuleReference ruleReference = new RuleReference();
427: ruleReference.setRuleSetReference(ruleSetReference);
428: ruleReference.setRule(externalRule);
429:
430: Element ruleElement = (Element) ruleNode;
431: if (ruleElement.hasAttribute("name")) {
432: ruleReference.setName(ruleElement.getAttribute("name"));
433: }
434: if (ruleElement.hasAttribute("message")) {
435: ruleReference.setMessage(ruleElement
436: .getAttribute("message"));
437: }
438: if (ruleElement.hasAttribute("externalInfoUrl")) {
439: ruleReference.setExternalInfoUrl(ruleElement
440: .getAttribute("externalInfoUrl"));
441: }
442: for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
443: Node node = ruleElement.getChildNodes().item(i);
444: if (node.getNodeType() == Node.ELEMENT_NODE) {
445: if (node.getNodeName().equals("description")) {
446: ruleReference.setDescription(parseTextNode(node));
447: } else if (node.getNodeName().equals("example")) {
448: ruleReference.addExample(parseTextNode(node));
449: } else if (node.getNodeName().equals("priority")) {
450: ruleReference.setPriority(Integer
451: .parseInt(parseTextNode(node)));
452: } else if (node.getNodeName().equals("properties")) {
453: Properties p = new Properties();
454: parsePropertiesNode(p, node);
455: ruleReference.addProperties(p);
456: }
457: }
458: }
459:
460: if (externalRule.getPriority() <= minPriority) {
461: ruleSet.addRule(ruleReference);
462: }
463: }
464:
465: /**
466: * Parse a properties node.
467: *
468: * @param p The Properties to which the properties should be added.
469: * @param propertiesNode Must be a properties element node.
470: */
471: private static void parsePropertiesNode(Properties p,
472: Node propertiesNode) {
473: for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
474: Node node = propertiesNode.getChildNodes().item(i);
475: if (node.getNodeType() == Node.ELEMENT_NODE
476: && node.getNodeName().equals("property")) {
477: parsePropertyNode(p, node);
478: }
479: }
480: }
481:
482: /**
483: * Parse a property node.
484: *
485: * @param p The Properties to which the property should be added.
486: * @param propertyNode Must be a property element node.
487: */
488: private static void parsePropertyNode(Properties p,
489: Node propertyNode) {
490: Element propertyElement = (Element) propertyNode;
491: String name = propertyElement.getAttribute("name");
492: String value = propertyElement.getAttribute("value");
493: // TODO String description = propertyElement.getAttribute("description");
494: if (value.trim().length() == 0) {
495: for (int i = 0; i < propertyNode.getChildNodes()
496: .getLength(); i++) {
497: Node node = propertyNode.getChildNodes().item(i);
498: if ((node.getNodeType() == Node.ELEMENT_NODE)
499: && node.getNodeName().equals("value")) {
500: value = parseTextNode(node);
501: }
502: }
503: }
504: if (propertyElement.hasAttribute("pluginname")) {
505: p.setProperty("pluginname", propertyElement
506: .getAttributeNode("pluginname").getNodeValue());
507: }
508: p.setProperty(name, value);
509: }
510:
511: /**
512: * Parse a String from a textually type node.
513: *
514: * @param node The node.
515: * @return The String.
516: */
517: private static String parseTextNode(Node node) {
518: StringBuffer buffer = new StringBuffer();
519: for (int i = 0; i < node.getChildNodes().getLength(); i++) {
520: Node childNode = node.getChildNodes().item(i);
521: if (childNode.getNodeType() == Node.CDATA_SECTION_NODE
522: || childNode.getNodeType() == Node.TEXT_NODE) {
523: buffer.append(childNode.getNodeValue());
524: }
525: }
526: return buffer.toString();
527: }
528: }
|