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

pmd / pmd / 117

14 Aug 2025 03:28PM UTC coverage: 78.476% (+0.02%) from 78.454%
117

push

github

adangel
Merge remote-tracking branch 'origin/main'

17884 of 23631 branches covered (75.68%)

Branch coverage included in aggregate %.

39177 of 49080 relevant lines covered (79.82%)

0.81 hits per line

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

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

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

7
import static org.hamcrest.MatcherAssert.assertThat;
8
import static org.hamcrest.Matchers.emptyString;
9
import static org.junit.jupiter.api.Assertions.assertEquals;
10
import static org.junit.jupiter.api.Assertions.assertFalse;
11
import static org.junit.jupiter.api.Assertions.assertTrue;
12
import static org.junit.jupiter.api.Assertions.fail;
13

14
import java.io.BufferedReader;
15
import java.io.ByteArrayInputStream;
16
import java.io.ByteArrayOutputStream;
17
import java.io.FileNotFoundException;
18
import java.io.IOException;
19
import java.io.InputStream;
20
import java.io.InputStreamReader;
21
import java.nio.charset.StandardCharsets;
22
import java.util.ArrayList;
23
import java.util.Arrays;
24
import java.util.Collections;
25
import java.util.HashMap;
26
import java.util.HashSet;
27
import java.util.List;
28
import java.util.Locale;
29
import java.util.Map;
30
import java.util.Properties;
31
import java.util.Set;
32
import java.util.StringTokenizer;
33
import java.util.regex.Pattern;
34
import javax.xml.parsers.SAXParser;
35
import javax.xml.parsers.SAXParserFactory;
36

37
import org.junit.jupiter.api.BeforeAll;
38
import org.junit.jupiter.api.Test;
39
import org.slf4j.event.Level;
40
import org.xml.sax.InputSource;
41
import org.xml.sax.SAXException;
42
import org.xml.sax.SAXParseException;
43
import org.xml.sax.helpers.DefaultHandler;
44

45
import net.sourceforge.pmd.internal.util.IOUtil;
46
import net.sourceforge.pmd.lang.Language;
47
import net.sourceforge.pmd.lang.LanguageRegistry;
48
import net.sourceforge.pmd.lang.rule.InternalApiBridge;
49
import net.sourceforge.pmd.lang.rule.Rule;
50
import net.sourceforge.pmd.lang.rule.RuleReference;
51
import net.sourceforge.pmd.lang.rule.RuleSet;
52
import net.sourceforge.pmd.lang.rule.RuleSetLoader;
53
import net.sourceforge.pmd.lang.rule.RuleSetWriter;
54
import net.sourceforge.pmd.lang.rule.impl.UnnecessaryPmdSuppressionRule;
55
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
56
import net.sourceforge.pmd.properties.PropertyDescriptor;
57
import net.sourceforge.pmd.util.log.internal.MessageReporterBase;
58

59
/**
60
 * Base test class to verify the language's rulesets. This class should be
61
 * subclassed for each language.
62
 */
63
public abstract class AbstractRuleSetFactoryTest {
64

65
    private static ValidateDefaultHandler validateDefaultHandler;
66
    private static SAXParser saxParser;
67

68
    // todo rename this field to validCoreRules or something. Make private.
69
    protected Set<String> validXPathClassNames = new HashSet<>();
×
70
    private final Set<String> languagesToSkip = new HashSet<>();
×
71

72
    public AbstractRuleSetFactoryTest() {
73
        this(new String[0]);
×
74
    }
×
75

76
    /**
77
     * Constructor used when a module that depends on another module wants to filter out the dependee's rulesets.
78
     *
79
     * @param languagesToSkip {@link Language}s terse names that appear in the classpath via a dependency, but should be
80
     * skipped because they aren't the primary language which the concrete instance of this class is testing.
81
     */
82
    public AbstractRuleSetFactoryTest(String... languagesToSkip) {
×
83
        this.languagesToSkip.add("dummy");
×
84
        this.languagesToSkip.addAll(Arrays.asList(languagesToSkip));
×
85
        validXPathClassNames.add(XPathRule.class.getName());
×
86
        validXPathClassNames.add(UnnecessaryPmdSuppressionRule.class.getName());
×
87
    }
×
88

89
    public AbstractRuleSetFactoryTest(Language... languagesToSkip) {
90
        this(Arrays.stream(languagesToSkip).map(Language::getId).toArray(String[]::new));
×
91
    }
×
92

93

94
    /**
95
     * Setups the XML parser with validation.
96
     *
97
     * @throws Exception
98
     *             any error
99
     */
100
    @BeforeAll
101
    static void init() throws Exception {
102
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
×
103
        saxParserFactory.setValidating(true);
×
104
        saxParserFactory.setNamespaceAware(true);
×
105

106
        // Hope we're using Xerces, or this may not work!
107
        // Note: Features are listed here
108
        // http://xerces.apache.org/xerces2-j/features.html
109
        saxParserFactory.setFeature("http://xml.org/sax/features/validation", true);
×
110
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema", true);
×
111
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
×
112

113
        validateDefaultHandler = new ValidateDefaultHandler();
×
114

115
        saxParser = saxParserFactory.newSAXParser();
×
116
    }
×
117

118
    /**
119
     * Checks all rulesets of all languages on the classpath and verifies that
120
     * all required attributes for all rules are specified.
121
     *
122
     * @throws Exception
123
     *             any error
124
     */
125
    @Test
126
    void testAllPMDBuiltInRulesMeetConventions() throws Exception {
127
        int invalidSinceAttributes = 0;
×
128
        int invalidExternalInfoURL = 0;
×
129
        int invalidClassName = 0;
×
130
        int invalidRegexSuppress = 0;
×
131
        int invalidXPathSuppress = 0;
×
132
        StringBuilder messages = new StringBuilder();
×
133
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
134
        for (String fileName : ruleSetFileNames) {
×
135
            RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
136
            for (Rule rule : ruleSet.getRules()) {
×
137

138
                // Skip references
139
                if (rule instanceof RuleReference) {
×
140
                    continue;
×
141
                }
142

143
                Language language = rule.getLanguage();
×
144
                String group = fileName.substring(fileName.lastIndexOf('/') + 1);
×
145
                group = group.substring(0, group.indexOf(".xml"));
×
146
                if (group.indexOf('-') >= 0) {
×
147
                    group = group.substring(0, group.indexOf('-'));
×
148
                }
149

150
                // Is since missing ?
151
                if (rule.getSince() == null) {
×
152
                    invalidSinceAttributes++;
×
153
                    messages.append("Rule ")
×
154
                            .append(fileName)
×
155
                            .append("/")
×
156
                            .append(rule.getName())
×
157
                            .append(" is missing 'since' attribute\n");
×
158
                }
159
                // Is URL valid ?
160
                if (rule.getExternalInfoUrl() == null || "".equalsIgnoreCase(rule.getExternalInfoUrl())) {
×
161
                    invalidExternalInfoURL++;
×
162
                    messages.append("Rule ")
×
163
                            .append(fileName)
×
164
                            .append("/")
×
165
                            .append(rule.getName())
×
166
                            .append(" is missing 'externalInfoURL' attribute\n");
×
167
                } else {
168
                    String expectedExternalInfoURL = "https://docs.pmd-code.org/.+/pmd_rules_"
×
169
                            + language.getId() + "_"
×
170
                            + IOUtil.getFilenameBase(fileName)
×
171
                            + ".html#"
172
                            + rule.getName().toLowerCase(Locale.ROOT);
×
173
                    if (rule.getExternalInfoUrl() == null
×
174
                            || !rule.getExternalInfoUrl().matches(expectedExternalInfoURL)) {
×
175
                        invalidExternalInfoURL++;
×
176
                        messages.append("Rule ")
×
177
                                .append(fileName)
×
178
                                .append("/")
×
179
                                .append(rule.getName())
×
180
                                .append(" seems to have an invalid 'externalInfoURL' value (")
×
181
                                .append(rule.getExternalInfoUrl())
×
182
                                .append("), it should be:")
×
183
                                .append(expectedExternalInfoURL)
×
184
                                .append('\n');
×
185
                    }
186
                }
187
                // Proper class name/packaging?
188
                String expectedClassName = "net.sourceforge.pmd.lang." + language.getId() + ".rule." + group
×
189
                        + "." + rule.getName() + "Rule";
×
190
                if (!rule.getRuleClass().equals(expectedClassName)
×
191
                        && !validXPathClassNames.contains(rule.getRuleClass())) {
×
192
                    invalidClassName++;
×
193
                    messages.append("Rule ")
×
194
                            .append(fileName)
×
195
                            .append("/")
×
196
                            .append(rule.getName())
×
197
                            .append(" seems to have an invalid 'class' value (")
×
198
                            .append(rule.getRuleClass())
×
199
                            .append("), it should be:")
×
200
                            .append(expectedClassName)
×
201
                            .append('\n');
×
202
                }
203
                // Should not have violation suppress regex property
204
                if (rule.getProperty(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR).isPresent()) {
×
205
                    invalidRegexSuppress++;
×
206
                    messages.append("Rule ")
×
207
                            .append(fileName)
×
208
                            .append("/")
×
209
                            .append(rule.getName())
×
210
                            .append(" should not have '")
×
211
                            .append(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR.name())
×
212
                            .append("', this is intended for end user customization only.\n");
×
213
                }
214
                // Should not have violation suppress xpath property
215
                if (rule.getProperty(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR).isPresent()) {
×
216
                    invalidXPathSuppress++;
×
217
                    messages.append("Rule ").append(fileName).append("/").append(rule.getName()).append(" should not have '").append(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR.name()).append("', this is intended for end user customization only.").append(System.lineSeparator());
×
218
                }
219
            }
×
220
        }
×
221
        // We do this at the end to ensure we test ALL the rules before failing
222
        // the test
223
        if (invalidSinceAttributes > 0 || invalidExternalInfoURL > 0 || invalidClassName > 0 || invalidRegexSuppress > 0
×
224
                || invalidXPathSuppress > 0) {
225
            fail("All built-in PMD rules need 'since' attribute (" + invalidSinceAttributes
×
226
                    + " are missing), a proper ExternalURLInfo (" + invalidExternalInfoURL
227
                    + " are invalid), a class name meeting conventions (" + invalidClassName + " are invalid), no '"
228
                    + Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR.name() + "' property (" + invalidRegexSuppress
×
229
                    + " are invalid), and no '" + Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR.name() + "' property ("
×
230
                    + invalidXPathSuppress + " are invalid)\n" + messages);
231
        }
232
    }
×
233

234
    /**
235
     * Verifies that all rulesets are valid XML according to the xsd schema.
236
     *
237
     * @throws Exception
238
     *             any error
239
     */
240
    @Test
241
    void testXmlSchema() throws Exception {
242
        boolean allValid = true;
×
243
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
244
        for (String fileName : ruleSetFileNames) {
×
245
            boolean valid = validateAgainstSchema(fileName);
×
246
            allValid = allValid && valid;
×
247
        }
×
248
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
249
    }
×
250

251
    /**
252
     * @deprecated Since 7.17.0. This method will be removed. PMD has the rule "MissingEncoding" for XML files that
253
     *             is used instead.
254
     */
255
    @Deprecated
256
    public static boolean hasCorrectEncoding(String fileName) throws IOException {
257
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
258
            // first bytes must be:
259
            byte[] expectedBytes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes(StandardCharsets.UTF_8);
×
260
            byte[] bytes = new byte[expectedBytes.length];
×
261
            int count = inputStream.read(bytes);
×
262
            if (count != expectedBytes.length || !Arrays.equals(expectedBytes, bytes)) {
×
263
                return false;
×
264
            }
265
        }
×
266
        return true;
×
267
    }
