/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd;
import net.sourceforge.pmd.util.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
public class RuleSetFactory {
private ClassLoader classLoader;
/**
* Returns an Iterator of RuleSet objects loaded from descriptions from
* the "rulesets.properties" resource or from the "rulesets.filenames" property.
* @return an iterator on RuleSet objects
*/
public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException {
try {
Properties props = new Properties();
props.load(ResourceLoader.loadResourceAsStream("rulesets/rulesets.properties"));
String rulesetFilenames = props.getProperty("rulesets.filenames");
List ruleSets = new ArrayList();
for (StringTokenizer st = new StringTokenizer(rulesetFilenames, ","); st.hasMoreTokens();) {
ruleSets.add(createRuleSet(st.nextToken()));
}
return ruleSets.iterator();
} catch (IOException ioe) {
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"));
}
}
/**
* Create a ruleset from a name or from a list of name
* @param name name of rule set file loaded as a resource
* @param classLoader the classloader used to load the ruleset and subsequent rules
* @return the new ruleset
* @throws RuleSetNotFoundException
*/
public RuleSet createRuleSet(String name, ClassLoader classLoader) throws RuleSetNotFoundException {
RuleSet ruleSet = null;
setClassLoader(classLoader);
if (name.indexOf(',') == -1) {
ruleSet = createRuleSet(tryToGetStreamTo(name, classLoader));
} else {
ruleSet = new RuleSet();
for (StringTokenizer st = new StringTokenizer(name, ","); st.hasMoreTokens();) {
String ruleSetName = st.nextToken().trim();
RuleSet tmpRuleSet = createRuleSet(ruleSetName, classLoader);
ruleSet.addRuleSet(tmpRuleSet);
}
}
return ruleSet;
}
/**
* Creates a ruleset. If passed a comma-delimited string (rulesets/basic.xml,rulesets/unusedcode.xml)
* it will parse that string and create a new ruleset for each item in the list.
* Same as createRuleSet(name, ruleSetFactory.getClassLoader()).
*/
public RuleSet createRuleSet(String name) throws RuleSetNotFoundException {
return createRuleSet(name, getClass().getClassLoader());
}
/**
* Create a ruleset from an inputsteam.
* Same as createRuleSet(inputStream, ruleSetFactory.getClassLoader()).
* @param inputStream an input stream that contains a ruleset descripion
* @return a new ruleset
*/
public RuleSet createRuleSet(InputStream inputStream) {
return createRuleSet(inputStream, getClass().getClassLoader());
}
/**
* Create a ruleset from an input stream with a specified class loader
* @param inputStream an input stream that contains a ruleset descripion
* @param classLoader a class loader used to load rule classes
* @return a new ruleset
*/
public RuleSet createRuleSet(InputStream inputStream, ClassLoader classLoader) {
try {
setClassLoader(classLoader);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(inputStream);
Element root = doc.getDocumentElement();
RuleSet ruleSet = new RuleSet();
ruleSet.setName(root.getAttribute("name"));
NodeList nodeList = root.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (node.getNodeName().equals("description")) {
parseDescriptionNode(ruleSet, node);
} else if (node.getNodeName().equals("rule")) {
parseRuleNode(ruleSet, node);
}
}
}
return ruleSet;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Couldn't read from that source: " + e.getMessage());
}
}
/**
* Return the class loader used to load ruleset resources and rules
* @return
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Sets the class loader used to load ruleset resources and rules
* @param loader a class loader
*/
public void setClassLoader(ClassLoader loader) {
classLoader = loader;
}
/**
* Try to load a resource with the specified class loader
* @param name a resource name (contains a ruleset description)
* @param loader a class loader used to load that rule set description
* @return an inputstream to that resource
* @throws RuleSetNotFoundException
*/
private InputStream tryToGetStreamTo(String name, ClassLoader loader) throws RuleSetNotFoundException {
InputStream in = ResourceLoader.loadResourceAsStream(name, loader);
if (in == null) {
throw new RuleSetNotFoundException("Can't find resource " + name + ". Make sure the resource is a valid file or URL or is on the CLASSPATH");
}
return in;
}
/**
* Parse a ruleset description node
* @param ruleSet the ruleset being constructed
* @param descriptionNode must be a description element node
*/
private void parseDescriptionNode(RuleSet ruleSet, Node descriptionNode) {
NodeList nodeList = descriptionNode.getChildNodes();
StringBuffer buffer = new StringBuffer();
for (int i = 0 ; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.TEXT_NODE) {
buffer.append(node.getNodeValue());
} else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
buffer.append(node.getNodeValue());
}
}
ruleSet.setDescription(buffer.toString());
}
/**
* Parse a rule node
* @param ruleSet the ruleset being constructed
* @param ruleElement must be a rule element node
*/
private void parseRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException {
Element ruleElement = (Element) ruleNode;
String ref = ruleElement.getAttribute("ref");
if (ref.trim().length() == 0) {
parseInternallyDefinedRuleNode(ruleSet, ruleNode);
} else {
parseExternallyDefinedRuleNode(ruleSet, ruleNode);
}
}
/**
* Process a rule definition node
* @param ruleSet the ruleset being constructed
* @param ruleNode must be a rule element node
*/
private void parseInternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Element ruleElement = (Element) ruleNode;
String className = ruleElement.getAttribute("class");
String name = ruleElement.getAttribute("name");
String message = ruleElement.getAttribute("message");
Rule rule = (Rule) getClassLoader().loadClass(className).newInstance();
rule.setName(name);
rule.setMessage(message);
NodeList nodeList = ruleElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (node.getNodeName().equals("description")) {
parseDescriptionNode(rule, node);
} else if (node.getNodeName().equals("example")) {
parseExampleNode(rule, node);
} else if (node.getNodeName().equals("priority")) {
parsePriorityNode(rule, node);
} else if (node.getNodeName().equals("properties")) {
parsePropertiesNode(rule, node);
}
}
}
ruleSet.addRule(rule);
}
/**
* Process a reference to a rule
* @param ruleSet the ruleset being constructucted
* @param ruleNode must be a ruke element node
*/
private void parseExternallyDefinedRuleNode(RuleSet ruleSet, Node ruleNode) throws RuleSetNotFoundException {
Element ruleElement = (Element) ruleNode;
String ref = ruleElement.getAttribute("ref");
if (ref.endsWith("xml")) {
parseRuleNodeWithExclude(ruleSet, ruleElement, ref);
} else {
parseRuleNodeWithSimpleReference(ruleSet, ref);
}
}
/**
* Parse a rule node with a simple reference
* @param ruleSet the ruleset being constructed
* @param ref a reference to a rule
*/
private void parseRuleNodeWithSimpleReference(RuleSet ruleSet, String ref) throws RuleSetNotFoundException {
RuleSetFactory rsf = new RuleSetFactory();
ExternalRuleID externalRuleID = new ExternalRuleID(ref);
RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(externalRuleID.getFilename()));
ruleSet.addRule(externalRuleSet.getRuleByName(externalRuleID.getRuleName()));
}
/**
* Parse a reference rule node with excludes
* @param ruleSet the ruleset being constructed
* @param ruleElement must be a rule element
* @param ref the ruleset reference
*/
private void parseRuleNodeWithExclude(RuleSet ruleSet, Element ruleElement, String ref) throws RuleSetNotFoundException {
NodeList excludeNodes = ruleElement.getChildNodes();
Set excludes = new HashSet();
for (int i=0; i<excludeNodes.getLength(); i++) {
Node node = excludeNodes.item(i);
if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("exclude"))) {
Element excludeElement = (Element) node;
excludes.add(excludeElement.getAttribute("name"));
}
}
RuleSetFactory rsf = new RuleSetFactory();
RuleSet externalRuleSet = rsf.createRuleSet(ResourceLoader.loadResourceAsStream(ref));
for (Iterator i = externalRuleSet.getRules().iterator(); i.hasNext();) {
Rule rule = (Rule) i.next();
if (!excludes.contains(rule.getName())) {
ruleSet.addRule(rule);
}
}
}
/**
* Process a rule descrtiprion node
* @param rule the rule being constructed
* @param descriptionNode must be a description element node
*/
private void parseDescriptionNode(Rule rule, Node descriptionNode) {
NodeList nodeList = descriptionNode.getChildNodes();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
buffer.append(node.getNodeValue());
} else if (node.getNodeType() == Node.TEXT_NODE) {
buffer.append(node.getNodeValue());
}
}
rule.setDescription(buffer.toString());
}
/**
* Process a rule example node
* @param rule the rule being constructed
* @param exampleNode must be a example element node
*/
private void parseExampleNode(Rule rule, Node exampleNode) {
NodeList nodeList = exampleNode.getChildNodes();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
buffer.append(node.getNodeValue());
} else if (node.getNodeType() == Node.TEXT_NODE) {
buffer.append(node.getNodeValue());
}
}
rule.setExample(buffer.toString());
}
/**
* Parse a priority node
* @param rule the rule being constructed
* @param priorityNode must be a priority element
*/
private void parsePriorityNode(Rule rule, Node priorityNode) {
StringBuffer buffer = new StringBuffer();
NodeList nodeList = priorityNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.TEXT_NODE) {
buffer.append(node.getNodeValue());
}
}
rule.setPriority(new Integer(buffer.toString().trim()).intValue());
}
/**
* Parse a properties node
* @param rule the rule being constructed
* @param propertiesNode must be a properties element node
*/
private void parsePropertiesNode(Rule rule, Node propertiesNode) {
NodeList nodeList = propertiesNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("property"))) {
parsePropertyNode(rule, node);
}
}
}
/**
* Parse a property node
* @param rule the rule being constructed
* @param propertyNode must be a property element node
*/
private void parsePropertyNode(Rule rule, Node propertyNode) {
Element propertyElement = (Element) propertyNode;
String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
if (value.trim().length() == 0) {
NodeList nodeList = propertyNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getNodeName().equals("value"))) {
value = parseValueNode(node);
}
}
}
if (propertyElement.hasAttribute("pluginname")) {
rule.addProperty("pluginname", propertyElement.getAttributeNode("pluginname").getNodeValue());
}
rule.addProperty(name, value);
}
/**
* Parse a value node
* @param valueNode must be a value element node
* @return the value
*/
private String parseValueNode(Node valueNode) {
StringBuffer buffer = new StringBuffer();
NodeList nodeList = valueNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
buffer.append(node.getNodeValue());
} else if (node.getNodeType() == Node.TEXT_NODE) {
buffer.append(node.getNodeValue());
}
}
return buffer.toString();
}
}