RuleSetFactory.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd;
005 
006 import java.io.IOException;
007 import java.io.InputStream;
008 import java.util.ArrayList;
009 import java.util.Iterator;
010 import java.util.List;
011 import java.util.Properties;
012 import java.util.logging.Logger;
013 
014 import javax.xml.parsers.DocumentBuilder;
015 import javax.xml.parsers.DocumentBuilderFactory;
016 import javax.xml.parsers.ParserConfigurationException;
017 
018 import net.sourceforge.pmd.lang.Language;
019 import net.sourceforge.pmd.lang.LanguageVersion;
020 import net.sourceforge.pmd.lang.rule.MockRule;
021 import net.sourceforge.pmd.lang.rule.RuleReference;
022 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorFactory;
023 import net.sourceforge.pmd.util.ResourceLoader;
024 import net.sourceforge.pmd.util.StringUtil;
025 
026 import org.w3c.dom.Document;
027 import org.w3c.dom.Element;
028 import org.w3c.dom.Node;
029 import org.w3c.dom.NodeList;
030 import org.xml.sax.SAXException;
031 
032 /**
033  * RuleSetFactory is responsible for creating RuleSet instances from XML content.
034  * By default Rules will be loaded using the ClassLoader for this class, using
035  * the {@link RulePriority#LOW} priority, with Rule deprecation warnings off.
036  */
037 public class RuleSetFactory {
038 
039     private static final Logger LOG = Logger.getLogger(RuleSetFactory.class.getName());
040 
041     private ClassLoader classLoader = RuleSetFactory.class.getClassLoader();
042     private RulePriority minimumPriority = RulePriority.LOW;
043     private boolean warnDeprecated = false;
044 
045     /**
046      * Set the ClassLoader to use when loading Rules.
047      *
048      @param classLoader The ClassLoader to use.
049      */
050     public void setClassLoader(ClassLoader classLoader) {
051   this.classLoader = classLoader;
052     }
053 
054     /**
055      * Set the minimum rule priority threshold for all Rules which are loaded
056      * from RuleSets via reference.
057      
058      @param minimumPriority The minimum priority.
059      */
060     public void setMinimumPriority(RulePriority minimumPriority) {
061   this.minimumPriority = minimumPriority;
062     }
063 
064     /**
065      * Set whether warning messages should be logged for usage of deprecated Rules.
066      @param warnDeprecated <code>true</code> to log warning messages.
067      */
068     public void setWarnDeprecated(boolean warnDeprecated) {
069   this.warnDeprecated = warnDeprecated;
070     }
071 
072     /**
073      * Returns an Iterator of RuleSet objects loaded from descriptions from the
074      * "rulesets.properties" resource for each Language with Rule support.
075      *
076      @return An Iterator of RuleSet objects.
077      */
078     public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException {
079   String rulesetsProperties = null;
080   try {
081       List<RuleSetReferenceId> ruleSetReferenceIds = new ArrayList<RuleSetReferenceId>();
082       for (Language language : Language.findWithRuleSupport()) {
083     Properties props = new Properties();
084     rulesetsProperties = "rulesets/" + language.getTerseName() "/rulesets.properties";
085     props.load(ResourceLoader.loadResourceAsStream(rulesetsProperties));
086     String rulesetFilenames = props.getProperty("rulesets.filenames");
087     ruleSetReferenceIds.addAll(RuleSetReferenceId.parse(rulesetFilenames));
088       }
089       return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator();
090   catch (IOException ioe) {
091       throw new RuntimeException("Couldn't find " + rulesetsProperties
092         "; please ensure that the rulesets directory is on the classpath.  The current classpath is: "
093         + System.getProperty("java.class.path"));
094   }
095     }
096 
097     /**
098      * Create a RuleSets from a comma separated list of RuleSet reference IDs.  This is a
099      * convenience method which calls {@link RuleSetReferenceId#parse(String)}, and then calls
100      {@link #createRuleSets(List)}.
101      * The currently configured ClassLoader is used.
102      *
103      @param referenceString A comma separated list of RuleSet reference IDs.
104      @return The new RuleSets.
105      @throws RuleSetNotFoundException if unable to find a resource.
106      */
107     public synchronized RuleSets createRuleSets(String referenceStringthrows RuleSetNotFoundException {
108   return createRuleSets(RuleSetReferenceId.parse(referenceString));
109     }
110 
111     /**
112      * Create a RuleSets from a list of RuleSetReferenceIds.
113      * The currently configured ClassLoader is used.
114      *
115      @param ruleSetReferenceIds The List of RuleSetReferenceId of the RuleSets to create.
116      @return The new RuleSets.
117      @throws RuleSetNotFoundException if unable to find a resource.
118      */
119     public synchronized RuleSets createRuleSets(List<RuleSetReferenceId> ruleSetReferenceIds)
120       throws RuleSetNotFoundException {
121   RuleSets ruleSets = new RuleSets();
122   for (RuleSetReferenceId ruleSetReferenceId : ruleSetReferenceIds) {
123       RuleSet ruleSet = createRuleSet(ruleSetReferenceId);
124       ruleSets.addRuleSet(ruleSet);
125   }
126   return ruleSets;
127     }
128 
129     /**
130      * Create a RuleSet from a RuleSet reference ID string.  This is a
131      * convenience method which calls {@link RuleSetReferenceId#parse(String)}, gets the first
132      * item in the List, and then calls {@link #createRuleSet(RuleSetReferenceId)}.
133      * The currently configured ClassLoader is used.
134      *
135      @param referenceString A comma separated list of RuleSet reference IDs.
136      @return A new RuleSet.
137      @throws RuleSetNotFoundException if unable to find a resource.
138      */
139     public synchronized RuleSet createRuleSet(String referenceStringthrows RuleSetNotFoundException {
140   List<RuleSetReferenceId> references = RuleSetReferenceId.parse(referenceString);
141   if (references.isEmpty()) {
142       throw new RuleSetNotFoundException("No RuleSetReferenceId can be parsed from the string: <"
143         + referenceString + ">");
144   }
145   return createRuleSet(references.get(0));
146     }
147 
148     /**
149      * Create a RuleSet from a RuleSetReferenceId.  Priority filtering is ignored when loading
150      * a single Rule.
151      * The currently configured ClassLoader is used.
152      *
153      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet to create.
154      @return A new RuleSet.
155      @throws RuleSetNotFoundException if unable to find a resource.
156      */
157     public synchronized RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceIdthrows RuleSetNotFoundException {
158   return parseRuleSetNode(ruleSetReferenceId, ruleSetReferenceId.getInputStream(this.classLoader));
159     }
160 
161     /**
162      * Create a Rule from a RuleSet created from a file name resource.
163      * The currently configured ClassLoader is used.
164      <p>
165      * Any Rules in the RuleSet other than the one being created, are _not_ created.
166      *
167      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet with the Rule to create.
168      @return A new Rule.
169      @throws RuleSetNotFoundException if unable to find a resource.
170      */
171     private Rule createRule(RuleSetReferenceId ruleSetReferenceIdthrows RuleSetNotFoundException {
172   if (ruleSetReferenceId.isAllRules()) {
173       throw new IllegalArgumentException("Cannot parse a single Rule from an all Rule RuleSet reference: <"
174         + ruleSetReferenceId + ">.");
175   }
176   RuleSet ruleSet = createRuleSet(ruleSetReferenceId);
177   return ruleSet.getRuleByName(ruleSetReferenceId.getRuleName());
178     }
179 
180     /**
181      * Parse a ruleset node to construct a RuleSet.
182      
183      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
184      @param inputStream InputStream containing the RuleSet XML configuration.
185      @return The new RuleSet.
186      */
187     private RuleSet parseRuleSetNode(RuleSetReferenceId ruleSetReferenceId, InputStream inputStream) {
188   if (!ruleSetReferenceId.isExternal()) {
189       throw new IllegalArgumentException("Cannot parse a RuleSet from a non-external reference: <"
190         + ruleSetReferenceId + ">.");
191   }
192   try {
193       DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
194       Document document = builder.parse(inputStream);
195       Element ruleSetElement = document.getDocumentElement();
196 
197       RuleSet ruleSet = new RuleSet();
198       ruleSet.setFileName(ruleSetReferenceId.getRuleSetFileName());
199       ruleSet.setName(ruleSetElement.getAttribute("name"));
200 
201       NodeList nodeList = ruleSetElement.getChildNodes();
202       for (int i = 0; i < nodeList.getLength(); i++) {
203     Node node = nodeList.item(i);
204     if (node.getNodeType() == Node.ELEMENT_NODE) {
205         if (node.getNodeName().equals("description")) {
206       ruleSet.setDescription(parseTextNode(node));
207         else if (node.getNodeName().equals("include-pattern")) {
208       ruleSet.addIncludePattern(parseTextNode(node));
209         else if (node.getNodeName().equals("exclude-pattern")) {
210       ruleSet.addExcludePattern(parseTextNode(node));
211         else if (node.getNodeName().equals("rule")) {
212       parseRuleNode(ruleSetReferenceId, ruleSet, node);
213         else {
214       throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
215         "> encountered as child of <ruleset> element.");
216         }
217     }
218       }
219 
220       return ruleSet;
221   catch (ClassNotFoundException cnfe) {
222       return classNotFoundProblem(cnfe);
223   catch (InstantiationException ie) {
224       return classNotFoundProblem(ie);
225   catch (IllegalAccessException iae) {
226       return classNotFoundProblem(iae);
227   catch (ParserConfigurationException pce) {
228       return classNotFoundProblem(pce);
229   catch (RuleSetNotFoundException rsnfe) {
230       return classNotFoundProblem(rsnfe);
231   catch (IOException ioe) {
232       return classNotFoundProblem(ioe);
233   catch (SAXException se) {
234       return classNotFoundProblem(se);
235   }
236     }
237 
238     private static RuleSet classNotFoundProblem(Exception exthrows RuntimeException {
239   ex.printStackTrace();
240   throw new RuntimeException("Couldn't find the class " + ex.getMessage());
241     }
242 
243     /**
244      * Parse a rule node.
245      *
246      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
247      @param ruleSet The RuleSet being constructed.
248      @param ruleNode Must be a rule element node.
249      */
250     private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
251       throws ClassNotFoundException, InstantiationException, IllegalAccessException, RuleSetNotFoundException {
252   Element ruleElement = (ElementruleNode;
253   String ref = ruleElement.getAttribute("ref");
254   if (ref.endsWith("xml")) {
255       parseRuleSetReferenceNode(ruleSetReferenceId, ruleSet, ruleElement, ref);
256   else if (StringUtil.isEmpty(ref)) {
257       parseSingleRuleNode(ruleSetReferenceId, ruleSet, ruleNode);
258   else {
259       parseRuleReferenceNode(ruleSetReferenceId, ruleSet, ruleNode, ref);
260   }
261     }
262 
263     /**
264      * Parse a rule node as an RuleSetReference for all Rules.  Every Rule from
265      * the referred to RuleSet will be added as a RuleReference except for those
266      * explicitly excluded, below the minimum priority threshold for this
267      * RuleSetFactory, or which are deprecated.
268      *
269      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
270      @param ruleSet The RuleSet being constructed.
271      @param ruleElement Must be a rule element node.
272      @param ref The RuleSet reference.
273      */
274     private void parseRuleSetReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Element ruleElement,
275       String refthrows RuleSetNotFoundException {
276   RuleSetReference ruleSetReference = new RuleSetReference();
277   ruleSetReference.setAllRules(true);
278   ruleSetReference.setRuleSetFileName(ref);
279   NodeList excludeNodes = ruleElement.getChildNodes();
280   for (int i = 0; i < excludeNodes.getLength(); i++) {
281       if ((excludeNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
282         && (excludeNodes.item(i).getNodeName().equals("exclude"))) {
283     Element excludeElement = (ElementexcludeNodes.item(i);
284     ruleSetReference.addExclude(excludeElement.getAttribute("name"));
285       }
286   }
287 
288   RuleSetFactory ruleSetFactory = new RuleSetFactory();
289   ruleSetFactory.setClassLoader(this.classLoader);
290   RuleSet otherRuleSet = ruleSetFactory.createRuleSet(RuleSetReferenceId.parse(ref).get(0));
291   for (Rule rule : otherRuleSet.getRules()) {
292       if (!ruleSetReference.getExcludes().contains(rule.getName())
293         && rule.getPriority().compareTo(minimumPriority<= && !rule.isDeprecated()) {
294     RuleReference ruleReference = new RuleReference();
295     ruleReference.setRuleSetReference(ruleSetReference);
296     ruleReference.setRule(rule);
297     ruleSet.addRule(ruleReference);
298       }
299   }
300     }
301 
302     /**
303      * Parse a rule node as a single Rule.  The Rule has been fully defined within
304      * the context of the current RuleSet.
305      *
306      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
307      @param ruleSet The RuleSet being constructed.
308      @param ruleNode Must be a rule element node.
309      */
310     private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode)
311       throws ClassNotFoundException, InstantiationException, IllegalAccessException {
312   Element ruleElement = (ElementruleNode;
313 
314   // Stop if we're looking for a particular Rule, and this element is not it.
315   if (!StringUtil.isEmpty(ruleSetReferenceId.getRuleName())
316     && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
317       return;
318   }
319 
320   String attribute = ruleElement.getAttribute("class");
321   Class<?> c = classLoader.loadClass(attribute);
322   Rule rule = (Rulec.newInstance();
323 
324   rule.setName(ruleElement.getAttribute("name"));
325 
326   if (ruleElement.hasAttribute("language")) {
327       String languageName = ruleElement.getAttribute("language");
328       Language language = Language.findByTerseName(languageName);
329       if (language == null) {
330     throw new IllegalArgumentException("Unknown Language '" + languageName + "' for Rule " + rule.getName()
331       ", supported Languages are "
332       + Language.commaSeparatedTerseNames(Language.findWithRuleSupport()));
333       }
334       rule.setLanguage(language);
335   }
336 
337   Language language = rule.getLanguage();
338   if (language == null) {
339       throw new IllegalArgumentException("Rule " + rule.getName()
340         " does not have a Language; missing 'language' attribute?");
341   }
342 
343   if (ruleElement.hasAttribute("minimumLanguageVersion")) {
344       String minimumLanguageVersionName = ruleElement.getAttribute("minimumLanguageVersion");
345       LanguageVersion minimumLanguageVersion = language.getVersion(minimumLanguageVersionName);
346       if (minimumLanguageVersion == null) {
347     throw new IllegalArgumentException("Unknown minimum Language Version '" + minimumLanguageVersionName
348       "' for Language '" + language.getTerseName() "' for Rule " + rule.getName()
349       "; supported Language Versions are: "
350       + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
351       }
352       rule.setMinimumLanguageVersion(minimumLanguageVersion);
353   }
354 
355   if (ruleElement.hasAttribute("maximumLanguageVersion")) {
356       String maximumLanguageVersionName = ruleElement.getAttribute("maximumLanguageVersion");
357       LanguageVersion maximumLanguageVersion = language.getVersion(maximumLanguageVersionName);
358       if (maximumLanguageVersion == null) {
359     throw new IllegalArgumentException("Unknown maximum Language Version '" + maximumLanguageVersionName
360       "' for Language '" + language.getTerseName() "' for Rule " + rule.getName()
361       "; supported Language Versions are: "
362       + LanguageVersion.commaSeparatedTerseNames(language.getVersions()));
363       }
364       rule.setMaximumLanguageVersion(maximumLanguageVersion);
365   }
366 
367   if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null) {
368       throw new IllegalArgumentException("The minimum Language Version '"
369         + rule.getMinimumLanguageVersion().getTerseName()
370         "' must be prior to the maximum Language Version '"
371         + rule.getMaximumLanguageVersion().getTerseName() "' for Rule " + rule.getName()
372         "; perhaps swap them around?");
373   }
374 
375   String since = ruleElement.getAttribute("since");
376   if (since.length() 0) {
377       rule.setSince(since);
378   }
379   rule.setMessage(ruleElement.getAttribute("message"));
380   rule.setRuleSetName(ruleSet.getName());
381   rule.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
382 
383   if (ruleElement.hasAttribute("dfa"&& ruleElement.getAttribute("dfa").equals("true")) {
384       rule.setUsesDFA();
385   }
386 
387   if (ruleElement.hasAttribute("typeResolution"&& ruleElement.getAttribute("typeResolution").equals("true")) {
388       rule.setUsesTypeResolution();
389   }
390 
391   for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
392       Node node = ruleElement.getChildNodes().item(i);
393       if (node.getNodeType() == Node.ELEMENT_NODE) {
394     if (node.getNodeName().equals("description")) {
395         rule.setDescription(parseTextNode(node));
396     else if (node.getNodeName().equals("example")) {
397         rule.addExample(parseTextNode(node));
398     else if (node.getNodeName().equals("priority")) {
399         rule.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node).trim())));
400     else if (node.getNodeName().equals("properties")) {
401         parsePropertiesNode(rule, node);
402     else {
403         throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
404           "> encountered as child of <rule> element for Rule " + rule.getName());
405     }
406       }
407   }
408   if (!StringUtil.isEmpty(ruleSetReferenceId.getRuleName()) || rule.getPriority().compareTo(minimumPriority<= 0) {
409       ruleSet.addRule(rule);
410   }
411     }
412 
413     /**
414      * Parse a rule node as a RuleReference.  A RuleReference is a single Rule
415      * which comes from another RuleSet with some of it's attributes potentially
416      * overridden.
417      *
418      @param ruleSetReferenceId The RuleSetReferenceId of the RuleSet being parsed.
419      @param ruleSet The RuleSet being constructed.
420      @param ruleNode Must be a rule element node.
421      @param ref A reference to a Rule.
422      */
423     private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSet ruleSet, Node ruleNode,
424       String refthrows RuleSetNotFoundException {
425   Element ruleElement = (ElementruleNode;
426 
427   // Stop if we're looking for a particular Rule, and this element is not it.
428   if (!StringUtil.isEmpty(ruleSetReferenceId.getRuleName())
429     && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) {
430       return;
431   }
432 
433   RuleSetFactory ruleSetFactory = new RuleSetFactory();
434   ruleSetFactory.setClassLoader(this.classLoader);
435 
436   RuleSetReferenceId otherRuleSetReferenceId = RuleSetReferenceId.parse(ref).get(0);
437   if (!otherRuleSetReferenceId.isExternal()) {
438       otherRuleSetReferenceId = new RuleSetReferenceId(ref, ruleSetReferenceId);
439   }
440   Rule referencedRule = ruleSetFactory.createRule(otherRuleSetReferenceId);
441   if (referencedRule == null) {
442       throw new IllegalArgumentException("Unable to find referenced rule "
443         + otherRuleSetReferenceId.getRuleName() "; perhaps the rule name is mispelled?");
444   }
445 
446   if (warnDeprecated && referencedRule.isDeprecated()) {
447       if (referencedRule instanceof RuleReference) {
448     RuleReference ruleReference = (RuleReferencereferencedRule;
449     LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() "/"
450       + ruleReference.getName() " instead of the deprecated Rule name " + otherRuleSetReferenceId
451       ". Future versions of PMD will remove support for this deprecated Rule name usage.");
452       else if (referencedRule instanceof MockRule) {
453     LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
454       " as it has been removed from PMD and no longer functions."
455       " Future versions of PMD will remove support for this Rule.");
456       else {
457     LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId
458       " as it is scheduled for removal from PMD."
459       " Future versions of PMD will remove support for this Rule.");
460       }
461   }
462 
463   RuleSetReference ruleSetReference = new RuleSetReference();
464   ruleSetReference.setAllRules(false);
465   ruleSetReference.setRuleSetFileName(otherRuleSetReferenceId.getRuleSetFileName());
466 
467   RuleReference ruleReference = new RuleReference();
468   ruleReference.setRuleSetReference(ruleSetReference);
469   ruleReference.setRule(referencedRule);
470 
471   if (ruleElement.hasAttribute("deprecated")) {
472       ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated")));
473   }
474   if (ruleElement.hasAttribute("name")) {
475       ruleReference.setName(ruleElement.getAttribute("name"));
476   }
477   if (ruleElement.hasAttribute("message")) {
478       ruleReference.setMessage(ruleElement.getAttribute("message"));
479   }
480   if (ruleElement.hasAttribute("externalInfoUrl")) {
481       ruleReference.setExternalInfoUrl(ruleElement.getAttribute("externalInfoUrl"));
482   }
483   for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) {
484       Node node = ruleElement.getChildNodes().item(i);
485       if (node.getNodeType() == Node.ELEMENT_NODE) {
486     if (node.getNodeName().equals("description")) {
487         ruleReference.setDescription(parseTextNode(node));
488     else if (node.getNodeName().equals("example")) {
489         ruleReference.addExample(parseTextNode(node));
490     else if (node.getNodeName().equals("priority")) {
491         ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node))));
492     else if (node.getNodeName().equals("properties")) {
493         parsePropertiesNode(ruleReference, node);
494     else {
495         throw new IllegalArgumentException("Unexpected element <" + node.getNodeName()
496           "> encountered as child of <rule> element for Rule " + ruleReference.getName());
497     }
498       }
499   }
500 
501   if (!StringUtil.isEmpty(ruleSetReferenceId.getRuleName())
502     || referencedRule.getPriority().compareTo(minimumPriority<= 0) {
503       ruleSet.addRule(ruleReference);
504   }
505     }
506 
507     /**
508      * Parse a properties node.
509      *
510      @param rule The Rule to which the properties should be added. 
511      @param propertiesNode Must be a properties element node.
512      */
513     private static void parsePropertiesNode(Rule rule, Node propertiesNode) {
514   for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) {
515       Node node = propertiesNode.getChildNodes().item(i);
516       if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("property")) {
517     parsePropertyNode(rule, node);
518       }
519   }
520     }
521 
522     /**
523      * Parse a property node.
524      *
525      @param rule The Rule to which the property should be added. 
526      @param propertyNode Must be a property element node.
527      */
528     @SuppressWarnings("unchecked")
529     private static void parsePropertyNode(Rule rule, Node propertyNode) {
530   Element propertyElement = (ElementpropertyNode;
531   String name = propertyElement.getAttribute("name");
532   String description = propertyElement.getAttribute("description");
533   String type = propertyElement.getAttribute("type");
534   String delimiter = propertyElement.getAttribute("delimiter");
535   String min = propertyElement.getAttribute("min");
536   String max = propertyElement.getAttribute("max");
537   String value = propertyElement.getAttribute("value");
538 
539   // If value not provided, get from child <value> element.
540   if (StringUtil.isEmpty(value)) {
541       for (int i = 0; i < propertyNode.getChildNodes().getLength(); i++) {
542     Node node = propertyNode.getChildNodes().item(i);
543     if ((node.getNodeType() == Node.ELEMENT_NODE&& node.getNodeName().equals("value")) {
544         value = parseTextNode(node);
545     }
546       }
547   }
548 
549   // Setting of existing property, or defining a new property?
550   if (StringUtil.isEmpty(type)) {
551       PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(name);
552       if (propertyDescriptor == null) {
553     throw new IllegalArgumentException("Cannot set non-existant property '" + name + "' on Rule "
554       + rule.getName());
555       else {
556     Object realValue = propertyDescriptor.valueFrom(value);
557     rule.setProperty(propertyDescriptor, realValue);
558       }
559   else {
560       PropertyDescriptor propertyDescriptor = PropertyDescriptorFactory.createPropertyDescriptor(name,
561         description, type, delimiter, min, max, value);
562       rule.definePropertyDescriptor(propertyDescriptor);
563   }
564     }
565 
566     /**
567      * Parse a String from a textually type node.
568      *
569      @param node The node.
570      @return The String.
571      */
572     private static String parseTextNode(Node node) {
573 
574   final int nodeCount = node.getChildNodes().getLength();
575   if (nodeCount == 0) {
576       return "";
577   }
578 
579   StringBuilder buffer = new StringBuilder();
580 
581   for (int i = 0; i < nodeCount; i++) {
582       Node childNode = node.getChildNodes().item(i);
583       if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) {
584     buffer.append(childNode.getNodeValue());
585       }
586   }
587   return buffer.toString();
588     }
589 
590     /**
591      * Determine if the specified rule element will represent a Rule with the given name. 
592      @param ruleElement The rule element.
593      @param ruleName The Rule name.
594      @return <code>true</code> if the Rule would have the given name, <code>false<code> otherwise.
595      */
596     private boolean isRuleName(Element ruleElement, String ruleName) {
597   if (ruleElement.hasAttribute("name")) {
598       return ruleElement.getAttribute("name").equals(ruleName);
599   else if (ruleElement.hasAttribute("ref")) {
600       RuleSetReferenceId ruleSetReferenceId = RuleSetReferenceId.parse(ruleElement.getAttribute("ref")).get(0);
601       return ruleSetReferenceId.getRuleName() != null && ruleSetReferenceId.getRuleName().equals(ruleName);
602   else {
603       return false;
604   }
605     }
606 }