268

269
    /**
270
     * Verifies that all rulesets are valid XML according to the DTD.
271
     *
272
     * @throws Exception
273
     *             any error
274
     */
275
    @Test
276
    void testDtd() throws Exception {
277
        boolean allValid = true;
×
278
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
279
        for (String fileName : ruleSetFileNames) {
×
280
            boolean valid = validateAgainstDtd(fileName);
×
281
            allValid = allValid && valid;
×
282
        }
×
283
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
284
    }
×
285

286
    /**
287
     * Reads and writes the rulesets to make sure, that no data is lost if the
288
     * rulests are processed.
289
     *
290
     * @throws Exception
291
     *             any error
292
     */
293
    @Test
294
    void testReadWriteRoundTrip() throws Exception {
295

296
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
297
        for (String fileName : ruleSetFileNames) {
×
298
            testRuleSet(fileName);
×
299
        }
×
300

301
    }
×
302

303
    // Gets all test PMD Ruleset XML files
304
    private List<String> getRuleSetFileNames() throws IOException {
305
        List<String> result = new ArrayList<>();
×
306

307
        for (Language language : LanguageRegistry.PMD.getLanguages()) {
×
308
            if (this.languagesToSkip.contains(language.getId())) {
×
309
                continue;
×
310
            }
311
            result.addAll(getRuleSetFileNames(language.getId()));
×
312
        }
×
313

314
        return result;
×
315
    }
316

317
    private List<String> getRuleSetFileNames(String language) throws IOException {
318
        List<String> ruleSetFileNames = new ArrayList<>();
×
319
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "rulesets/" + language + "/rulesets.properties"));
×
320
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "category/" + language + "/categories.properties"));
×
321
        return ruleSetFileNames;
×
322
    }
323

324
    private List<String> getRuleSetFileNames(String language, String propertiesPath) throws IOException {
325
        List<String> ruleSetFileNames = new ArrayList<>();
×
326
        Properties properties = new Properties();
×
327
        @SuppressWarnings("PMD.CloseResource")
328
        InputStream input = loadResourceAsStream(propertiesPath);
×
329
        if (input == null) {
×
330
            // this might happen if a language is only support by CPD, but not
331
            // by PMD
332
            System.err.println("No rulesets found for language " + language + " at " + propertiesPath);
×
333
            return Collections.emptyList();
×
334
        }
335
        try (InputStream is = input) {
×
336
            properties.load(is);
×
337
        }
338
        String fileNames = properties.getProperty("rulesets.filenames");
×
339
        StringTokenizer st = new StringTokenizer(fileNames, ",");
×
340
        while (st.hasMoreTokens()) {
×
341
            ruleSetFileNames.add(st.nextToken());
×
342
        }
343
        return ruleSetFileNames;
×
344
    }
345

346
    private RuleSet loadRuleSetByFileName(String ruleSetFileName) {
347
        final StringBuilder messages = new StringBuilder();
×
348
        class Reporter extends MessageReporterBase {
×
349
            @Override
350
            protected void logImpl(Level level, String message) {
351
                messages.append(message).append(System.lineSeparator());
×
352
            }
×
353
        }
354

355
        RuleSet ruleSet = InternalApiBridge.withReporter(new RuleSetLoader(), new Reporter())
×
356
                .loadFromResource(ruleSetFileName);
×
357

358
        assertThat("There should be no warnings while loading the ruleset",
×
359
                messages.toString(), emptyString());
×
360

361
        return ruleSet;
×
362
    }
363

364
    private boolean validateAgainstSchema(String fileName) throws IOException, SAXException {
365
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
366
            boolean valid = validateAgainstSchema(inputStream);
×
367
            if (!valid) {
×
368
                System.err.println("Validation against XML Schema failed for: " + fileName);
×
369
            }
370
            return valid;
×
371
        }
372
    }
373

374
    private boolean validateAgainstSchema(InputStream inputStream) throws IOException, SAXException {
375

376
        saxParser.parse(inputStream, validateDefaultHandler.resetValid());
×
377
        inputStream.close();
×
378
        return validateDefaultHandler.isValid();
×
379
    }
380

381
    private boolean validateAgainstDtd(String fileName) throws IOException, SAXException {
382
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
383
            boolean valid = validateAgainstDtd(inputStream);
×
384
            if (!valid) {
×
385
                System.err.println("Validation against DTD failed for: " + fileName);
×
386
            }
387
            return valid;
×
388
        }
389
    }
390

391
    private boolean validateAgainstDtd(InputStream inputStream) throws IOException, SAXException {
392

393
        // Read file into memory
394
        String file = readFullyToString(inputStream);
×
395
        inputStream.close();
×
396

397
        String rulesetNamespace = RuleSetWriter.RULESET_2_0_0_NS_URI;
×
398

399
        // Remove XML Schema stuff, replace with DTD
400
        file = file.replaceAll("<\\?xml [ a-zA-Z0-9=\".-]*\\?>", "");
×
401
        file = file.replaceAll("xmlns=\"" + rulesetNamespace + "\"", "");
×
402
        file = file.replaceAll("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
×
403
        file = file.replaceAll("xsi:schemaLocation=\"" + rulesetNamespace
×
404
                + " https://pmd.sourceforge.io/ruleset_\\d_0_0.xsd\"", "");
405

406
        if (RuleSetWriter.RULESET_2_0_0_NS_URI.equals(rulesetNamespace)) {
×
407
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
408
                + "<!DOCTYPE ruleset SYSTEM \"https://pmd.sourceforge.io/ruleset_2_0_0.dtd\">" + System.lineSeparator()
×
409
                + file;
410
        } else {
411
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
412
                + "<!DOCTYPE ruleset>" + System.lineSeparator()
×
413
                + file;
414
        }
415

416
        try (InputStream modifiedStream = new ByteArrayInputStream(file.getBytes())) {
×
417
            saxParser.parse(modifiedStream, validateDefaultHandler.resetValid());
×
418
        }
419
        return validateDefaultHandler.isValid();
×
420
    }
421

422
    private String readFullyToString(InputStream inputStream) throws IOException {
423
        StringBuilder buf = new StringBuilder(64 * 1024);
×
424
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
×
425
            String line;
426
            while ((line = reader.readLine()) != null) {
×
427
                buf.append(line);
×
428
                buf.append(System.lineSeparator());
×
429
            }
430
            return buf.toString();
×
431
        }
432
    }
433

434
    private static InputStream loadResourceAsStream(String resource) {
435
        return AbstractRuleSetFactoryTest.class.getClassLoader().getResourceAsStream(resource);
×
436
    }
437

438
    private void testRuleSet(String fileName) throws IOException, SAXException {
439

440
        // Load original XML
441
        // String xml1 =
442
        // readFullyToString(ResourceLoader.loadResourceAsStream(fileName));
443
        // System.out.println("xml1: " + xml1);
444

445
        // Load the original RuleSet
446
        RuleSet ruleSet1 = loadRuleSetByFileName(fileName);
×
447

448
        // Write to XML, first time
449
        ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
×
450
        RuleSetWriter writer1 = new RuleSetWriter(outputStream1);
×
451
        writer1.write(ruleSet1);
×
452
        writer1.close();
×
453
        String xml2 = new String(outputStream1.toByteArray());
×
454
        // System.out.println("xml2: " + xml2);
455

456
        // Read RuleSet from XML, first time
457
        RuleSetLoader loader = new RuleSetLoader();
×
458
        RuleSet ruleSet2 = loader.loadFromString("readRuleSet1.xml", xml2);
×
459

460
        // Do write/read a 2nd time, just to be sure
461

462
        // Write to XML, second time
463
        ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
×
464
        RuleSetWriter writer2 = new RuleSetWriter(outputStream2);
×
465
        writer2.write(ruleSet2);
×
466
        writer2.close();
×
467
        String xml3 = new String(outputStream2.toByteArray());
×
468
        // System.out.println("xml3: " + xml3);
469

470
        // Read RuleSet from XML, second time
471
        RuleSet ruleSet3 = loader.loadFromString("readRuleSet2.xml", xml3);
×
472

473
        // The 2 written XMLs should all be valid w.r.t Schema/DTD
474
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml2.getBytes())),
×
475
                "1st roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
