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

pmd / pmd / 4515

27 Mar 2025 12:01PM UTC coverage: 77.853% (+0.06%) from 77.796%
4515

push

github

adangel
Fix #5590: [java] LiteralsFirstInComparisons with constant field (#5595)

Merge pull request #5595 from oowekyala:issue5590-literal-comparison

17580 of 23536 branches covered (74.69%)

Branch coverage included in aggregate %.

106 of 110 new or added lines in 11 files covered. (96.36%)

62 existing lines in 5 files now uncovered.

38483 of 48475 relevant lines covered (79.39%)

0.8 hits per line

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

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

5
package net.sourceforge.pmd;
6

7
import static net.sourceforge.pmd.lang.document.InternalApiBridge.newCollector;
8
import static net.sourceforge.pmd.lang.rule.InternalApiBridge.loadRuleSetsWithoutException;
9
import static net.sourceforge.pmd.lang.rule.InternalApiBridge.ruleSetApplies;
10
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
11

12
import java.nio.file.Path;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Objects;
21
import java.util.Set;
22

23
import org.slf4j.Logger;
24
import org.slf4j.LoggerFactory;
25
import org.slf4j.event.Level;
26

27
import net.sourceforge.pmd.benchmark.TimeTracker;
28
import net.sourceforge.pmd.benchmark.TimedOperation;
29
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
30
import net.sourceforge.pmd.cache.internal.AnalysisCacheListener;
31
import net.sourceforge.pmd.cache.internal.NoopAnalysisCache;
32
import net.sourceforge.pmd.internal.LogMessages;
33
import net.sourceforge.pmd.internal.util.ClasspathClassLoader;
34
import net.sourceforge.pmd.internal.util.FileCollectionUtil;
35
import net.sourceforge.pmd.internal.util.IOUtil;
36
import net.sourceforge.pmd.lang.InternalApiBridge;
37
import net.sourceforge.pmd.lang.JvmLanguagePropertyBundle;
38
import net.sourceforge.pmd.lang.Language;
39
import net.sourceforge.pmd.lang.LanguageProcessor.AnalysisTask;
40
import net.sourceforge.pmd.lang.LanguageProcessorRegistry;
41
import net.sourceforge.pmd.lang.LanguageProcessorRegistry.LanguageTerminationException;
42
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
43
import net.sourceforge.pmd.lang.LanguageRegistry;
44
import net.sourceforge.pmd.lang.LanguageVersion;
45
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
46
import net.sourceforge.pmd.lang.document.FileCollector;
47
import net.sourceforge.pmd.lang.document.TextFile;
48
import net.sourceforge.pmd.lang.rule.Rule;
49
import net.sourceforge.pmd.lang.rule.RuleSet;
50
import net.sourceforge.pmd.lang.rule.RuleSetLoader;
51
import net.sourceforge.pmd.lang.rule.internal.RuleSets;
52
import net.sourceforge.pmd.renderers.AbstractAccumulatingRenderer;
53
import net.sourceforge.pmd.renderers.Renderer;
54
import net.sourceforge.pmd.reporting.ConfigurableFileNameRenderer;
55
import net.sourceforge.pmd.reporting.DeterministicOutputListenerWrapper;
56
import net.sourceforge.pmd.reporting.FileAnalysisListener;
57
import net.sourceforge.pmd.reporting.GlobalAnalysisListener;
58
import net.sourceforge.pmd.reporting.ListenerInitializer;
59
import net.sourceforge.pmd.reporting.Report;
60
import net.sourceforge.pmd.reporting.Report.GlobalReportBuilderListener;
61
import net.sourceforge.pmd.reporting.ReportStats;
62
import net.sourceforge.pmd.reporting.ReportStatsListener;
63
import net.sourceforge.pmd.util.AssertionUtil;
64
import net.sourceforge.pmd.util.CollectionUtil;
65
import net.sourceforge.pmd.util.StringUtil;
66
import net.sourceforge.pmd.util.log.PmdReporter;
67

