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

pmd / pmd / 658

10 Jul 2026 12:22PM UTC coverage: 79.202%. Remained the same
658

push

github

web-flow
[core] refactor: AnalysisCache based on Path (#6841)

* [core] refactor: AnalysisCache based on Path

And not anymore based on URLs.
Moved out dependency to (URL)ClassLoader.
Added new utility AnalysisClasspathUtil to interpret with
the classpath.

* Refactor ClasspathClassLoader to reuse AnalysisClasspathUtil

* Fix unit tests (Windows...)

* Fix unit tests (Windows...) - Part 2

* Fix unit tests (Windows...) - Part 3

* Fix unit tests (Windows...) - Part 4

* Rename AnalysisClasspathUtil -> AuxClasspathUtil

* Fix compile errors

19192 of 25165 branches covered (76.26%)

Branch coverage included in aggregate %.

73 of 91 new or added lines in 10 files covered. (80.22%)

41622 of 51618 relevant lines covered (80.63%)

0.81 hits per line

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

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

5
package net.sourceforge.pmd.util.internal;
6

7
import java.io.File;
8
import java.io.IOException;
9
import java.net.URI;
10
import java.net.URISyntaxException;
11
import java.net.URL;
12
import java.net.URLClassLoader;
13
import java.nio.charset.Charset;
14
import java.nio.file.Files;
15
import java.nio.file.Path;
16
import java.nio.file.Paths;
17
import java.util.ArrayList;
18
import java.util.Collections;
19
import java.util.List;
20
import java.util.StringTokenizer;
21
import java.util.stream.Collectors;
22
import java.util.stream.Stream;
23

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

27
import net.sourceforge.pmd.PMDConfiguration;
28
import net.sourceforge.pmd.internal.util.IOUtil;
29

30
/**
31
 * Utilities to interpret a string-based classpath.
32
 * Can be used for the auxiliary classpath (analysis classpath) or runtime classpath.
33
 *
34
 * <p>The general format is a string consisting of entries separated by {@link File#pathSeparator}.
35
 * The entries are path names, that might point to a single jar file, a directory (which contains
36
 * class files) or a wildcard entry pointing to a directory.</p>
37
 *
38
 * <p>Additionally, PMD supports a classpath file. If the string starts with {@code file:}, then
39
 * the whole string is interpreted as a simple path name pointing to a text file (platform encoding).
40
 * Each line is interpreted as a single classpath entry. Lines starting with {@code #} are comments
41
 * and are ignored.</p>
42
 *
43
 * @see PMDConfiguration#prependAuxClasspath(String)
44
 * @see PMDConfiguration#getClassLoader()
45
 * @see PMDConfiguration#setClassLoader(ClassLoader)
46
 *
47
 * @since 7.27.0
48
 */
49
public final class AuxClasspathUtil {
50
    private static final Logger LOG = LoggerFactory.getLogger(AuxClasspathUtil.class);
1✔
51

52
    private AuxClasspathUtil() {}
53

54
    /**
55
     * Determines the currently used runtime classpath via the system property
56
     * {@code java.class.path}. Note: This doesn't include the platform classes
57
     * like "java.lang.Object".
58
     *
59
     * @see #getPlatformClasspath()
60
     */
61
    public static List<Path> getRuntimeClasspath() {
62
        return expandClasspath(System.getProperty("java.class.path"));
1✔
63
    }
64

65
    /**
66
     * Determines the platform classpath of the currently running JVM. This is either
67
     * the file {@code lib/rt.jar} or {@code lib/jrt-fs.jar}.
68
     *
69
     * <p>Note: The file {@code lib/jrt-fs.jar} doesn't contain the actual class files,
70
     * but provides a way to access the Modular Runtime Image.</p>
71
     *
72
     * @see <a href="https://openjdk.org/jeps/220">JEP 220: Modular Run-Time Images</a>
73
     */
74
    public static List<Path> getPlatformClasspath() {
NEW
75
        String javaHome = System.getProperty("java.home");
×
NEW
76
        Path jrtFsJar = Paths.get(javaHome, "lib", "jrt-fs.jar"); // Java 11+
×
NEW
77
        Path rtJar = Paths.get(javaHome, "lib", "rt.jar"); // Java 8
×
NEW
78
        if (Files.isRegularFile(jrtFsJar)) {
×
NEW
79
            LOG.debug("Found current JVM runtime classes at {}", jrtFsJar);
×
NEW
80
            return Collections.singletonList(jrtFsJar);
×
NEW
81
        } else if (Files.isRegularFile(rtJar)) {
×
NEW
82
            LOG.debug("Found current JVM runtime classes at {}", rtJar);
×
NEW
83
            return Collections.singletonList(rtJar);
×
84
        }
NEW
85
        throw new IllegalStateException("Could not determine current jvm classpath");
×
86
    }
87

88
    /**
89
     * Uses the given configuration to either return the classpath entries from an externally
90
     * provided classloader (soon to be deprecated functionality) or from the given auxClasspath
91
     * (CLI option {@code --aux-classpath}).
92
     *
93
     * @see #expandClasspath(String)
94
     */
95
    public static List<Path> getAuxClasspath(PMDConfiguration configuration) {
96
        List<Path> result = new ArrayList<>();
1✔
97

98
        ClassLoader classLoader = configuration.getClassLoader();
1✔
99
        try {
100
            if (classLoader instanceof URLClassLoader) {
1✔
101
                @SuppressWarnings("PMD.CloseResource") // we just need to get the URLs, don't close it here. the classloader will be needed later on...
102
                URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
1✔
103
                for (URL url : urlClassLoader.getURLs()) {
1✔
104
                    result.add(Paths.get(url.toURI()));
1✔
105
                }
106
            }
NEW
107
        } catch (URISyntaxException e) {
×
NEW
108
            throw new RuntimeException(e);
×
109
        }
1✔
110

111
        return result;
1✔
112
    }
113

114
    /**
115
     * Converts the single string into a list of valid path names. The path names are either single jar files
116
     * or directories.
117
     *
118
     * <p>Note: There is no check performed, whether the entries on the classpath actually exist.
119
     * Wildcard classpath entries are resolved to the actual jar files, so that we have syntactically
120
     * valid paths ("*" is an invalid character under Windows for path names).</p>
121
     *
122
     * <p>The path names are relative to the working directory, if they were relative before. If they
123
     * were provided as absolute names, then they are absolute.</p>
124
     *
125
     * @throws IllegalArgumentException if there is a problem while resolving wildcard classpath entries.
126
     */
127
    public static List<Path> expandClasspath(String classpath) {
128
        if (classpath == null) {
1✔
129
            return Collections.emptyList();
1✔
130
        }
131

132
        List<String> entries = new ArrayList<>();
1✔
133

134
        if (classpath.startsWith("file:")) {
1✔
135
            try {
136
                Path path;
137
                if (classpath.length() > 5 && classpath.charAt(5) == '/') {
1!
138
                    path = Paths.get(new URI(classpath));
1✔
139
                } else {
140
                    // support relative paths
141
                    path = Paths.get(classpath.substring(5));
1✔
142
                }
143

144
                try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
1✔
145
                    entries.addAll(lines
1✔
146
                            .map(String::trim)
1✔
147
                            .filter(s -> !s.isEmpty())
1✔
148
                            .filter(s -> !s.startsWith("#"))
1✔
149
                            .collect(Collectors.toList()));
1✔
150
                }
NEW
151
            } catch (IOException | URISyntaxException e) {
×
NEW
152
                throw new IllegalArgumentException(e);
×
153
            }
1✔
154
        } else {
155
            StringTokenizer toker = new StringTokenizer(classpath, File.pathSeparator);
1✔
156
            while (toker.hasMoreTokens()) {
1✔
157
                String token = toker.nextToken().trim();
1✔
158
                entries.add(token);
1✔
159
            }
1✔
160
        }
161

162
        List<Path> result = new ArrayList<>();
1✔
163
        for (String entry : entries) {
1✔
164
            if (entry.endsWith("/*") || entry.endsWith("\\*")) {
1!
165
                Path wildcardDirectory = Paths.get(entry.substring(0, entry.length() - 2));
1✔
166
                try (Stream<Path> stream = Files.list(wildcardDirectory)) {
1✔
167
                    result.addAll(stream
1✔
168
                            .filter(p -> "jar".equalsIgnoreCase(IOUtil.getFilenameExtension(p.getFileName().toString())))
1✔
169
                            .sorted() // make the results deterministic
1✔
170
                            .collect(Collectors.toList()));
1✔
NEW
171
                } catch (IOException e) {
×
NEW
172
                    throw new IllegalArgumentException("Error while resolving wildcard entry '" + entry + "'", e);
×
173
                }
1✔
174
            } else {
1✔
175
                result.add(Paths.get(entry));
1✔
176
            }
177
        }
1✔
178

179
        return result;
1✔
180
    }
181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc