• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 588

12 Jun 2026 08:53AM UTC coverage: 79.108% (-0.005%) from 79.113%
588

push

github

web-flow
[chore] #6641: Further improve the test (#6777)

19066 of 25029 branches covered (76.18%)

Branch coverage included in aggregate %.

41414 of 51423 relevant lines covered (80.54%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.12
/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleSetWriter.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.rule;
6

7
import java.io.OutputStream;
8
import java.util.HashSet;
9
import java.util.List;
10
import java.util.Map;
11
import java.util.Set;
12
import java.util.regex.Pattern;
13
import javax.xml.parsers.DocumentBuilder;
14
import javax.xml.parsers.DocumentBuilderFactory;
15
import javax.xml.parsers.FactoryConfigurationError;
16
import javax.xml.parsers.ParserConfigurationException;
17
import javax.xml.transform.OutputKeys;
18
import javax.xml.transform.Transformer;
19
import javax.xml.transform.TransformerException;
20
import javax.xml.transform.TransformerFactory;
21
import javax.xml.transform.dom.DOMSource;
22
import javax.xml.transform.stream.StreamResult;
23

24
import org.checkerframework.checker.nullness.qual.NonNull;
25
import org.checkerframework.checker.nullness.qual.Nullable;
26
import org.slf4j.Logger;
27
import org.slf4j.LoggerFactory;
28
import org.w3c.dom.CDATASection;
29
import org.w3c.dom.DOMException;
30
import org.w3c.dom.Document;
31
import org.w3c.dom.Element;
32
import org.w3c.dom.Text;
33

34
import net.sourceforge.pmd.internal.util.IOUtil;
35
import net.sourceforge.pmd.lang.Language;
36
import net.sourceforge.pmd.lang.LanguageVersion;
37
import net.sourceforge.pmd.lang.rule.internal.RuleSetReference;
38
import net.sourceforge.pmd.properties.InternalApiBridge;
39
import net.sourceforge.pmd.properties.PropertyConstraint;
40
import net.sourceforge.pmd.properties.PropertyDescriptor;
41
import net.sourceforge.pmd.properties.PropertySerializer;
42
import net.sourceforge.pmd.properties.PropertySource;
43
import net.sourceforge.pmd.properties.internal.PropertyTypeId;
44
import net.sourceforge.pmd.util.internal.xml.SchemaConstants;
45

46
/**
47
 * This class represents a way to serialize a RuleSet to an XML configuration
48
 * file.
49
 */
50
public class RuleSetWriter {
51
    private static final Logger LOG = LoggerFactory.getLogger(RuleSetWriter.class);
1✔
52

53
    public static final String RULESET_2_0_0_NS_URI = "http://pmd.sourceforge.net/ruleset/2.0.0";
54

55
    private final OutputStream outputStream;
56
    private Document document;
57
    private Set<String> ruleSetFileNames;
58

59

60
    public RuleSetWriter(OutputStream outputStream) {
1✔
61
        this.outputStream = outputStream;
1✔
62
    }
1✔
63

64
    public void close() {
65
        IOUtil.closeQuietly(outputStream);
1✔
66
    }
1✔
67

68
    public void write(RuleSet ruleSet) {
69
        write(ruleSet, 3);
1✔
70
    }
1✔
71

72
    void write(RuleSet ruleSet, int indentation) {
73
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
1✔
74
        try {
75
            // Set the context classloader to be the platform classloader, so that we don't
76
            // find the TransformerFactory from Saxon (which is on the classpath), but fallback
77
            // to Java's built-in one. That one supports the "indent-number" attribute, Saxon does not.
78
            // TODO: PMD 8: Use ClassLoader.getPlatformClassLoader()
79
            Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent());
1✔
80

81
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
1✔
82
            documentBuilderFactory.setNamespaceAware(true);
1✔
83
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
1✔
84
            document = documentBuilder.newDocument();
1✔
85
            ruleSetFileNames = new HashSet<>();
1✔
86

87
            Element ruleSetElement = createRuleSetElement(ruleSet);
1✔
88
            document.appendChild(ruleSetElement);
1✔
89

90
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
1✔
91
            try {
92
                transformerFactory.setAttribute("indent-number", indentation);
1✔
93
            } catch (IllegalArgumentException iae) {
×
94
                // ignore it, specific to one parser
95
                LOG.debug("Couldn't set indentation", iae);
×
96
            }
1✔
97
            Transformer transformer = transformerFactory.newTransformer();
1✔
98
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
1✔
99
            // This is as close to pretty printing as we'll get using standard
100
            // Java APIs.
101
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1✔
102
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
1✔
103
            transformer.transform(new DOMSource(document), new StreamResult(outputStream));
1✔
104
        } catch (DOMException | FactoryConfigurationError | ParserConfigurationException | TransformerException e) {
×
105
            throw new RuntimeException(e);
×
106
        } finally {
107
            Thread.currentThread().setContextClassLoader(contextClassLoader);
1✔
108
        }
109
    }
1✔
110

111
    private Element createRuleSetElement(RuleSet ruleSet) {
112
        Element ruleSetElement = document.createElementNS(RULESET_2_0_0_NS_URI, "ruleset");
1✔
113
        ruleSetElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
1✔
114
        ruleSetElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation",
1✔
115
                RULESET_2_0_0_NS_URI + " https://pmd.sourceforge.io/ruleset_2_0_0.xsd");
116
        ruleSetElement.setAttribute("name", ruleSet.getName());
1✔
117

118
        Element descriptionElement = createDescriptionElement(ruleSet.getDescription());
1✔
119
        ruleSetElement.appendChild(descriptionElement);
1✔
120

121
        for (Pattern excludePattern : ruleSet.getFileExclusions()) {
1!
122
            Element excludePatternElement = createExcludePatternElement(excludePattern.pattern());
×
123
            ruleSetElement.appendChild(excludePatternElement);
×
124
        }
×
125
        for (Pattern includePattern : ruleSet.getFileInclusions()) {
1!
126
            Element includePatternElement = createIncludePatternElement(includePattern.pattern());
×
127
            ruleSetElement.appendChild(includePatternElement);
×
128
        }
×
129
        for (Rule rule : ruleSet.getRules()) {
1✔
130
            Element ruleElement = createRuleElement(rule);
1✔
131
            if (ruleElement != null) {
1✔
132
                ruleSetElement.appendChild(ruleElement);
1✔
133
            }
134
        }
1✔
135

136
        return ruleSetElement;
1✔
137
    }
138

139
    private Element createDescriptionElement(String description) {
140
        return createTextElement("description", description);
1✔
141
    }
142

143
    private Element createPropertyValueElement(String name) {
144
        return document.createElementNS(RULESET_2_0_0_NS_URI, name);
1✔
145
    }
146

147
    private Element createExcludePatternElement(String excludePattern) {
148
        return createTextElement("exclude-pattern", excludePattern);
×
149
    }
150

151
    private Element createIncludePatternElement(String includePattern) {
152
        return createTextElement("include-pattern", includePattern);
×
153
    }
154

155
    private Element createRuleElement() {
156
        return document.createElementNS(RULESET_2_0_0_NS_URI, "rule");
1✔
157
    }
158

159
    private Element createExcludeElement(String exclude) {
160
        Element element = document.createElementNS(RULESET_2_0_0_NS_URI, "exclude");
1✔
161
        element.setAttribute("name", exclude);
1✔
162
        return element;
1✔
163
    }
164

165
    private Element createExampleElement(String example) {
166
        return createCDATASectionElement("example", example);
×
167
    }
168

169
    private Element createPriorityElement(RulePriority priority) {
170
        return createTextElement("priority", String.valueOf(priority.getPriority()));
1✔
171
    }
172

173
    private Element createPropertiesElement() {
174
        return document.createElementNS(RULESET_2_0_0_NS_URI, "properties");
1✔
175
    }
176

177
    private Element createRuleElement(Rule rule) {
178
        if (rule instanceof RuleReference) {
1✔
179
            RuleReference ruleReference = (RuleReference) rule;
1✔
180
            RuleSetReference ruleSetReference = ruleReference.getRuleSetReference();
1✔
181
            if (ruleSetReference.isAllRules()) {
1✔
182
                if (!ruleSetFileNames.contains(ruleSetReference.getRuleSetFileName())) {
1✔
183
                    ruleSetFileNames.add(ruleSetReference.getRuleSetFileName());
1✔
184
                    return createRuleSetReferenceElement(ruleSetReference);
1✔
185
                } else {
186
                    return null;
1✔
187
                }
188
            } else {
189
                LanguageVersion minimumLanguageVersion = ruleReference.getOverriddenMinimumLanguageVersion();
1✔
190
                LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion();
1✔
191
                Boolean deprecated = ruleReference.isOverriddenDeprecated();
1✔
192
                String name = ruleReference.getOverriddenName();
1✔
193
                String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + '/'
1✔
194
                        + ruleReference.getRule().getName();
1✔
195
                String message = ruleReference.getOverriddenMessage();
1✔
196
                String externalInfoUrl = ruleReference.getOverriddenExternalInfoUrl();
1✔
197
                String description = ruleReference.getOverriddenDescription();
1✔
198
                RulePriority priority = ruleReference.getOverriddenPriority();
1✔
199
                List<String> examples = ruleReference.getOverriddenExamples();
1✔
200

201
                return createSingleRuleElement(null, minimumLanguageVersion, maximumLanguageVersion, deprecated,
1✔
202
                                               name, null, ref, message, externalInfoUrl, null, description, priority,
203
                                               ruleReference, examples);
204
            }
205
        } else {
206
            return createSingleRuleElement(rule.getLanguage(),
1✔
207
                                           rule.getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(),
1✔
208
                                           rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(),
1✔
209
                                           rule.getRuleClass(),
1✔
210
                                           rule.getDescription(),
1✔
211
                                           rule.getPriority(), rule,
1✔
212
                                           rule.getExamples());
1✔
213
        }
214
    }