68
/**
69
 * Main programmatic API of PMD. This is not a CLI entry point, see module
70
 * {@code pmd-cli} for that.
71
 *
72
 * <h2>Usage overview</h2>
73
 *
74
 * <p>Create and configure a {@link PMDConfiguration},
75
 * then use {@link #create(PMDConfiguration)} to obtain an instance.
76
 * You can perform additional configuration on the instance, e.g. adding
77
 * files to process, or additional rulesets and renderers. Then, call
78
 * {@link #performAnalysis()} or one of the related terminal methods.
79
 *
80
 * <h2>Simple example</h2>
81
 *
82
 * <pre>{@code
83
 *   PMDConfiguration config = new PMDConfiguration();
84
 *   config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
85
 *   config.addInputPath(Path.of("src/main/java"));
86
 *   config.prependClasspath("target/classes");
87
 *   config.setMinimumPriority(RulePriority.HIGH);
88
 *   config.addRuleSet("rulesets/java/quickstart.xml");
89
 *   config.setReportFormat("xml");
90
 *   config.setReportFile("target/pmd-report.xml");
91
 *
92
 *   try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
93
 *     // note: don't use `config` once a PmdAnalysis has been created.
94
 *     // optional: add more rulesets
95
 *     pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml"));
96
 *     // optional: add more files
97
 *     pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
98
 *     // optional: add more renderers
99
 *     pmd.addRenderer(renderer);
100
 *
101
 *     pmd.performAnalysis();
102
 *   }
103
 * }</pre>
104
 *
105
 * <h2>Rendering reports</h2>
106
 *
107
 * <p>If you just want to render a report to a file like with the CLI, you
108
 * should use a {@link Renderer}. You can add a custom one with {@link PmdAnalysis#addRenderer(Renderer)}.
109
 * You can add one of the builtin renderers from its ID using {@link PMDConfiguration#setReportFormat(String)}.
110
 *
111
 * <h2>Reports and events</h2>
112
 *
113
 * <p>If you want strongly typed access to violations and other analysis events,
114
 * you can implement and register a {@link GlobalAnalysisListener} with {@link #addListener(GlobalAnalysisListener)}.
115
 * The listener needs to provide a new {@link FileAnalysisListener} for each file,
116
 * which will receive events from the analysis. The listener's lifecycle
117
 * happens only once the analysis is started ({@link #performAnalysis()}).
118
 *
119
 * <p>If you want access to all events once the analysis ends instead of processing
120
 * events as they go, you can obtain a {@link Report} instance from {@link #performAnalysisAndCollectReport()},
121
 * or use {@link Report.GlobalReportBuilderListener} manually. Keep in
122
 * mind collecting a report is less memory-efficient than using a listener.
123
 *
124
 * <p>If you want to process events in batches, one per file, you can
125
 * use {@link Report.ReportBuilderListener}. to implement {@link GlobalAnalysisListener#startFileAnalysis(TextFile)}.
126
 *
127
 * <p>Listeners can be used alongside renderers.
128
 *
129
 * <h2>Specifying the Java classpath</h2>
130
 *
131
 * <p>Java rules work better if you specify the path to the compiled classes
132
 * of the analysed sources. See {@link PMDConfiguration#prependAuxClasspath(String)}.
133
 *
134
 * <h2>Customizing message output</h2>
135
 *
136
 * <p>The analysis reports messages like meta warnings and errors through a
137
 * {@link PmdReporter} instance. To override how those messages are output,
138
 * you can set it in {@link PMDConfiguration#setReporter(PmdReporter)}.
139
 * By default, it forwards messages to SLF4J.
140
 *
141
 */
