1 package net.sourceforge.pmd;
2
3 import java.io.OutputStream;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8
9 import javax.xml.parsers.DocumentBuilder;
10 import javax.xml.parsers.DocumentBuilderFactory;
11 import javax.xml.parsers.FactoryConfigurationError;
12 import javax.xml.parsers.ParserConfigurationException;
13 import javax.xml.transform.OutputKeys;
14 import javax.xml.transform.Transformer;
15 import javax.xml.transform.TransformerException;
16 import javax.xml.transform.TransformerFactory;
17 import javax.xml.transform.dom.DOMSource;
18 import javax.xml.transform.stream.StreamResult;
19
20 import net.sourceforge.pmd.lang.Language;
21 import net.sourceforge.pmd.lang.LanguageVersion;
22 import net.sourceforge.pmd.lang.rule.ImmutableLanguage;
23 import net.sourceforge.pmd.lang.rule.RuleReference;
24 import net.sourceforge.pmd.lang.rule.XPathRule;
25 import net.sourceforge.pmd.lang.rule.properties.PropertyDescriptorWrapper;
26 import net.sourceforge.pmd.lang.rule.properties.factories.PropertyDescriptorUtil;
27 import net.sourceforge.pmd.util.IOUtil;
28
29 import org.w3c.dom.CDATASection;
30 import org.w3c.dom.DOMException;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Text;
34
35
36
37
38 public class RuleSetWriter {
39
40 public static final String RULESET_NS_URI = "http://pmd.sourceforge.net/ruleset/2.0.0";
41
42 private final OutputStream outputStream;
43 private Document document;
44 private Set<String> ruleSetFileNames;
45
46 public RuleSetWriter(OutputStream outputStream) {
47 this.outputStream = outputStream;
48 }
49
50 public void close() {
51 IOUtil.closeQuietly(outputStream);
52 }
53
54 public void write(RuleSet ruleSet) {
55 try {
56 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
57 documentBuilderFactory.setNamespaceAware(true);
58 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
59 document = documentBuilder.newDocument();
60 ruleSetFileNames = new HashSet<String>();
61
62 Element ruleSetElement = createRuleSetElement(ruleSet);
63 document.appendChild(ruleSetElement);
64
65 TransformerFactory transformerFactory = TransformerFactory.newInstance();
66 try {
67 transformerFactory.setAttribute("indent-number", 3);
68 } catch (IllegalArgumentException iae) {
69
70 }
71 Transformer transformer = transformerFactory.newTransformer();
72 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
73
74 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
75 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
76 transformer.transform(new DOMSource(document), new StreamResult(outputStream));
77 } catch (DOMException e) {
78 throw new RuntimeException(e);
79 } catch (FactoryConfigurationError e) {
80 throw new RuntimeException(e);
81 } catch (ParserConfigurationException e) {
82 throw new RuntimeException(e);
83 } catch (TransformerException e) {
84 throw new RuntimeException(e);
85 }
86 }
87
88 private Element createRuleSetElement(RuleSet ruleSet) {
89 Element ruleSetElement = document.createElementNS(RULESET_NS_URI, "ruleset");
90 ruleSetElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
91 ruleSetElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", RULESET_NS_URI + " http://pmd.sourceforge.net/ruleset_2_0_0.xsd");
92 ruleSetElement.setAttribute("name", ruleSet.getName());
93
94 Element descriptionElement = createDescriptionElement(ruleSet.getDescription());
95 ruleSetElement.appendChild(descriptionElement);
96
97 for (String excludePattern : ruleSet.getExcludePatterns()) {
98 Element excludePatternElement = createExcludePatternElement(excludePattern);
99 ruleSetElement.appendChild(excludePatternElement);
100 }
101 for (String includePattern : ruleSet.getIncludePatterns()) {
102 Element includePatternElement = createIncludePatternElement(includePattern);
103 ruleSetElement.appendChild(includePatternElement);
104 }
105 for (Rule rule : ruleSet.getRules()) {
106 Element ruleElement = createRuleElement(rule);
107 if (ruleElement != null) {
108 ruleSetElement.appendChild(ruleElement);
109 }
110 }
111
112 return ruleSetElement;
113 }
114
115 private Element createDescriptionElement(String description) {
116 return createTextElement("description", description);
117 }
118
119 private Element createExcludePatternElement(String excludePattern) {
120 return createTextElement("exclude-pattern", excludePattern);
121 }
122
123 private Element createIncludePatternElement(String includePattern) {
124 return createTextElement("include-pattern", includePattern);
125 }
126
127 private Element createRuleElement() {
128 return document.createElementNS(RULESET_NS_URI, "rule");
129 }
130
131 private Element createExcludeElement(String exclude) {
132 Element element = document.createElementNS(RULESET_NS_URI, "exclude");
133 element.setAttribute("name", exclude);
134 return element;
135 }
136
137 private Element createExampleElement(String example) {
138 return createCDATASectionElement("example", example);
139 }
140
141 private Element createPriorityElement(RulePriority priority) {
142 return createTextElement("priority", String.valueOf(priority.getPriority()));
143 }
144
145 private Element createPropertiesElement() {
146 return document.createElementNS(RULESET_NS_URI, "properties");
147 }
148
149 private Element createRuleElement(Rule rule) {
150 if (rule instanceof RuleReference) {
151 RuleReference ruleReference = (RuleReference) rule;
152 RuleSetReference ruleSetReference = ruleReference.getRuleSetReference();
153 if (ruleSetReference.isAllRules()) {
154 if (!ruleSetFileNames.contains(ruleSetReference.getRuleSetFileName())) {
155 ruleSetFileNames.add(ruleSetReference.getRuleSetFileName());
156 Element ruleSetReferenceElement = createRuleSetReferenceElement(ruleSetReference);
157 return ruleSetReferenceElement;
158 } else {
159 return null;
160 }
161 } else {
162 Language language = ruleReference.getOverriddenLanguage();
163 LanguageVersion minimumLanguageVersion = ruleReference.getOverriddenMinimumLanguageVersion();
164 LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion();
165 Boolean deprecated = ruleReference.isOverriddenDeprecated();
166 String name = ruleReference.getOverriddenName();
167 String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + ruleReference.getName();
168 String message = ruleReference.getOverriddenMessage();
169 String externalInfoUrl = ruleReference.getOverriddenExternalInfoUrl();
170 String description = ruleReference.getOverriddenDescription();
171 RulePriority priority = ruleReference.getOverriddenPriority();
172 List<PropertyDescriptor<?>> propertyDescriptors = ruleReference.getOverriddenPropertyDescriptors();
173 Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor = ruleReference.getOverriddenPropertiesByPropertyDescriptor();
174 List<String> examples = ruleReference.getOverriddenExamples();
175
176 return createSingleRuleElement(language, minimumLanguageVersion, maximumLanguageVersion, deprecated,
177 name, null, ref, message, externalInfoUrl, null, null, null, description, priority,
178 propertyDescriptors, propertiesByPropertyDescriptor, examples);
179 }
180 } else {
181 return createSingleRuleElement(rule instanceof ImmutableLanguage ? null : rule.getLanguage(),
182 rule.getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(),
183 rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(),
184 rule.getRuleClass(), rule.usesDFA(), rule.usesTypeResolution(), rule.getDescription(),
185 rule.getPriority(), rule.getPropertyDescriptors(), rule.getPropertiesByPropertyDescriptor(),
186 rule.getExamples());
187 }
188 }
189
190 private void setIfNonNull(Object value, Element target, String id) {
191 if (value != null) {
192 target.setAttribute(id, value.toString());
193 }
194 }
195
196 private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion,
197 LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref,
198 String message, String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution,
199 String description, RulePriority priority, List<PropertyDescriptor<?>> propertyDescriptors,
200 Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor, List<String> examples) {
201 Element ruleElement = createRuleElement();
202 if (language != null) {
203 ruleElement.setAttribute("language", language.getTerseName());
204 }
205 if (minimumLanguageVersion != null) {
206 ruleElement.setAttribute("minimumLanguageVersion", minimumLanguageVersion.getVersion());
207 }
208 if (maximumLanguageVersion != null) {
209 ruleElement.setAttribute("maximumLanguageVersion", maximumLanguageVersion.getVersion());
210 }
211
212 setIfNonNull(deprecated, ruleElement, "deprecated");
213 setIfNonNull(name, ruleElement, "name");
214 setIfNonNull(since, ruleElement, "since");
215 setIfNonNull(ref, ruleElement, "ref");
216 setIfNonNull(message, ruleElement, "message");
217 setIfNonNull(clazz, ruleElement, "class");
218 setIfNonNull(externalInfoUrl, ruleElement, "externalInfoUrl");
219 setIfNonNull(dfa, ruleElement, "dfa");
220 setIfNonNull(typeResolution, ruleElement, "typeResolution");
221
222 if (description != null) {
223 Element descriptionElement = createDescriptionElement(description);
224 ruleElement.appendChild(descriptionElement);
225 }
226 if (priority != null) {
227 Element priorityElement = createPriorityElement(priority);
228 ruleElement.appendChild(priorityElement);
229 }
230 Element propertiesElement = createPropertiesElement(propertyDescriptors, propertiesByPropertyDescriptor);
231 if (propertiesElement != null) {
232 ruleElement.appendChild(propertiesElement);
233 }
234 if (examples != null) {
235 for (String example : examples) {
236 Element exampleElement = createExampleElement(example);
237 ruleElement.appendChild(exampleElement);
238 }
239 }
240 return ruleElement;
241 }
242
243 private Element createRuleSetReferenceElement(RuleSetReference ruleSetReference) {
244 Element ruleSetReferenceElement = createRuleElement();
245 ruleSetReferenceElement.setAttribute("ref", ruleSetReference.getRuleSetFileName());
246 for (String exclude : ruleSetReference.getExcludes()) {
247 Element excludeElement = createExcludeElement(exclude);
248 ruleSetReferenceElement.appendChild(excludeElement);
249 }
250 return ruleSetReferenceElement;
251 }
252
253 @SuppressWarnings("PMD.CompareObjectsWithEquals")
254 private Element createPropertiesElement(List<PropertyDescriptor<?>> propertyDescriptors, Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor) {
255
256 Element propertiesElement = null;
257 if (propertyDescriptors != null) {
258
259 for (PropertyDescriptor<?> propertyDescriptor : propertyDescriptors) {
260
261 if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
262 if (propertiesElement == null) {
263 propertiesElement = createPropertiesElement();
264 }
265
266 Element propertyElement = createPropertyDefinitionElementBR(((PropertyDescriptorWrapper<?>) propertyDescriptor).getPropertyDescriptor());
267 propertiesElement.appendChild(propertyElement);
268 } else {
269 if (propertiesByPropertyDescriptor != null) {
270 Object defaultValue = propertyDescriptor.defaultValue();
271 Object value = propertiesByPropertyDescriptor.get(propertyDescriptor);
272 if (value != defaultValue && (value == null || !value.equals(defaultValue))) {
273 if (propertiesElement == null) {
274 propertiesElement = createPropertiesElement();
275 }
276
277 Element propertyElement = createPropertyValueElement(propertyDescriptor, value);
278 propertiesElement.appendChild(propertyElement);
279 }
280 }
281 }
282 }
283 }
284
285 if (propertiesByPropertyDescriptor != null) {
286
287 for (Map.Entry<PropertyDescriptor<?>, Object> entry : propertiesByPropertyDescriptor.entrySet()) {
288
289 PropertyDescriptor<?> propertyDescriptor = entry.getKey();
290 if (!propertyDescriptors.contains(propertyDescriptor)) {
291
292
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 = createPropertiesElement();
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.createElementNS(RULESET_NS_URI, "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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341 private Element createPropertyDefinitionElementBR(PropertyDescriptor<?> propertyDescriptor) {
342
343 final Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
344 propertyElement.setAttribute(PropertyDescriptorFields.TYPE, PropertyDescriptorUtil.typeIdFor(propertyDescriptor.type()));
345
346 Map<String, String> propertyValuesById = propertyDescriptor.attributeValuesById();
347 for (Map.Entry<String, String> entry : propertyValuesById.entrySet()) {
348 propertyElement.setAttribute(entry.getKey(), entry.getValue());
349 }
350
351 return propertyElement;
352 }
353
354 private Element createTextElement(String name, String value) {
355 Element element = document.createElementNS(RULESET_NS_URI, name);
356 Text text = document.createTextNode(value);
357 element.appendChild(text);
358 return element;
359 }
360
361 private Element createCDATASectionElement(String name, String value) {
362 Element element = document.createElementNS(RULESET_NS_URI, name);
363 CDATASection cdataSection = document.createCDATASection(value);
364 element.appendChild(cdataSection);
365 return element;
366 }
367 }