215

216
    private void setIfNonNull(Object value, Element target, String id) {
217
        if (value != null) {
1✔
218
            target.setAttribute(id, value.toString());
1✔
219
        }
220
    }
1✔
221

222
    private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion,
223
                                            LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref,
224
                                            String message, String externalInfoUrl, String clazz,
225
                                            String description, RulePriority priority, PropertySource propertySource, List<String> examples) {
226
        Element ruleElement = createRuleElement();
1✔
227
        // language is now a required attribute, unless this is a rule reference
228
        if (clazz != null) {
1✔
229
            ruleElement.setAttribute("language", language.getId());
1✔
230
        }
231
        if (minimumLanguageVersion != null) {
1!
232
            ruleElement.setAttribute("minimumLanguageVersion", minimumLanguageVersion.getVersion());
×
233
        }
234
        if (maximumLanguageVersion != null) {
1!
235
            ruleElement.setAttribute("maximumLanguageVersion", maximumLanguageVersion.getVersion());
×
236
        }
237

238
        setIfNonNull(deprecated, ruleElement, "deprecated");
1✔
239
        setIfNonNull(name, ruleElement, "name");
1✔
240
        setIfNonNull(since, ruleElement, "since");
1✔
241
        setIfNonNull(ref, ruleElement, "ref");
1✔
242
        setIfNonNull(message, ruleElement, "message");
1✔
243
        setIfNonNull(clazz, ruleElement, "class");
1✔
244
        setIfNonNull(externalInfoUrl, ruleElement, "externalInfoUrl");
1✔
245

246
        if (description != null) {
1!
247
            Element descriptionElement = createDescriptionElement(description);
×
248
            ruleElement.appendChild(descriptionElement);
×
249
        }
250
        if (priority != null) {
1✔
251
            Element priorityElement = createPriorityElement(priority);
1✔
252
            ruleElement.appendChild(priorityElement);
1✔
253
        }
254
        Element propertiesElement = createPropertiesElement(propertySource);
1✔
255
        if (propertiesElement != null) {
1✔
256
            ruleElement.appendChild(propertiesElement);
1✔
257
        }
258
        if (examples != null) {
1✔
259
            for (String example : examples) {
1!
260
                Element exampleElement = createExampleElement(example);
×
261
                ruleElement.appendChild(exampleElement);
×
262
            }
×
263
        }
264
        return ruleElement;
1✔
265
    }
266

267
    private Element createRuleSetReferenceElement(RuleSetReference ruleSetReference) {
268
        Element ruleSetReferenceElement = createRuleElement();
1✔
269
        ruleSetReferenceElement.setAttribute("ref", ruleSetReference.getRuleSetFileName());
1✔
270
        for (String exclude : ruleSetReference.getExcludes()) {
1✔
271
            Element excludeElement = createExcludeElement(exclude);
1✔
272
            ruleSetReferenceElement.appendChild(excludeElement);
1✔
273
        }
1✔
274
        return ruleSetReferenceElement;
1✔
275
    }
276