142
public final class PmdAnalysis implements AutoCloseable {
143

144
    private static final Logger LOG = LoggerFactory.getLogger(PmdAnalysis.class);
1✔
145

146
    private final FileCollector collector;
147
    private final List<Renderer> renderers = new ArrayList<>();
1✔
148
    private final List<GlobalAnalysisListener> listeners = new ArrayList<>();
1✔
149
    private final List<RuleSet> ruleSets = new ArrayList<>();
1✔
150
    private final PMDConfiguration configuration;
151
    private final PmdReporter reporter;
152

153
    private final Map<Language, LanguagePropertyBundle> langProperties = new HashMap<>();
1✔
154
    private boolean closed;
155
    private final ConfigurableFileNameRenderer fileNameRenderer = new ConfigurableFileNameRenderer();
1✔
156

157
    /**
158
     * Constructs a new instance. The files paths (input files, filelist,
159
     * exclude list, etc) given in the configuration are collected into
160
     * the file collector ({@link #files()}), but more can be added
161
     * programmatically using the file collector.
162
     */
163
    private PmdAnalysis(PMDConfiguration config) {
1✔
164
        this.configuration = config;
1✔
165
        this.reporter = config.getReporter();
1✔
166
        this.collector = newCollector(
1✔
167
            config.getLanguageVersionDiscoverer(),
1✔
168
            reporter
169
        );
170

171
    }
1✔
172

173
    /**
174
     * Constructs a new instance from a configuration.
175
     *
176
     * <ul>
177
     * <li> The files paths (input files, filelist,
178
     * exclude list, etc) are explored and the files to analyse are
179
     * collected into the file collector ({@link #files()}).
180
     * More can be added programmatically using the file collector.
181
     * <li>The rulesets given in the configuration are loaded ({@link PMDConfiguration#getRuleSetPaths()})
182
     * <li>A renderer corresponding to the parameters of the configuration
183
     * is created and added (but not started).
184
     * </ul>
185
     */
186
    public static PmdAnalysis create(PMDConfiguration config) {
187
        PmdAnalysis pmd = new PmdAnalysis(config);
1✔
188

189
        // note: do not filter files by language
190
        // they could be ignored later. The problem is if you call
191
        // addRuleSet later, then you could be enabling new languages
192
        // So the files should not be pruned in advance
193
        FileCollectionUtil.collectFiles(config, pmd.files());
1✔
194

195
        if (config.getReportFormat() != null) {
1!
UNCOV
196
            Renderer renderer = config.createRenderer(true);
×
UNCOV
197
            pmd.addRenderer(renderer);
×
198
        }
199

200
        if (!config.getRuleSetPaths().isEmpty()) {
1✔
201
            final RuleSetLoader ruleSetLoader = pmd.newRuleSetLoader();
1✔
202
            final List<RuleSet> ruleSets = loadRuleSetsWithoutException(ruleSetLoader, config.getRuleSetPaths());
1✔
203
            pmd.addRuleSets(ruleSets);
1✔
204
        }
205

206
        for (Language language : config.getLanguageRegistry()) {
1✔
207
            LanguagePropertyBundle props = config.getLanguageProperties(language);
1✔
208
            assert props.getLanguage().equals(language);
1!
209
            pmd.langProperties.put(language, props);
1✔
210

211
            LanguageVersion forcedVersion = config.getForceLanguageVersion();
1✔
212
            if (forcedVersion != null && forcedVersion.getLanguage().equals(language)) {
1✔
213
                props.setLanguageVersion(forcedVersion.getVersion());
1✔
214
            }
215

216
            // TODO replace those with actual language properties when the
217
            //  CLI syntax is implemented. #2947
218
            props.setProperty(LanguagePropertyBundle.SUPPRESS_MARKER, config.getSuppressMarker());
1✔
219
            if (props instanceof JvmLanguagePropertyBundle) {
1!
UNCOV
220
                ((JvmLanguagePropertyBundle) props).setClassLoader(config.getClassLoader());
×
221
            }
222
        }
1✔
223

224
        for (Path path : config.getRelativizeRoots()) {
1✔
225
            pmd.fileNameRenderer.relativizeWith(path);
1✔
226
        }
1✔
227

228
        return pmd;
1✔
229
    }
230

231
    // test only
232
    List<RuleSet> rulesets() {
233
        return ruleSets;
1✔
234
    }
235

236
    // test only
237
    List<Renderer> renderers() {
238
        return renderers;
1✔
239
    }
240

241

242
    /**
243
     * Returns the file collector for the analysed sources.
244
     */
245
    public FileCollector files() {
246
        return collector; // todo user can close collector programmatically
1✔
247
    }
248

249
    /**
250
     * Returns a new ruleset loader, which can be used to create new
251
     * rulesets (add them then with {@link #addRuleSet(RuleSet)}).
252
     *
253
     * <pre>{@code
254
     * try (PmdAnalysis pmd = create(config)) {
255
     *     pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml"));
256
     * }
257
     * }</pre>
258
     */
259
    public RuleSetLoader newRuleSetLoader() {
260
        return RuleSetLoader.fromPmdConfig(configuration);
1✔
261
    }
262

263
    /**
264
     * Add a new renderer. The given renderer must not already be started,
265
     * it will be started by {@link #performAnalysis()}.
266
     *
267
     * @throws NullPointerException If the parameter is null
268
     */
269
    public void addRenderer(Renderer renderer) {
270
        AssertionUtil.requireParamNotNull("renderer", renderer);
1✔
271
        this.renderers.add(renderer);
1✔
272
    }
1✔
273

274
    /**
275
     * Add several renderers at once.
276
     *
277
     * @throws NullPointerException If the parameter is null, or any of its items is null.
278
     */
279
    public void addRenderers(Collection<Renderer> renderers) {
UNCOV
280
        renderers.forEach(this::addRenderer);
×
UNCOV
281
    }
×
282

283
    /**
284
     * Add a new listener. As per the contract of {@link GlobalAnalysisListener},
285
     * this object must be ready for interaction. However, nothing will
286
     * be done with the listener until {@link #performAnalysis()} is called.
287
     * The listener will be closed by {@link #performAnalysis()}, or
288
     * {@link #close()}, whichever happens first.
289
     *
290
     * @throws NullPointerException If the parameter is null
291
     */
292
    public void addListener(GlobalAnalysisListener listener) {
293
        AssertionUtil.requireParamNotNull("listener", listener);
1✔
294
        this.listeners.add(listener);
1✔
295
    }
1✔
296

297
    /**
298
     * Add several listeners at once.
299
     *
300
     * @throws NullPointerException If the parameter is null, or any of its items is null.
301
     * @see #addListener(GlobalAnalysisListener)
302
     */
303
    public void addListeners(Collection<? extends GlobalAnalysisListener> listeners) {
UNCOV
304
        listeners.forEach(this::addListener);
×
UNCOV
305
    }
×
306

307
    /**
308
     * Add a new ruleset.
309
     *
310
     * @throws NullPointerException If the parameter is null
311
     */
312
    public void addRuleSet(RuleSet ruleSet) {
313
        AssertionUtil.requireParamNotNull("rule set", ruleSet);
1✔
314
        this.ruleSets.add(ruleSet);
1✔
315
    }
1✔
316

317
    /**
318
     * Add several rulesets at once.
319
     *
320
     * @throws NullPointerException If the parameter is null, or any of its items is null.
321
     */
322
    public void addRuleSets(Collection<RuleSet> ruleSets) {
323
        ruleSets.forEach(this::addRuleSet);
1✔
324
    }
1✔
325

326
    /**
327
     * Returns an unmodifiable view of the ruleset list. That will be
328
     * processed.
329
     */
330
    public List<RuleSet> getRulesets() {
331
        return Collections.unmodifiableList(ruleSets);
1✔
332
    }
333

334

335
    /**
336
     * Returns a mutable bundle of language properties that are associated
337
     * to the given language (always the same for a given language).
338
     *
339
     * @param language A language, which must be registered
340
     */
341
    public LanguagePropertyBundle getLanguageProperties(Language language) {
UNCOV
342
        configuration.checkLanguageIsRegistered(language);
×
UNCOV
343
        return langProperties.computeIfAbsent(language, Language::newPropertyBundle);
×
344
    }
345

346

347
    public ConfigurableFileNameRenderer fileNameRenderer() {
348
        return fileNameRenderer;
1✔
349
    }
350

351
    /**
352
     * Run PMD with the current state of this instance. This will start
353
     * and finish the registered renderers, and close all
354
     * {@linkplain #addListener(GlobalAnalysisListener) registered listeners}.
355
     * All files collected in the {@linkplain #files() file collector} are
356
     * processed. This does not return a report, as the analysis results
357
     * are consumed by {@link GlobalAnalysisListener} instances (of which
358
     * Renderers are a special case). Note that this does
359
     * not throw, errors are instead accumulated into a {@link PmdReporter}.
360
     */
361
    public void performAnalysis() {
362
        performAnalysisImpl(Collections.emptyList());
1✔
363
    }
1✔
364

365
    /**
366
     * Run PMD with the current state of this instance. This will start
367
     * and finish the registered renderers. All files collected in the
368
     * {@linkplain #files() file collector} are processed. Returns the
369
     * output report. Note that this does not throw, errors are instead
370
     * accumulated into a {@link PmdReporter}.
371
     */
372
    public Report performAnalysisAndCollectReport() {
373
        try (GlobalReportBuilderListener reportBuilder = new GlobalReportBuilderListener()) {
1✔
374
            performAnalysisImpl(listOf(reportBuilder)); // closes the report builder
1✔
375
            return reportBuilder.getResultImpl();
1✔
376
        }
377
    }
378

379
    void performAnalysisImpl(List<? extends GlobalReportBuilderListener> extraListeners) {
380
        try (FileCollector files = collector) {
1✔
381
            files.filterLanguages(getApplicableLanguages(false));
1✔
382
            performAnalysisImpl(extraListeners, files.getCollectedFiles());
1✔
383
        }
384
    }
1✔
385

386
    void performAnalysisImpl(List<? extends GlobalReportBuilderListener> extraListeners, List<TextFile> textFiles) {
387
        RuleSets rulesets = new RuleSets(this.ruleSets);
1✔
388

389
        GlobalAnalysisListener listener;
390
        try {
391
            @SuppressWarnings("PMD.CloseResource")
392
            AnalysisCacheListener cacheListener = new AnalysisCacheListener(configuration.getAnalysisCache(),
1✔
393
                                                                            rulesets,
394
                                                                            configuration.getClassLoader(),
1✔
395
                                                                            textFiles);
396
            listener = GlobalAnalysisListener.tee(listOf(createComposedRendererListener(renderers),
1✔
397
                                                         GlobalAnalysisListener.tee(listeners),
1✔
398
                                                         GlobalAnalysisListener.tee(extraListeners),
1✔
399
                                                         cacheListener));
400
            
401
            // Initialize listeners
402
            try (ListenerInitializer initializer = listener.initializer()) {
1✔
403
                initializer.setNumberOfFilesToAnalyze(textFiles.size());
1✔
404
                initializer.setFilesToAnalyze(CollectionUtil.map(textFiles, TextFile::getFileId));
1✔
405
                initializer.setFileNameRenderer(fileNameRenderer());
1✔
406
            }
UNCOV
407
        } catch (Exception e) {
×
UNCOV
408
            reporter.errorEx("Exception while initializing analysis listeners", e);
×
UNCOV
409
            throw new RuntimeException("Exception while initializing analysis listeners", e);
×
410
        }
1✔
411

412
        try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) {
1✔
413
            for (final Rule rule : removeBrokenRules(rulesets)) {
1!
414
                // todo Just like we throw for invalid properties, "broken rules"
415
                // shouldn't be a "config error". This is the only instance of
416
                // config errors...
417
                // see https://github.com/pmd/pmd/issues/3901
UNCOV
418
                listener.onConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason()));
×
UNCOV
419
            }
