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 return createTextElement("exclude", exclude);
133 }
134
135 private Element createExampleElement(String example) {
136 return createCDATASectionElement("example", example);
137 }
138
139 private Element createPriorityElement(RulePriority priority) {
140 return createTextElement("priority", String.valueOf(priority.getPriority()));
141 }
142
143 private Element createPropertiesElement() {
144 return document.createElementNS(RULESET_NS_URI, "properties");
145 }
146
147 private Element createRuleElement(Rule rule) {
148 if (rule instanceof RuleReference) {
149 RuleReference ruleReference = (RuleReference) rule;
150 RuleSetReference ruleSetReference = ruleReference.getRuleSetReference();
151 if (ruleSetReference.isAllRules()) {
152 if (!ruleSetFileNames.contains(ruleSetReference.getRuleSetFileName())) {
153 ruleSetFileNames.add(ruleSetReference.getRuleSetFileName());
154 Element ruleSetReferenceElement = createRuleSetReferenceElement(ruleSetReference);
155 return ruleSetReferenceElement;
156 } else {
157 return null;
158 }
159 } else {
160 Language language = ruleReference.getOverriddenLanguage();
161 LanguageVersion minimumLanguageVersion = ruleReference.getOverriddenMinimumLanguageVersion();
162 LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion();
163 Boolean deprecated = ruleReference.isOverriddenDeprecated();
164 String name = ruleReference.getOverriddenName();
165 String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + ruleReference.getName();
166 String message = ruleReference.getOverriddenMessage();
167 String externalInfoUrl = ruleReference.getOverriddenExternalInfoUrl();
168 String description = ruleReference.getOverriddenDescription();
169 RulePriority priority = ruleReference.getOverriddenPriority();
170 List<PropertyDescriptor<?>> propertyDescriptors = ruleReference.getOverriddenPropertyDescriptors();
171 Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor = ruleReference.getOverriddenPropertiesByPropertyDescriptor();
172 List<String> examples = ruleReference.getOverriddenExamples();
173
174 return createSingleRuleElement(language, minimumLanguageVersion, maximumLanguageVersion, deprecated,
175 name, null, ref, message, externalInfoUrl, null, null, null, description, priority,
176 propertyDescriptors, propertiesByPropertyDescriptor, examples);
177 }
178 } else {
179 return createSingleRuleElement(rule instanceof ImmutableLanguage ? null : rule.getLanguage(),
180 rule.getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(),
181 rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(),
182 rule.getRuleClass(), rule.usesDFA(), rule.usesTypeResolution(), rule.getDescription(),
183 rule.getPriority(), rule.getPropertyDescriptors(), rule.getPropertiesByPropertyDescriptor(),
184 rule.getExamples());
185 }
186 }
187
188 private void setIfNonNull(Object value, Element target, String id) {
189 if (value != null) {
190 target.setAttribute(id, value.toString());
191 }
192 }
193
194 private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion,
195 LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref,
196 String message, String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution,
197 String description, RulePriority priority, List<PropertyDescriptor<?>> propertyDescriptors,
198 Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor, List<String> examples) {
199 Element ruleElement = createRuleElement();
200 if (language != null) {
201 ruleElement.setAttribute("language", language.getTerseName());
202 }
203 if (minimumLanguageVersion != null) {
204 ruleElement.setAttribute("minimumLanguageVersion", minimumLanguageVersion.getVersion());
205 }
206 if (maximumLanguageVersion != null) {
207 ruleElement.setAttribute("maximumLanguageVersion", maximumLanguageVersion.getVersion());
208 }
209
210 setIfNonNull(deprecated, ruleElement, "deprecated");
211 setIfNonNull(name, ruleElement, "name");
212 setIfNonNull(since, ruleElement, "since");
213 setIfNonNull(ref, ruleElement, "ref");
214 setIfNonNull(message, ruleElement, "message");
215 setIfNonNull(clazz, ruleElement, "class");
216 setIfNonNull(externalInfoUrl, ruleElement, "externalInfoUrl");
217 setIfNonNull(dfa, ruleElement, "dfa");
218 setIfNonNull(typeResolution, ruleElement, "typeResolution");
219
220 if (description != null) {
221 Element descriptionElement = createDescriptionElement(description);
222 ruleElement.appendChild(descriptionElement);
223 }
224 if (priority != null) {
225 Element priorityElement = createPriorityElement(priority);
226 ruleElement.appendChild(priorityElement);
227 }
228 Element propertiesElement = createPropertiesElement(propertyDescriptors, propertiesByPropertyDescriptor);
229 if (propertiesElement != null) {
230 ruleElement.appendChild(propertiesElement);
231 }
232 if (examples != null) {
233 for (String example : examples) {
234 Element exampleElement = createExampleElement(example);
235 ruleElement.appendChild(exampleElement);
236 }
237 }
238 return ruleElement;
239 }
240
241 private Element createRuleSetReferenceElement(RuleSetReference ruleSetReference) {
242 Element ruleSetReferenceElement = createRuleElement();
243 ruleSetReferenceElement.setAttribute("ref", ruleSetReference.getRuleSetFileName());
244 for (String exclude : ruleSetReference.getExcludes()) {
245 Element excludeElement = createExcludeElement(exclude);
246 ruleSetReferenceElement.appendChild(excludeElement);
247 }
248 return ruleSetReferenceElement;
249 }
250
251 @SuppressWarnings("PMD.CompareObjectsWithEquals")
252 private Element createPropertiesElement(List<PropertyDescriptor<?>> propertyDescriptors, Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor) {
253
254 Element propertiesElement = null;
255 if (propertyDescriptors != null) {
256
257 for (PropertyDescriptor<?> propertyDescriptor : propertyDescriptors) {
258
259 if (propertyDescriptor instanceof PropertyDescriptorWrapper) {
260 if (propertiesElement == null) {
261 propertiesElement = createPropertiesElement();
262 }
263
264 Element propertyElement = createPropertyDefinitionElementBR(((PropertyDescriptorWrapper<?>) propertyDescriptor).getPropertyDescriptor());
265 propertiesElement.appendChild(propertyElement);
266 } else {
267 if (propertiesByPropertyDescriptor != null) {
268 Object defaultValue = propertyDescriptor.defaultValue();
269 Object value = propertiesByPropertyDescriptor.get(propertyDescriptor);
270 if (value != defaultValue && (value == null || !value.equals(defaultValue))) {
271 if (propertiesElement == null) {
272 propertiesElement = createPropertiesElement();
273 }
274
275 Element propertyElement = createPropertyValueElement(propertyDescriptor, value);
276 propertiesElement.appendChild(propertyElement);
277 }
278 }
279 }
280 }
281 }
282
283 if (propertiesByPropertyDescriptor != null) {
284
285 for (Map.Entry<PropertyDescriptor<?>, Object> entry : propertiesByPropertyDescriptor.entrySet()) {
286
287 PropertyDescriptor<?> propertyDescriptor = entry.getKey();
288 if (!propertyDescriptors.contains(propertyDescriptor)) {
289
290
291 Object defaultValue = propertyDescriptor.defaultValue();
292 Object value = entry.getValue();
293 if (value != defaultValue && (value == null || !value.equals(defaultValue))) {
294 if (propertiesElement == null) {
295 propertiesElement = createPropertiesElement();
296 }
297 Element propertyElement = createPropertyValueElement(propertyDescriptor, value);
298 propertiesElement.appendChild(propertyElement);
299 }
300 }
301 }
302 }
303 return propertiesElement;
304 }
305
306 private Element createPropertyValueElement(PropertyDescriptor propertyDescriptor, Object value) {
307 Element propertyElement = document.createElementNS(RULESET_NS_URI, "property");
308 propertyElement.setAttribute("name", propertyDescriptor.name());
309 String valueString = propertyDescriptor.asDelimitedString(value);
310 if (XPathRule.XPATH_DESCRIPTOR.equals(propertyDescriptor)) {
311 Element valueElement = createCDATASectionElement("value", valueString);
312 propertyElement.appendChild(valueElement);
313 } else {
314 propertyElement.setAttribute("value", valueString);
315 }
316
317 return propertyElement;
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339 private Element createPropertyDefinitionElementBR(PropertyDescriptor<?> propertyDescriptor) {
340
341 final Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
342 propertyElement.setAttribute(PropertyDescriptorFields.TYPE, PropertyDescriptorUtil.typeIdFor(propertyDescriptor.type()));
343
344 Map<String, String> propertyValuesById = propertyDescriptor.attributeValuesById();
345 for (Map.Entry<String, String> entry : propertyValuesById.entrySet()) {
346 propertyElement.setAttribute(entry.getKey(), entry.getValue());
347 }
348
349 return propertyElement;
350 }
351
352 private Element createTextElement(String name, String value) {
353 Element element = document.createElementNS(RULESET_NS_URI, name);
354 Text text = document.createTextNode(value);
355 element.appendChild(text);
356 return element;
357 }
358
359 private Element createCDATASectionElement(String name, String value) {
360 Element element = document.createElementNS(RULESET_NS_URI, name);
361 CDATASection cdataSection = document.createCDATASection(value);
362 element.appendChild(cdataSection);
363 return element;
364 }
365 }