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