476
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml3.getBytes())),
×
477
                "2nd roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
478
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml2.getBytes())),
×
479
                "1st roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
480
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml3.getBytes())),
×
481
                "2nd roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
482

483
        // All 3 versions of the RuleSet should be the same
484
        assertEqualsRuleSet("Original RuleSet and 1st roundtrip Ruleset not the same (filename: " + fileName + ")",
×
485
                ruleSet1, ruleSet2);
486
        assertEqualsRuleSet("1st roundtrip Ruleset and 2nd roundtrip RuleSet not the same (filename: " + fileName + ")",
×
487
                ruleSet2, ruleSet3);
488

489
        // It's hard to compare the XML DOMs. At least the roundtrip ones should
490
        // textually be the same.
491
        assertEquals(xml2, xml3,
×
492
                "1st roundtrip RuleSet XML and 2nd roundtrip RuleSet XML (filename: " + fileName + ")");
493
    }
×
494

495
    private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleSet2) {
496
        assertEquals(ruleSet1.getName(), ruleSet2.getName(), message + ", RuleSet name");
×
497
        assertEquals(ruleSet1.getDescription(), ruleSet2.getDescription(), message + ", RuleSet description");
×
498
        assertEquals(ruleSet1.getFileExclusions(), ruleSet2.getFileExclusions(),
×
499
                message + ", RuleSet exclude patterns");
500
        assertEquals(ruleSet1.getFileInclusions(), ruleSet2.getFileInclusions(),
×
501
                message + ", RuleSet include patterns");
502
        assertEquals(ruleSet1.getRules().size(), ruleSet2.getRules().size(), message + ", RuleSet rule count");
×
503

504
        for (int i = 0; i < ruleSet1.getRules().size(); i++) {
×
505
            Rule rule1 = ((List<Rule>) ruleSet1.getRules()).get(i);
×
506
            Rule rule2 = ((List<Rule>) ruleSet2.getRules()).get(i);
×
507

508
            assertFalse(rule1 instanceof RuleReference != rule2 instanceof RuleReference,
×
509
                    message + ", Different RuleReference");
510

511
            if (rule1 instanceof RuleReference) {
×
512
                RuleReference ruleReference1 = (RuleReference) rule1;
×
513
                RuleReference ruleReference2 = (RuleReference) rule2;
×
514
                assertEquals(ruleReference1.getOverriddenMinimumLanguageVersion(),
×
515
                        ruleReference2.getOverriddenMinimumLanguageVersion(),
×
516
                        message + ", RuleReference overridden minimum language version");
517
                assertEquals(ruleReference1.getOverriddenMaximumLanguageVersion(),
×
518
                        ruleReference2.getOverriddenMaximumLanguageVersion(),
×
519
                        message + ", RuleReference overridden maximum language version");
520
                assertEquals(ruleReference1.isOverriddenDeprecated(), ruleReference2.isOverriddenDeprecated(),
×
521
                        message + ", RuleReference overridden deprecated");
522
                assertEquals(ruleReference1.getOverriddenName(), ruleReference2.getOverriddenName(),
×
523
                        message + ", RuleReference overridden name");
524
                assertEquals(ruleReference1.getOverriddenDescription(), ruleReference2.getOverriddenDescription(),
×
525
                        message + ", RuleReference overridden description");
526
                assertEquals(ruleReference1.getOverriddenMessage(), ruleReference2.getOverriddenMessage(),
×
527
                        message + ", RuleReference overridden message");
528
                assertEquals(ruleReference1.getOverriddenExternalInfoUrl(), ruleReference2.getOverriddenExternalInfoUrl(),
×
529
                        message + ", RuleReference overridden external info url");
530
                assertEquals(ruleReference1.getOverriddenPriority(), ruleReference2.getOverriddenPriority(),
×
531
                        message + ", RuleReference overridden priority");
532
                assertEquals(ruleReference1.getOverriddenExamples(), ruleReference2.getOverriddenExamples(),
×
533
                        message + ", RuleReference overridden examples");
534
            }
535

536
            assertEquals(rule1.getName(), rule2.getName(), message + ", Rule name");
×
537
            assertEquals(rule1.getRuleClass(), rule2.getRuleClass(), message + ", Rule class");
×
538
            assertEquals(rule1.getDescription(), rule2.getDescription(),
×
539
                    message + ", Rule description " + rule1.getName());
×
540
            assertEquals(rule1.getMessage(), rule2.getMessage(), message + ", Rule message");
×
541
            assertEquals(rule1.getExternalInfoUrl(), rule2.getExternalInfoUrl(), message + ", Rule external info url");
×
542
            assertEquals(rule1.getPriority(), rule2.getPriority(), message + ", Rule priority");
×
543
            assertEquals(rule1.getExamples(), rule2.getExamples(), message + ", Rule examples");
×
544

545
            List<PropertyDescriptor<?>> propertyDescriptors1 = rule1.getPropertyDescriptors();
×
546
            List<PropertyDescriptor<?>> propertyDescriptors2 = rule2.getPropertyDescriptors();
×
547
            assertEquals(propertyDescriptors1, propertyDescriptors2, message + ", Rule property descriptor ");
×
548
            for (int j = 0; j < propertyDescriptors1.size(); j++) {
×
549
                Object value1 = rule1.getProperty(propertyDescriptors1.get(j));
×
550
                Object value2 = rule2.getProperty(propertyDescriptors2.get(j));
×
551
                // special case for Pattern, there is no equals method
552
                if (value1 instanceof Pattern && value2 instanceof Pattern) {
×
553
                    value1 = ((Pattern) value1).pattern();
×
554
                    value2 = ((Pattern) value2).pattern();
×
555
                }
556
                assertEquals(value1, value2, message + ", Rule " + rule1.getName() + " property "
×
557
                    + propertyDescriptors1.get(j).name());
×
558
            }
559
            assertEquals(propertyDescriptors1.size(), propertyDescriptors2.size(),
×
560
                    message + ", Rule property descriptor count");
561
        }
562
    }