277
    private @Nullable Element createPropertiesElement(PropertySource propertySource) {
278

279
        Element propertiesElement = null;
1✔
280
        List<PropertyDescriptor<?>> overridden = propertySource.getOverriddenPropertyDescriptors();
1✔
281
        List<PropertyDescriptor<?>> defined = propertySource.getPropertyDescriptors();
1✔
282

283
        for (PropertyDescriptor<?> descriptor : defined) {
1✔
284
            // For each provided PropertyDescriptor
285

286
            PropertyTypeId typeId = InternalApiBridge.getTypeId(descriptor);
1✔
287
            // RuleReferences can't define additional properties
288
            boolean isPropertyDefinition = typeId != null && !(propertySource instanceof RuleReference);
1✔
289

290
            // skip properties, which neither are definitions nor override the default value
291
            if (!isPropertyDefinition && !overridden.contains(descriptor)) {
1✔
292
                continue;
1✔
293
            }
294

295
            if (propertiesElement == null) {
1✔
296
                propertiesElement = createPropertiesElement();
1✔
297
            }
298

299
            if (isPropertyDefinition) {
1✔
300
                propertiesElement.appendChild(createPropertyDefinitionElementBR(descriptor, typeId));
1✔
301
            } else {
302
                propertiesElement.appendChild(propertyElementWithValueAttribute(propertySource, descriptor));
1✔
303
            }
304
        }
1✔
305

306
        return propertiesElement;
1✔
307
    }
308

309
    private <T> @NonNull Element propertyElementWithValueAttribute(PropertySource propertySource, PropertyDescriptor<T> propertyDescriptor) {
310
        Element element = document.createElementNS(RULESET_2_0_0_NS_URI, "property");
1✔
311
        SchemaConstants.NAME.setOn(element, propertyDescriptor.name());
1✔
312

313
        PropertySerializer<T> xmlStrategy = propertyDescriptor.serializer();
1✔
314
        T value = propertySource.getProperty(propertyDescriptor);
1✔
315
        SchemaConstants.PROPERTY_VALUE.setOn(element, xmlStrategy.toString(value));
1✔
316
        return element;
1✔
317
    }
318

319
    private <T> Element createPropertyValueElement(PropertyDescriptor<T> propertyDescriptor, T value) {
320
        Element element = document.createElementNS(RULESET_2_0_0_NS_URI, "property");
1✔
321
        SchemaConstants.NAME.setOn(element, propertyDescriptor.name());
1✔
322

323
        PropertySerializer<T> xmlStrategy = propertyDescriptor.serializer();
1✔
324

325
        Element valueElt = createPropertyValueElement(SchemaConstants.PROPERTY_VALUE.xmlName());
1✔
326
        valueElt.setTextContent(xmlStrategy.toString(value));
1✔
327
        element.appendChild(valueElt);
1✔
328

329
        return element;
1✔
330
    }
331

332
    private <T> Element createPropertyDefinitionElementBR(PropertyDescriptor<T> propertyDescriptor, @NonNull PropertyTypeId typeId) {
333

334
        final Element element = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue());
1✔
335

336
        SchemaConstants.NAME.setOn(element, propertyDescriptor.name());
1✔
337
        SchemaConstants.PROPERTY_TYPE.setOn(element, typeId.getStringId());
1✔
338
        SchemaConstants.DESCRIPTION.setOn(element, propertyDescriptor.description());
1✔
339

340
        for (PropertyConstraint<? super T> constraint : propertyDescriptor.serializer().getConstraints()) {
1✔
341
            Map<String, String> attributes = constraint.getXmlConstraint();
1✔
342

343
            if (attributes == null || attributes.isEmpty()) {
1!
344
                throw new IllegalArgumentException("Unsupported property constraint in XML: " + constraint);
×
345
            }
346

347
            for (Map.Entry<String, String> attribute : attributes.entrySet()) {
1✔
348
                if (SchemaConstants.PROPERTY_MAX.xmlName().equals(attribute.getKey())) {
1✔
349
                    SchemaConstants.PROPERTY_MAX.setOn(element, attribute.getValue());
1✔
350
                } else if (SchemaConstants.PROPERTY_MIN.xmlName().equals(attribute.getKey())) {
1!
351
                    SchemaConstants.PROPERTY_MIN.setOn(element, attribute.getValue());
1✔
352
                } else {
353
                    throw new IllegalArgumentException("Unsupported property constraint in XML: " + constraint
×
354
                            + ". There is no attribute " + attribute.getKey());
×
355
                }
356
            }
1✔
357

358
        }
1✔
359

360
        return element;
1✔
361
    }
362

363
    private Element createTextElement(String name, String value) {
364
        Element element = document.createElementNS(RULESET_2_0_0_NS_URI, name);
1✔
365
        Text text = document.createTextNode(value);
1✔
366
        element.appendChild(text);
1✔
367
        return element;
1✔
368
    }
369

370
    private Element createCDATASectionElement(String name, String value) {
371
        Element element = document.createElementNS(RULESET_2_0_0_NS_URI, name);
×
372
        CDATASection cdataSection = document.createCDATASection(value);
×
373
        element.appendChild(cdataSection);
×
374
        return element;
×
375
    }
376
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc