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

pmd / pmd / #3787

pending completion
#3787

push

github actions

oowekyala
add a test for display name in the cache

67063 of 127681 relevant lines covered (52.52%)

0.53 hits per line

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

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

5
package net.sourceforge.pmd.cache;
6

7
import java.io.File;
8
import java.io.IOException;
9
import java.net.URL;
10
import java.net.URLClassLoader;
11
import java.nio.file.FileVisitOption;
12
import java.nio.file.FileVisitResult;
13
import java.nio.file.Files;
14
import java.nio.file.Path;
15
import java.nio.file.SimpleFileVisitor;
16
import java.nio.file.attribute.BasicFileAttributes;
17
import java.util.ArrayList;
18
import java.util.Collections;
19
import java.util.EnumSet;
20
import java.util.List;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.concurrent.ConcurrentMap;
23

24
import org.slf4j.Logger;
25
import org.slf4j.LoggerFactory;
26

27
import net.sourceforge.pmd.PMDVersion;
28
import net.sourceforge.pmd.Report.ProcessingError;
29
import net.sourceforge.pmd.RuleSets;
30
import net.sourceforge.pmd.RuleViolation;
31
import net.sourceforge.pmd.annotation.InternalApi;
32
import net.sourceforge.pmd.benchmark.TimeTracker;
33
import net.sourceforge.pmd.benchmark.TimedOperation;
34
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
35
import net.sourceforge.pmd.cache.internal.ClasspathFingerprinter;
36
import net.sourceforge.pmd.internal.util.IOUtil;
37
import net.sourceforge.pmd.lang.document.TextDocument;
38
import net.sourceforge.pmd.reporting.FileAnalysisListener;
39

40
/**
41
 * Abstract implementation of the analysis cache. Handles all operations, except for persistence.
42
 *
43
 * @deprecated This is internal API, will be hidden with 7.0.0
44
 */
45
@Deprecated
46
@InternalApi
47
public abstract class AbstractAnalysisCache implements AnalysisCache {
48

49
    protected static final Logger LOG = LoggerFactory.getLogger(AbstractAnalysisCache.class);
1✔
50
    protected static final ClasspathFingerprinter FINGERPRINTER = new ClasspathFingerprinter();
1✔
51
    protected final String pmdVersion;
52
    protected final ConcurrentMap<String, AnalysisResult> fileResultsCache = new ConcurrentHashMap<>();
1✔
53
    protected final ConcurrentMap<String, AnalysisResult> updatedResultsCache = new ConcurrentHashMap<>();
1✔
54
    protected final CachedRuleMapper ruleMapper = new CachedRuleMapper();
1✔
55
    protected long rulesetChecksum;
56
    protected long auxClassPathChecksum;
57
    protected long executionClassPathChecksum;
58

59
    /**
60
     * Creates a new empty cache
61
     */
62
    public AbstractAnalysisCache() {
1✔
63
        pmdVersion = PMDVersion.VERSION;
1✔
64
    }
1✔
65

66
    @Override
67
    public boolean isUpToDate(final TextDocument document) {
68
        try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.ANALYSIS_CACHE, "up-to-date check")) {
1✔
69
            final AnalysisResult cachedResult = fileResultsCache.get(document.getPathId());
1✔
70
            final AnalysisResult updatedResult;
71

72
            // is this a known file? has it changed?
73
            final boolean upToDate = cachedResult != null
1✔
74
                && cachedResult.getFileChecksum() == document.getCheckSum();
1✔
75

76
            if (upToDate) {
1✔
77
                LOG.trace("Incremental Analysis cache HIT");
1✔
78
                
79
                /*
80
                 * Update cached violation "filename" to match the appropriate text document,
81
                 * so we can honor relativized paths for the current run
82
                 */
83
                final String displayName = document.getDisplayName();
1✔
84
                cachedResult.getViolations().forEach(v -> ((CachedRuleViolation) v).setFileDisplayName(displayName));
1✔
85
                
86
                // copy results over
87
                updatedResult = cachedResult;
1✔
88
            } else {
1✔
89
                LOG.trace("Incremental Analysis cache MISS - {}",
1✔
90
                          cachedResult != null ? "file changed" : "no previous result found");
1✔
91
                
92
                // New file being analyzed, create new empty entry
93
                updatedResult = new AnalysisResult(document.getCheckSum(), new ArrayList<>());
1✔
94
            }
95

96
            updatedResultsCache.put(document.getPathId(), updatedResult);
1✔
97
            
98
            return upToDate;
1✔
99
        }
100
    }
101

102
    @Override
103
    public List<RuleViolation> getCachedViolations(final TextDocument sourceFile) {
104
        final AnalysisResult analysisResult = fileResultsCache.get(sourceFile.getPathId());
1✔
105

106
        if (analysisResult == null) {
1✔
107
            // new file, avoid nulls
108
            return Collections.emptyList();
×
109
        }
110

111
        return analysisResult.getViolations();
1✔
112
    }
113

114
    @Override
115
    public void analysisFailed(final TextDocument sourceFile) {
116
        updatedResultsCache.remove(sourceFile.getPathId());
×
117
    }
×
118

119

120
    /**
121
     * Returns true if the cache exists. If so, normal cache validity checks
122
     * will be performed. Otherwise, the cache is necessarily invalid (e.g. on a first run).
123
     */
124
    protected abstract boolean cacheExists();
125