×
563

564
    /**
565
     * Validator for the SAX parser
566
     */
567
    private static class ValidateDefaultHandler extends DefaultHandler {
568
        private boolean valid = true;
×
569
        private final Map<String, String> schemaMapping;
570

571
        ValidateDefaultHandler() {
×
572
            schemaMapping = new HashMap<>();
×
573
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.xsd", "ruleset_2_0_0.xsd");
×
574
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.dtd", "ruleset_2_0_0.dtd");
×
575
        }
×
576

577
        public ValidateDefaultHandler resetValid() {
578
            valid = true;
×
579
            return this;
×
580
        }
581

582
        public boolean isValid() {
583
            return valid;
×
584
        }
585

586
        @Override
587
        public void error(SAXParseException e) {
588
            log("Error", e);
×
589
        }
×
590

591
        @Override
592
        public void fatalError(SAXParseException e) {
593
            log("FatalError", e);
×
594
        }
×
595

596
        @Override
597
        public void warning(SAXParseException e) {
598
            log("Warning", e);
×
599
        }
×
600

601
        private void log(String prefix, SAXParseException e) {
602
            String message = prefix + " at (" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage();
×
603
            System.err.println(message);
×
604
            valid = false;
×
605
        }
×
606

607
        @Override
608
        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
609
            String resource = schemaMapping.get(systemId);
×
610

611
            if (resource != null) {
×
612
                InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource);
×
613
                if (inputStream == null) {
×
614
                    throw new FileNotFoundException(resource);
×
615
                }
616
                return new InputSource(inputStream);
×
617
            }
618
            throw new IllegalArgumentException(
×
619
                    "No clue how to handle: publicId=" + publicId + ", systemId=" + systemId);
620
        }
621
    }
622

623
}
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