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 }