1 /*** 2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html 3 */ 4 package net.sourceforge.pmd; 5 6 import net.sourceforge.pmd.util.ResourceLoader; 7 import org.w3c.dom.Document; 8 import org.w3c.dom.Element; 9 import org.w3c.dom.Node; 10 import org.w3c.dom.NodeList; 11 12 import javax.xml.parsers.DocumentBuilder; 13 import javax.xml.parsers.DocumentBuilderFactory; 14 import java.io.IOException; 15 import java.io.InputStream; 16 import java.util.ArrayList; 17 import java.util.HashSet; 18 import java.util.Iterator; 19 import java.util.List; 20 import java.util.Properties; 21 import java.util.Set; 22 import java.util.StringTokenizer; 23 24 public class RuleSetFactory { 25 private ClassLoader classLoader; 26 27 /*** 28 * Returns an Iterator of RuleSet objects loaded from descriptions from 29 * the "rulesets.properties" resource or from the "rulesets.filenames" property. 30 * @return an iterator on RuleSet objects 31 */ 32 public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException { 33 try { 34 Properties props = new Properties(); 35 props.load(ResourceLoader.loadResourceAsStream("rulesets/rulesets.properties")); 36 String rulesetFilenames = props.getProperty("rulesets.filenames"); 37 List ruleSets = new ArrayList(); 38 for (StringTokenizer st = new StringTokenizer(rulesetFilenames, ","); st.hasMoreTokens();) { 39 ruleSets.add(createRuleSet(st.nextToken())); 40 } 41 return ruleSets.iterator(); 42 } catch (IOException ioe) { 43 throw new RuntimeException("Couldn't find rulesets.properties; please ensure that the rulesets directory is on the classpath. Here's the current classpath: " + System.getProperty("java.class.path")); 44 } 45 } 46 47 /*** 48 * Create a ruleset from a name or from a list of name 49 * @param name name of rule set file loaded as a resource 50 * @param classLoader the classloader used to load the ruleset and subsequent rules 51 * @return the new ruleset 52 * @throws RuleSetNotFoundException 53 */ 54 public RuleSet createRuleSet(String name, ClassLoader classLoader) throws RuleSetNotFoundException { 55 RuleSet ruleSet = null; 56 setClassLoader(classLoader); 57 58 if (name.indexOf(',') == -1) { 59 ruleSet = createRuleSet(tryToGetStreamTo(name, classLoader)); 60 } else { 61 ruleSet = new RuleSet(); 62 for (StringTokenizer st = new StringTokenizer(name, ","); st.hasMoreTokens();) { 63 String ruleSetName = st.nextToken().trim(); 64 RuleSet tmpRuleSet = createRuleSet(ruleSetName, classLoader); 65 ruleSet.addRuleSet(tmpRuleSet); 66 } 67 } 68 69 return ruleSet; 70 } 71 72 /*** 73 * Creates a ruleset. If passed a comma-delimited string (rulesets/basic.xml,rulesets/unusedcode.xml) 74 * it will parse that string and create a new ruleset for each item in the list. 75 * Same as createRuleSet(name, ruleSetFactory.getClassLoader()). 76 */ 77 public RuleSet createRuleSet(String name) throws RuleSetNotFoundException { 78 return createRuleSet(name, getClass().getClassLoader()); 79 } 80 81 /*** 82 * Create a ruleset from an inputsteam. 83 * Same as createRuleSet(inputStream, ruleSetFactory.getClassLoader()). 84 * @param inputStream an input stream that contains a ruleset descripion 85 * @return a new ruleset 86 */ 87 public RuleSet createRuleSet(InputStream inputStream) { 88 return createRuleSet(inputStream, getClass().getClassLoader()); 89 } 90 91 /*** 92 * Create a ruleset from an input stream with a specified class loader 93 * @param inputStream an input stream that contains a ruleset descripion 94 * @param classLoader a class loader used to load rule classes 95 * @return a new ruleset 96 */ 97 public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) { 98 try { 99 setClassLoader(classLoader); 100 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 101 Document doc = builder.parse(inputStream); 102 Element root = doc.getDocumentElement(); 103 104 RuleSet ruleSet = new RuleSet(); 105 ruleSet.setName(root.getAttribute("name")); 106 107 NodeList nodeList = root.getChildNodes(); 108 for (int i = 0; i < nodeList.getLength(); i++) { 109 Node node = nodeList.item(i); 110 if (node.getNodeType() == Node.ELEMENT_NODE) { 111 if (node.getNodeName().equals("description")) { 112 parseDescriptionNode(ruleSet, node); 113 } else if (node.getNodeName().equals("rule")) { 114 parseRuleNode(ruleSet, node); 115 } 116 } 117 } 118 119 return ruleSet; 120 } catch (Exception e) { 121 e.printStackTrace(); 122 throw new RuntimeException("Couldn't read from that source: " + e.getMessage()); 123 } 124 } 125 126 /*** 127 * Return the class loader used to load ruleset resources and rules 128 * @return 129 */ 130 public ClassLoader getClassLoader() { 131 return classLoader; 132 } 133 134 /*** 135 * Sets the class loader used to load ruleset resources and rules 136 * @param loader a class loader 137 */ 138 public void setClassLoader(ClassLoader loader) { 139 classLoader = loader; 140 } 141 142 /*** 143 * Try to load a resource with the specified class loader 144 * @param name a resource name (contains a ruleset description) 145 * @param loader a class loader used to load that rule set description 146 * @return an inputstream to that resource 147 * @throws RuleSetNotFoundException 148 */ 149 private InputStream tryToGetStreamTo(String name, ClassLoader loader) throws RuleSetNotFoundException { 150 InputStream in = ResourceLoader.loadResourceAsStream(name, loader); 151 if (in == null) { 152 throw new RuleSetNotFoundException("Can't find resource " + name + ". Make sure the resource is a valid file or URL or is on the CLASSPATH"); 153 } 154 155 return in; 156 } 157 158 /*** 159 * Parse a ruleset description node 160 * @param ruleSet the ruleset being constructed 161 * @param descriptionNode must be a description element node 162 */ 163 private void parseDescriptionNode(RuleSet ruleSet, Node descriptionNode) { 164 NodeList nodeList = descriptionNode.getChildNodes(); 165 StringBuffer buffer = new StringBuffer(); 166 for (int i = 0 ; i < nodeList.getLength(); i++) { 167 Node node = nodeList.item(i); 168 if (node.getNodeType() == Node.TEXT_NODE) { 169 buffer.append(node.getNodeValue()); 170 } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) { 171 buffer.append(node.getNodeValue()); 172 } 173 } 174 ruleSet.setDescription(buffer.toString()); 175 } 176 177 /*** 178 * Parse a rule node 179 * @param ruleSet the ruleset being constructed 180 * @param ruleElement must be a rule element node 181 */ 182 private void parseRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException { 183 Element ruleElement = (Element) ruleNode; 184 String ref = ruleElement.getAttribute("ref"); 185 if (ref.trim().length() == 0) { 186 parseInternallyDefinedRuleNode(ruleSet, ruleNode); 187 } else { 188 parseExternallyDefinedRuleNode(ruleSet, ruleNode); 189 } 190 } 191 192 /*** 193 * Process a rule definition node 194 * @param ruleSet the ruleset being constructed 195 * @param ruleNode must be a rule element node 196 */ 197 private void parseInternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException { 198 Element ruleElement = (Element) ruleNode; 199 200 String className = ruleElement.getAttribute("class"); 201 String name = ruleElement.getAttribute("name"); 202 String message = ruleElement.getAttribute("message"); 203 Rule rule = (Rule) getClassLoader().loadClass(className).newInstance(); 204 rule.setName(name); 205 rule.setMessage(message); 206 207 NodeList nodeList = ruleElement.getChildNodes(); 208 for (int i = 0; i < nodeList.getLength(); i++) { 209 Node node = nodeList.item(i); 210 if (node.getNodeType() == Node.ELEMENT_NODE) { 211 if (node.getNodeName().equals("description")) { 212 parseDescriptionNode(rule, node); 213 } else if (node.getNodeName().equals("example")) { 214 parseExampleNode(rule, node); 215 } else if (node.getNodeName().equals("priority")) { 216 parsePriorityNode(rule, node); 217 } else if (node.getNodeName().equals("properties")) { 218 parsePropertiesNode(rule, node); 219 } 220 } 221 } 222 223 ruleSet.addRule(rule); 224 } 225 226 /*** 227 * Process a reference to a rule 228 * @param ruleSet the ruleset being constructucted 229 * @param ruleNode must be a ruke element node 230 */ 231 private void parseExternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws RuleSetNotFoundException { 232 Element ruleElement = (Element) ruleNode; 233 String ref = ruleElement.getAttribute("ref"); 234 if (ref.endsWith("xml")) { 235 parseRuleNodeWithExclude(ruleSet, ruleElement, ref); 236 } else { 237 parseRuleNodeWithSimpleReference(ruleSet, ref); 238 } 239 } 240 241 /*** 242 * Parse a rule node with a simple reference 243 * @param ruleSet the ruleset being constructed 244 * @param ref a reference to a rule 245 */ 246 private void parseRuleNodeWithSimpleReference(RuleSet ruleSet, String ref) throws RuleSetNotFoundException { 247 RuleSetFactory rsf = new RuleSetFactory(); 248 ExternalRuleID externalRuleID = new ExternalRuleID(ref); 249 RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(externalRuleID.getFilename())); 250 ruleSet.addRule(externalRuleSet.getRuleByName(externalRuleID.getRuleName())); 251 } 252 253 /*** 254 * Parse a reference rule node with excludes 255 * @param ruleSet the ruleset being constructed 256 * @param ruleElement must be a rule element 257 * @param ref the ruleset reference 258 */ 259 private void parseRuleNodeWithExclude(RuleSet ruleSet, Element ruleElement, String ref) throws RuleSetNotFoundException { 260 NodeList excludeNodes = ruleElement.getChildNodes(); 261 Set excludes = new HashSet(); 262 for (int i=0; i<excludeNodes.getLength(); i++) { 263 Node node = excludeNodes.item(i); 264 if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("exclude"))) { 265 Element excludeElement = (Element) node; 266 excludes.add(excludeElement.getAttribute("name")); 267 } 268 } 269 270 RuleSetFactory rsf = new RuleSetFactory(); 271 RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(ref)); 272 for (Iterator i = externalRuleSet.getRules().iterator(); i.hasNext();) { 273 Rule rule = (Rule) i.next(); 274 if (!excludes.contains(rule.getName())) { 275 ruleSet.addRule(rule); 276 } 277 } 278 } 279 280 /*** 281 * Process a rule descrtiprion node 282 * @param rule the rule being constructed 283 * @param descriptionNode must be a description element node 284 */ 285 private void parseDescriptionNode(Rule rule, Node descriptionNode) { 286 NodeList nodeList = descriptionNode.getChildNodes(); 287 StringBuffer buffer = new StringBuffer(); 288 for (int i = 0; i < nodeList.getLength(); i++) { 289 Node node = nodeList.item(i); 290 if (node.getNodeType() == Node.CDATA_SECTION_NODE) { 291 buffer.append(node.getNodeValue()); 292 } else if (node.getNodeType() == Node.TEXT_NODE) { 293 buffer.append(node.getNodeValue()); 294 } 295 } 296 rule.setDescription(buffer.toString()); 297 } 298 299 /*** 300 * Process a rule example node 301 * @param rule the rule being constructed 302 * @param exampleNode must be a example element node 303 */ 304 private void parseExampleNode(Rule rule, Node exampleNode) { 305 NodeList nodeList = exampleNode.getChildNodes(); 306 StringBuffer buffer = new StringBuffer(); 307 for (int i = 0; i < nodeList.getLength(); i++) { 308 Node node = nodeList.item(i); 309 if (node.getNodeType() == Node.CDATA_SECTION_NODE) { 310 buffer.append(node.getNodeValue()); 311 } else if (node.getNodeType() == Node.TEXT_NODE) { 312 buffer.append(node.getNodeValue()); 313 } 314 } 315 rule.setExample(buffer.toString()); 316 } 317 318 /*** 319 * Parse a priority node 320 * @param rule the rule being constructed 321 * @param priorityNode must be a priority element 322 */ 323 private void parsePriorityNode(Rule rule, Node priorityNode) { 324 StringBuffer buffer = new StringBuffer(); 325 NodeList nodeList = priorityNode.getChildNodes(); 326 for (int i = 0; i < nodeList.getLength(); i++) { 327 Node node = nodeList.item(i); 328 if (node.getNodeType() == Node.TEXT_NODE) { 329 buffer.append(node.getNodeValue()); 330 } 331 } 332 rule.setPriority(new Integer(buffer.toString().trim()).intValue()); 333 } 334 335 /*** 336 * Parse a properties node 337 * @param rule the rule being constructed 338 * @param propertiesNode must be a properties element node 339 */ 340 private void parsePropertiesNode(Rule rule, Node propertiesNode) { 341 NodeList nodeList = propertiesNode.getChildNodes(); 342 for (int i = 0; i < nodeList.getLength(); i++) { 343 Node node = nodeList.item(i); 344 if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("property"))) { 345 parsePropertyNode(rule, node); 346 } 347 } 348 } 349 350 /*** 351 * Parse a property node 352 * @param rule the rule being constructed 353 * @param propertyNode must be a property element node 354 */ 355 private void parsePropertyNode(Rule rule, Node propertyNode) { 356 Element propertyElement = (Element) propertyNode; 357 String name = propertyElement.getAttribute("name"); 358 String value = propertyElement.getAttribute("value"); 359 if (value.trim().length() == 0) { 360 NodeList nodeList = propertyNode.getChildNodes(); 361 for (int i = 0; i < nodeList.getLength(); i++) { 362 Node node = nodeList.item(i); 363 if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("value"))) { 364 value = parseValueNode(node); 365 } 366 } 367 } 368 if (propertyElement.hasAttribute("pluginname")) { 369 rule.addProperty("pluginname", propertyElement.getAttributeNode("pluginname").getNodeValue()); 370 } 371 rule.addProperty(name, value); 372 } 373 374 /*** 375 * Parse a value node 376 * @param valueNode must be a value element node 377 * @return the value 378 */ 379 private String parseValueNode(Node valueNode) { 380 StringBuffer buffer = new StringBuffer(); 381 NodeList nodeList = valueNode.getChildNodes(); 382 for (int i = 0; i < nodeList.getLength(); i++) { 383 Node node = nodeList.item(i); 384 if (node.getNodeType() == Node.CDATA_SECTION_NODE) { 385 buffer.append(node.getNodeValue()); 386 } else if (node.getNodeType() == Node.TEXT_NODE) { 387 buffer.append(node.getNodeValue()); 388 } 389 } 390 return buffer.toString(); 391 } 392 }