126

127
    @Override
128
    public void checkValidity(final RuleSets ruleSets, final ClassLoader auxclassPathClassLoader) {
129
        try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.ANALYSIS_CACHE, "validity check")) {
1✔
130
            boolean cacheIsValid = cacheExists();
1✔
131

132
            if (cacheIsValid && ruleSets.getChecksum() != rulesetChecksum) {
1✔
133
                LOG.debug("Analysis cache invalidated, rulesets changed.");
1✔
134
                cacheIsValid = false;
1✔
135
            }
136

137
            final long currentAuxClassPathChecksum;
138
            if (auxclassPathClassLoader instanceof URLClassLoader) {
1✔
139
                // we don't want to close our aux classpath loader - we still need it...
140
                @SuppressWarnings("PMD.CloseResource") final URLClassLoader urlClassLoader = (URLClassLoader) auxclassPathClassLoader;
1✔
141
                currentAuxClassPathChecksum = FINGERPRINTER.fingerprint(urlClassLoader.getURLs());
1✔
142

143
                if (cacheIsValid && currentAuxClassPathChecksum != auxClassPathChecksum) {
1✔
144
                    // TODO some rules don't need that (in fact, some languages)
145
                    LOG.debug("Analysis cache invalidated, auxclasspath changed.");
1✔
146
                    cacheIsValid = false;
1✔
147
                }
148
            } else {
1✔
149
                currentAuxClassPathChecksum = 0;
1✔
150
            }
151

152
            final long currentExecutionClassPathChecksum = FINGERPRINTER.fingerprint(getClassPathEntries());
1✔
153
            if (cacheIsValid && currentExecutionClassPathChecksum != executionClassPathChecksum) {
1✔
154
                LOG.debug("Analysis cache invalidated, execution classpath changed.");
1✔
155
                cacheIsValid = false;
1✔
156
            }
157

158
            if (!cacheIsValid) {
1✔
159
                // Clear the cache
160
                fileResultsCache.clear();
1✔
161
            }
162

163
            // Update the local checksums
164
            rulesetChecksum = ruleSets.getChecksum();
1✔
165
            auxClassPathChecksum = currentAuxClassPathChecksum;
1✔
166
            executionClassPathChecksum = currentExecutionClassPathChecksum;
1✔
167
            ruleMapper.initialize(ruleSets);
1✔
168
        }
169
    }
1✔
170

171
    private static boolean isClassPathWildcard(String entry) {
172
        return entry.endsWith("/*") || entry.endsWith("\\*");
1✔
173
    }
174

175
    private URL[] getClassPathEntries() {
176
        final String classpath = System.getProperty("java.class.path");
1✔
177
        final String[] classpathEntries = classpath.split(File.pathSeparator);
1✔
178
        final List<URL> entries = new ArrayList<>();
1✔
179

180
        final SimpleFileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() {
1✔
181
            @Override
182
            public FileVisitResult visitFile(final Path file,
183
                                             final BasicFileAttributes attrs) throws IOException {
184
                if (!attrs.isSymbolicLink()) { // Broken link that can't be followed
1✔
185
                    entries.add(file.toUri().toURL());
1✔
186
                }
187
                return FileVisitResult.CONTINUE;
1✔
188
            }
189
        };
190
        final SimpleFileVisitor<Path> jarFileVisitor = new SimpleFileVisitor<Path>() {
1✔
191
            @Override
192
            public FileVisitResult visitFile(final Path file,
193
                                             final BasicFileAttributes attrs) throws IOException {
194
                String extension = IOUtil.getFilenameExtension(file.toString());
1✔
195
                if ("jar".equalsIgnoreCase(extension)) {
1✔
196
                    fileVisitor.visitFile(file, attrs);
1✔
197
                }
198
                return FileVisitResult.CONTINUE;
1✔
199
            }
200
        };
201

202
        try {
203
            for (final String entry : classpathEntries) {
1✔
204
                final File f = new File(entry);
1✔
205
                if (isClassPathWildcard(entry)) {
1✔
206
                    Files.walkFileTree(new File(entry.substring(0, entry.length() - 1)).toPath(),
1✔
207
                                       EnumSet.of(FileVisitOption.FOLLOW_LINKS), 1, jarFileVisitor);
1✔
208
                } else if (f.isFile()) {
1✔
209
                    entries.add(f.toURI().toURL());
1✔
210
                } else if (f.exists()) { // ignore non-existing directories
1✔
211
                    Files.walkFileTree(f.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
1✔
212
                                       fileVisitor);
213
                }
214
            }
215
        } catch (final IOException e) {
×
216
            LOG.error("Incremental analysis can't check execution classpath contents", e);
×
217
            throw new RuntimeException(e);
×
218
        }
1✔
219

220
        return entries.toArray(new URL[0]);
1✔
221
    }
222

223
    @Override
224
    public FileAnalysisListener startFileAnalysis(TextDocument file) {
225
        final String fileName = file.getPathId();
1✔
226

227
        return new FileAnalysisListener() {
1✔
228
            @Override
229
            public void onRuleViolation(RuleViolation violation) {
230
                updatedResultsCache.get(fileName).addViolation(violation);
1✔
231
            }
1✔
232

233
            @Override
234
            public void onError(ProcessingError error) {
235
                analysisFailed(file);
×
236
            }
×
237
        };
238
    }
239
}
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

© 2025 Coveralls, Inc