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 = (RuleReference) rule;
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 }
|