×
420

421
            encourageToUseIncrementalAnalysis(configuration);
1✔
422

423
            try (LanguageProcessorRegistry lpRegistry = LanguageProcessorRegistry.create(
1✔
424
                // only start the applicable languages (and dependencies)
425
                new LanguageRegistry(getApplicableLanguages(true)),
1✔
426
                langProperties,
427
                reporter
428
            )) {
429
                // Note the analysis task is shared: all processors see
430
                // the same file list, which may contain files for other
431
                // languages.
432
                AnalysisTask analysisTask = InternalApiBridge.createAnalysisTask(
1✔
433
                    rulesets,
434
                    textFiles,
435
                    listener,
436
                    configuration.getThreads(),
1✔
437
                    configuration.getAnalysisCache(),
1✔
438
                    reporter,
439
                    lpRegistry
440
                );
441

442
                List<AutoCloseable> analyses = new ArrayList<>();
1✔
443
                try {
444
                    for (Language lang : lpRegistry.getLanguages()) {
1✔
445
                        analyses.add(lpRegistry.getProcessor(lang).launchAnalysis(analysisTask));
1✔
446
                    }
1✔
447
                } finally {
448
                    Exception e = IOUtil.closeAll(analyses);
1✔
449
                    if (e != null) {
1!
450
                        reporter.errorEx("Error while joining analysis", e);
×
451
                    }
452
                }
453

UNCOV
454
            } catch (LanguageTerminationException e) {
×
UNCOV
455
                reporter.errorEx("Error while closing language processors", e);
×
456
            }
