RuleSetWriter.java
001 package net.sourceforge.pmd;
002 
003 import java.io.IOException;
004 import java.io.OutputStream;
005 import java.util.HashSet;
006 import java.util.List;
007 import java.util.Map;
008 import java.util.Set;
009 
010 import javax.xml.parsers.DocumentBuilder;
011 import javax.xml.parsers.DocumentBuilderFactory;
012 import javax.xml.parsers.FactoryConfigurationError;
013 import javax.xml.parsers.ParserConfigurationException;
014 import javax.xml.transform.OutputKeys;
015 import javax.xml.transform.Transformer;
016 import javax.xml.transform.TransformerException;
017 import javax.xml.transform.TransformerFactory;
018 import javax.xml.transform.dom.DOMSource;
019 import javax.xml.transform.stream.StreamResult;
020 
021 import net.sourceforge.pmd.lang.Language;
022 import net.sourceforge.pmd.lang.LanguageVersion;
023 import net.sourceforge.pmd.lang.rule.ImmutableLanguage;
024 import net.sourceforge.pmd.lang.rule.RuleReference;
025 import net.sourceforge.pmd.lang.rule.XPathRule;
026 import net.sourceforge.pmd.lang.rule.properties.AbstractNumericProperty;
027 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorFactory;
028 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
029 
030 import org.w3c.dom.CDATASection;
031 import org.w3c.dom.DOMException;
032 import org.w3c.dom.Document;
033 import org.w3c.dom.Element;
034 import org.w3c.dom.Text;
035 
036 /**
037  * This class represents a way to serialize a RuleSet to an XML configuration file.
038  */
039 public class RuleSetWriter {
040     private final OutputStream outputStream;
041     private Document document;
042     private Set<String> ruleSetFileNames;
043 
044     public RuleSetWriter(OutputStream outputStream) {
045   this.outputStream = outputStream;
046     }
047 
048     public void close() throws IOException {
049   outputStream.flush();
050   outputStream.close();
051     }
052 
053     public void write(RuleSet ruleSet) {
054   try {
055       DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
056       DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
057       this.document = documentBuilder.newDocument();
058       this.ruleSetFileNames = new HashSet<String>();
059 
060       Element ruleSetElement = createRuleSetElement(ruleSet);
061       document.appendChild(ruleSetElement);
062 
063       TransformerFactory transformerFactory = TransformerFactory.newInstance();
064       transformerFactory.setAttribute("indent-number"3);
065       Transformer transformer = transformerFactory.newTransformer();
066       transformer.setOutputProperty(OutputKeys.METHOD, "xml");
067       // This is as close to pretty printing as we'll get using standard Java APIs.
068       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
069       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
070       transformer.transform(new DOMSource(document)new StreamResult(outputStream));
071   catch (DOMException e) {
072       throw new RuntimeException(e);
073   catch (FactoryConfigurationError e) {
074       throw new RuntimeException(e);
075   catch (ParserConfigurationException e) {
076       throw new RuntimeException(e);
077   catch (TransformerException e) {
078       throw new RuntimeException(e);
079   }
080     }
081 
082     private Element createRuleSetElement(RuleSet ruleSet) {
083   Element ruleSetElement = document.createElement("ruleset");
084   ruleSetElement.setAttribute("xmlns""http://pmd.sf.net/ruleset/1.0.0");
085   ruleSetElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance""xsi:schemaLocation",
086     "http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd");
087   ruleSetElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance""xsi:noNamespaceSchemaLocation",
088     "http://pmd.sf.net/ruleset_xml_schema.xsd");
089   ruleSetElement.setAttribute("name", ruleSet.getName());
090 
091   Element descriptionElement = createDescriptionElement(ruleSet.getDescription());
092   ruleSetElement.appendChild(descriptionElement);
093 
094   for (String excludePattern : ruleSet.getExcludePatterns()) {
095       Element excludePatternElement = createExcludePatternElement(excludePattern);
096       ruleSetElement.appendChild(excludePatternElement);
097   }
098   for (String includePattern : ruleSet.getIncludePatterns()) {
099       Element includePatternElement = createIncludePatternElement(includePattern);
100       ruleSetElement.appendChild(includePatternElement);
101   }
102   for (Rule rule : ruleSet.getRules()) {
103       Element ruleElement = createRuleElement(rule);
104       if (ruleElement != null) {
105     ruleSetElement.appendChild(ruleElement);
106       }
107   }
108 
109   return ruleSetElement;
110     }
111 
112     private Element createDescriptionElement(String description) {
113   return createTextElement("description", description);
114     }
115 
116     private Element createExcludePatternElement(String excludePattern) {
117   return createTextElement("exclude-pattern", excludePattern);
118     }
119 
120     private Element createIncludePatternElement(String includePattern) {
121   return createTextElement("include-pattern", includePattern);
122     }
123 
124     private Element createRuleElement(Rule rule) {
125   if (rule instanceof RuleReference) {
126       RuleReference ruleReference = (RuleReferencerule;
127       RuleSetReference ruleSetReference = ruleReference.getRuleSetReference();
128       if (ruleSetReference.isAllRules()) {
129     if (!ruleSetFileNames.contains(ruleSetReference.getRuleSetFileName())) {
130         ruleSetFileNames.add(ruleSetReference.getRuleSetFileName());
131         Element ruleSetReferenceElement = createRuleSetReferenceElement(ruleSetReference);
132         return ruleSetReferenceElement;
133     else {
134         return null;
135     }
136       else {
137     Language language = ruleReference.getOverriddenLanguage();
138     LanguageVersion minimumLanguageVersion = ruleReference.getOverriddenMinimumLanguageVersion();
139     LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion();
140     Boolean deprecated = ruleReference.isOverriddenDeprecated();
141     String name = ruleReference.getOverriddenName();
142     String ref = ruleReference.getRuleSetReference().getRuleSetFileName() "/" + ruleReference.getName();
143     String message = ruleReference.getOverriddenMessage();
144     String externalInfoUrl = ruleReference.getOverriddenExternalInfoUrl();
145     String description = ruleReference.getOverriddenDescription();
146     RulePriority priority = ruleReference.getOverriddenPriority();
147     List<PropertyDescriptor<?>> propertyDescriptors = ruleReference.getOverriddenPropertyDescriptors();
148     Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor = ruleReference
149       .getOverriddenPropertiesByPropertyDescriptor();
150     List<String> examples = ruleReference.getOverriddenExamples();
151     return createSingleRuleElement(language, minimumLanguageVersion, maximumLanguageVersion, deprecated,
152       name, null, ref, message, externalInfoUrl, null, null, null, description, priority,
153       propertyDescriptors, propertiesByPropertyDescriptor, examples);
154       }
155   else {
156       return createSingleRuleElement(rule instanceof ImmutableLanguage ? null : rule.getLanguage(), rule
157         .getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(),
158         rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(), rule
159           .getRuleClass(), rule.usesDFA(), rule.usesTypeResolution(), rule.getDescription(), rule
160           .getPriority(), rule.getPropertyDescriptors(), rule.getPropertiesByPropertyDescriptor(),
161         rule.getExamples());
162   }
163     }
164 
165     private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion,
166       LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref,
167       String message, String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution,
168       String description, RulePriority priority, List<PropertyDescriptor<?>> propertyDescriptors,
169       Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor, List<String> examples) {
170   Element ruleElement = document.createElement("rule");
171   if (language != null) {
172       ruleElement.setAttribute("language", language.getTerseName());
173   }
174   if (minimumLanguageVersion != null) {
175       ruleElement.setAttribute("minimumLanguageVersion", minimumLanguageVersion.getVersion());
176   }
177   if (maximumLanguageVersion != null) {
178       ruleElement.setAttribute("maximumLanguageVersion", maximumLanguageVersion.getVersion());
179   }
180   if (deprecated != null) {
181       ruleElement.setAttribute("deprecated", deprecated.toString());
182   }
183   if (name != null) {
184       ruleElement.setAttribute("name", name);
185   }
186   if (since != null) {
187       ruleElement.setAttribute("since", since);
188   }
189   if (ref != null) {
190       ruleElement.setAttribute("ref", ref);
191   }
192   if (message != null) {
193       ruleElement.setAttribute("message", message);
194   }
195   if (externalInfoUrl != null) {
196       ruleElement.setAttribute("externalInfoUrl", externalInfoUrl);
197   }
198   if (clazz != null) {
199       ruleElement.setAttribute("class", clazz);
200   }
201   if (dfa != null) {
202       ruleElement.setAttribute("dfa", dfa.toString());
203   }
204   if (typeResolution != null) {
205       ruleElement.setAttribute("typeResolution", typeResolution.toString());
206   }
207 
208   if (description != null) {
209       Element descriptionElement = createDescriptionElement(description);
210       ruleElement.appendChild(descriptionElement);
211   }
212   if (priority != null) {
213       Element priorityElement = createPriorityElement(priority);
214       ruleElement.appendChild(priorityElement);
215   }
216   Element propertiesElement = createPropertiesElement(propertyDescriptors, propertiesByPropertyDescriptor);
217   if (propertiesElement != null) {
218       ruleElement.appendChild(propertiesElement);
219   }
220   if (examples != null) {
221       for (String example : examples) {
222     Element exampleElement = createExampleElement(example);
223     ruleElement.appendChild(exampleElement);
224       }
225   }
226   return ruleElement;
227     }
228 
229     private Element createRuleSetReferenceElement(RuleSetReference ruleSetReference) {
230   Element ruleSetReferenceElement = document.createElement("rule");
231   ruleSetReferenceElement.setAttribute("ref", ruleSetReference.getRuleSetFileName());
232   for (String exclude : ruleSetReference.getExcludes()) {
233       Element excludeElement = createExcludeElement(exclude);
234       ruleSetReferenceElement.appendChild(excludeElement);
235   }
236   return ruleSetReferenceElement;
237     }
238 
239     private Element createExcludeElement(String exclude) {
240   return createTextElement("exclude", exclude);
241     }
242 
243     private Element createExampleElement(String example) {
244   return createCDATASectionElement("example", example);
245     }
246 
247     private Element createPriorityElement(RulePriority priority) {
248   return createTextElement("priority", String.valueOf(priority.getPriority()));
249     }
250 
251     @SuppressWarnings("PMD.CompareObjectsWithEquals")
252     private Element createPropertiesElement(List<PropertyDescriptor<?>> propertyDescriptors,
253       Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor) {
254 
255   Element propertiesElement = null;
256   if (propertyDescriptors != null) {
257       // For each provided PropertyDescriptor
258       for (PropertyDescriptor<?> propertyDescriptor : propertyDescriptors) {
259     // Any wrapper property needs to go out as a definition.
260     if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
261         if (propertiesElement == null) {
262       propertiesElement = document.createElement("properties");
263         }
264         Element propertyElement = createPropertyDefinitionElement(((PropertyDescriptorWrapper<?>propertyDescriptor)
265           .getPropertyDescriptor());
266         propertiesElement.appendChild(propertyElement);
267     else {
268         // Otherwise, any property which has a value different than the
269         // default needs to go out as a value.
270         if (propertiesByPropertyDescriptor != null) {
271       Object defaultValue = propertyDescriptor.defaultValue();
272       Object value = propertiesByPropertyDescriptor.get(propertyDescriptor);
273       if (value != defaultValue && (value == null || !value.equals(defaultValue))) {
274           if (propertiesElement == null) {
275         propertiesElement = document.createElement("properties");
276           }
277           Element propertyElement = createPropertyValueElement(propertyDescriptor, value);
278           propertiesElement.appendChild(propertyElement);
279       }
280         }
281     }
282       }
283   }
284 
285   if (propertiesByPropertyDescriptor != null) {
286       // Then, for each PropertyDescriptor not explicitly provided
287       for (Map.Entry<PropertyDescriptor<?>, Object> entry : propertiesByPropertyDescriptor.entrySet()) {
288     // If not explicitly given...
289     PropertyDescriptor<?> propertyDescriptor = entry.getKey();
290     if (!propertyDescriptors.contains(propertyDescriptor)) {
291         // Otherwise, any property which has a value different than the
292         // default needs to go out as a value.
293         Object defaultValue = propertyDescriptor.defaultValue();
294         Object value = entry.getValue();
295         if (value != defaultValue && (value == null || !value.equals(defaultValue))) {
296       if (propertiesElement == null) {
297           propertiesElement = document.createElement("properties");
298       }
299       Element propertyElement = createPropertyValueElement(propertyDescriptor, value);
300       propertiesElement.appendChild(propertyElement);
301         }
302     }
303       }
304   }
305   return propertiesElement;
306     }
307 
308     private Element createPropertyValueElement(PropertyDescriptor propertyDescriptor, Object value) {
309   Element propertyElement = document.createElement("property");
310   propertyElement.setAttribute("name", propertyDescriptor.name());
311   String valueString = propertyDescriptor.asDelimitedString(value);
312   if (XPathRule.XPATH_DESCRIPTOR.equals(propertyDescriptor)) {
313       Element valueElement = createCDATASectionElement("value", valueString);
314       propertyElement.appendChild(valueElement);
315   else {
316       propertyElement.setAttribute("value", valueString);
317   }
318 
319   return propertyElement;
320     }
321 
322     private Element createPropertyDefinitionElement(PropertyDescriptor<?> propertyDescriptor) {
323   Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
324   propertyElement.setAttribute("description", propertyDescriptor.description());
325   String type = PropertyDescriptorFactory.getPropertyDescriptorType(propertyDescriptor);
326   propertyElement.setAttribute("type", type);
327   if (propertyDescriptor.isMultiValue()) {
328       propertyElement.setAttribute("delimiter", String.valueOf(propertyDescriptor.multiValueDelimiter()));
329   }
330   if (propertyDescriptor instanceof AbstractNumericProperty) {
331       propertyElement.setAttribute("min", String.valueOf(((AbstractNumericProperty<?>propertyDescriptor)
332         .lowerLimit()));
333       propertyElement.setAttribute("max", String.valueOf(((AbstractNumericProperty<?>propertyDescriptor)
334         .lowerLimit()));
335   }
336 
337   return propertyElement;
338     }
339 
340     private Element createTextElement(String name, String value) {
341   Element element = document.createElement(name);
342   Text text = document.createTextNode(value);
343   element.appendChild(text);
344   return element;
345     }
346 
347     private Element createCDATASectionElement(String name, String value) {
348   Element element = document.createElement(name);
349   CDATASection cdataSection = document.createCDATASection(value);
350   element.appendChild(cdataSection);
351   return element;
352     }
353 }