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