1✔
457
        } finally {
458
            try {
459
                listener.close();
1✔
UNCOV
460
            } catch (Exception e) {
×
UNCOV
461
                reporter.errorEx("Exception while closing analysis listeners", e);
×
462
                // todo better exception
UNCOV
463
                throw new RuntimeException("Exception while closing analysis listeners", e);
×
464
            }
1✔
465
        }
466
    }
1✔
467

468

469
    private GlobalAnalysisListener createComposedRendererListener(List<Renderer> renderers) throws Exception {
470
        if (renderers.isEmpty()) {
1✔
471
            return GlobalAnalysisListener.noop();
1✔
472
        }
473

474
        boolean isAnyIncremental = false;
1✔
475

476
        List<GlobalAnalysisListener> rendererListeners = new ArrayList<>(renderers.size());
1✔
477
        for (Renderer renderer : renderers) {
1✔
478
            isAnyIncremental |= !(renderer instanceof AbstractAccumulatingRenderer);
1!
479

480
            try {
481
                @SuppressWarnings("PMD.CloseResource")
482
                GlobalAnalysisListener listener =
1✔
483
                    Objects.requireNonNull(renderer.newListener(), "Renderer should provide non-null listener");
1✔
484
                rendererListeners.add(listener);
1✔
UNCOV
485
            } catch (Exception ioe) {
×
486
                // close listeners so far, throw their close exception or the ioe
UNCOV
487
                IOUtil.ensureClosed(rendererListeners, ioe);
×
UNCOV
488
                throw AssertionUtil.shouldNotReachHere("ensureClosed should have thrown", ioe);
×
489
            }
1✔
490
        }
1✔
491
        GlobalAnalysisListener rendererListener = GlobalAnalysisListener.tee(rendererListeners);
1✔
492
        // If all are non-incremental then they do their own buffering and should sort the events.
493
        // If any is incremental then we need to reorder the events for deterministic output.
494
        if (isAnyIncremental) {
1!
495
            rendererListener = new DeterministicOutputListenerWrapper(rendererListener);
1✔
496
        }
497
        return rendererListener;
1✔
498
    }
