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     	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) {		// For each provided PropertyDescriptor
258 			
259 			if (propertyDescriptor instanceof PropertyDescriptorWrapper) {				// Any wrapper property needs to go out as a definition.
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) {		// Otherwise, any property which has a value different than the default needs to go out as a value.
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 	    // Then, for each PropertyDescriptor not explicitly provided
285 	    for (Map.Entry<PropertyDescriptor<?>, Object> entry : propertiesByPropertyDescriptor.entrySet()) {
286 		// If not explicitly given...
287 		PropertyDescriptor<?> propertyDescriptor = entry.getKey();
288 		if (!propertyDescriptors.contains(propertyDescriptor)) {
289 		    // Otherwise, any property which has a value different than the
290 		    // default needs to go out as a value.
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 //	private Element createPropertyDefinitionElement(PropertyDescriptor<?> propertyDescriptor) {
321 //		Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
322 //		
323 //		propertyElement.setAttribute("description", propertyDescriptor.description());
324 //		String type = PropertyDescriptorFactory.getPropertyDescriptorType(propertyDescriptor);
325 //		propertyElement.setAttribute("type", type);
326 //		
327 //		if (propertyDescriptor.isMultiValue()) {
328 //			propertyElement.setAttribute("delimiter", String.valueOf(propertyDescriptor.multiValueDelimiter()));
329 //		}
330 //		
331 //		if (propertyDescriptor instanceof AbstractNumericProperty) {
332 //			propertyElement.setAttribute("min", String.valueOf(((AbstractNumericProperty<?>) propertyDescriptor).lowerLimit()));
333 //			propertyElement.setAttribute("max", String.valueOf(((AbstractNumericProperty<?>) propertyDescriptor).upperLimit()));
334 //		}
335 //
336 //		return propertyElement;
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 }