View Javadoc

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   * This class represents a way to serialize a RuleSet to an XML configuration file.
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  		    		//ignore it, specific to one parser
70  		    	}
71  		    Transformer transformer = transformerFactory.newTransformer();
72  		    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
73  		    // This is as close to pretty printing as we'll get using standard Java APIs.
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) {		// For each provided PropertyDescriptor
260 			
261 			if (propertyDescriptor instanceof PropertyDescriptorWrapper) {				// Any wrapper property needs to go out as a definition.
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) {		// Otherwise, any property which has a value different than the default needs to go out as a value.
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 	    // 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 = 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 //	private Element createPropertyDefinitionElement(PropertyDescriptor<?> propertyDescriptor) {
323 //		Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
324 //		
325 //		propertyElement.setAttribute("description", propertyDescriptor.description());
326 //		String type = PropertyDescriptorFactory.getPropertyDescriptorType(propertyDescriptor);
327 //		propertyElement.setAttribute("type", type);
328 //		
329 //		if (propertyDescriptor.isMultiValue()) {
330 //			propertyElement.setAttribute("delimiter", String.valueOf(propertyDescriptor.multiValueDelimiter()));
331 //		}
332 //		
333 //		if (propertyDescriptor instanceof AbstractNumericProperty) {
334 //			propertyElement.setAttribute("min", String.valueOf(((AbstractNumericProperty<?>) propertyDescriptor).lowerLimit()));
335 //			propertyElement.setAttribute("max", String.valueOf(((AbstractNumericProperty<?>) propertyDescriptor).upperLimit()));
336 //		}
337 //
338 //		return propertyElement;
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 }