499

500
    private Set<Language> getApplicableLanguages(boolean quiet) {
501
        Set<Language> languages = new HashSet<>();
1✔
502
        LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
1✔
503

504
        for (RuleSet ruleSet : ruleSets) {
1✔
505
            for (Rule rule : ruleSet.getRules()) {
1✔
506
                Language ruleLanguage = rule.getLanguage();
1✔
507
                Objects.requireNonNull(ruleLanguage, "Rule has no language " + rule);
1✔
508
                if (!languages.contains(ruleLanguage)) {
1!
509
                    LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage);
1✔
510
                    if (ruleSetApplies(rule, version)) {
1!
511
                        configuration.checkLanguageIsRegistered(ruleLanguage);
1✔
512
                        languages.add(ruleLanguage);
1✔
513
                        if (!quiet) {
1✔
514
                            LOG.trace("Using {} version ''{}''", version.getLanguage().getName(), version.getTerseName());
1✔
515
                        }
516
                    }
517
                }
518
            }
1✔
519
        }
1✔
520

521
        // collect all dependencies, they shouldn't be filtered out
522
        LanguageRegistry reg = configuration.getLanguageRegistry();
1✔
523
        boolean changed;
524
        do {
525
            changed = false;
1✔
526
            for (Language lang : new HashSet<>(languages)) {
1✔
527
                for (String depId : lang.getDependencies()) {
1!
UNCOV
528
                    Language depLang = reg.getLanguageById(depId);
×
UNCOV
529
                    if (depLang == null) {
×
530
                        // todo maybe report all then throw
UNCOV
531
                        throw new IllegalStateException(
×
UNCOV
532
                            "Language " + lang.getId() + " has unsatisfied dependencies: "
×
533
                                + depId + " is not found in " + reg
534
                        );
535
                    }
UNCOV
536
                    changed |= languages.add(depLang);
×
UNCOV
537
                }
×
538
            }
1✔
539
        } while (changed);
1!
540
        return languages;
1✔
541
    }
542

543
    /**
544
     * Remove and return the misconfigured rules from the rulesets and log them
545
     * for good measure.
546
     */
547
    private Set<Rule> removeBrokenRules(final RuleSets ruleSets) {
548
        final Set<Rule> brokenRules = new HashSet<>();
1✔
549
        ruleSets.removeDysfunctionalRules(brokenRules);
1✔
550

551
        for (final Rule rule : brokenRules) {
1!
UNCOV
552
            reporter.warn("Removed misconfigured rule: {0} cause: {1}",
×
553
                          rule.getName(), rule.dysfunctionReason());
×
UNCOV
554
        }
×
555

556
        return brokenRules;
1✔
557
    }
558

559

560
    public PmdReporter getReporter() {
561
        return reporter;
1✔
562
    }
563

564
    @Override
565
    public void close() {
566
        if (closed) {
1!
567
            return;
×
568
        }
569
        closed = true;
1✔
570
        collector.close();
1✔
571

572
        // close listeners if analysis is not run.
573
        IOUtil.closeAll(listeners);
1✔
574

575
        /*
576
         * Make sure it's our own classloader before attempting to close it....
577
         * Maven + Jacoco provide us with a cloaseable classloader that if closed
578
         * will throw a ClassNotFoundException.
579
         */
580
        if (configuration.getClassLoader() instanceof ClasspathClassLoader) {
1!
UNCOV
581
            IOUtil.tryCloseClassLoader(configuration.getClassLoader());
×
582
        }
583
    }
1✔
584

585
    public ReportStats runAndReturnStats() {
586
        if (getRulesets().isEmpty()) {
1!
587
            return ReportStats.empty();
×
588
        }
589

590
        @SuppressWarnings("PMD.CloseResource")
591
        ReportStatsListener listener = new ReportStatsListener();
1✔
592

593
        addListener(listener);
1✔
594

595
        try {
596
            performAnalysis();
1✔
UNCOV
597
        } catch (Exception e) {
×
UNCOV
598
            getReporter().errorEx("Exception during processing", e);
×
UNCOV
599
            ReportStats stats = listener.getResult();
×
UNCOV
600
            printErrorDetected(1 + stats.getNumErrors());
×
UNCOV
601
            return stats; // should have been closed
×
602
        }
1✔
603
        ReportStats stats = listener.getResult();
1✔
604

605
        if (stats.getNumErrors() > 0) {
1✔
606
            printErrorDetected(stats.getNumErrors());
1✔
607
        }
608

609
        return stats;
1✔
610
    }
611

612
    static void printErrorDetected(PmdReporter reporter, int errors) {
613
        String msg = LogMessages.errorDetectedMessage(errors, "PMD");
1✔
614
        // note: using error level here increments the error count of the reporter,
615
        // which we don't want.
616
        reporter.info(StringUtil.quoteMessageFormat(msg));
1✔
617
    }
1✔
618

619
    void printErrorDetected(int errors) {
620
        printErrorDetected(getReporter(), errors);
1✔
621
    }
1✔
622

623
    private static void encourageToUseIncrementalAnalysis(final PMDConfiguration configuration) {
624
        final PmdReporter reporter = configuration.getReporter();
1✔
625

626
        if (!configuration.isIgnoreIncrementalAnalysis()
1✔
627
            && configuration.getAnalysisCache() instanceof NoopAnalysisCache
1!
628
            && reporter.isLoggable(Level.WARN)) {
1✔
629
            final String version =
630
                PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-doc-" + PMDVersion.VERSION;
1!
631
            reporter.warn("This analysis could be faster, please consider using Incremental Analysis: "
1✔
632
                            + "https://docs.pmd-code.org/{0}/pmd_userdocs_incremental_analysis.html", version);
633
        }
634
    }
1✔
635

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