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

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

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

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

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

7
import java.io.BufferedReader;
8
import java.io.File;
9
import java.io.IOException;
10
import java.io.InputStream;
11
import java.io.InputStreamReader;
12
import java.io.UncheckedIOException;
13
import java.net.MalformedURLException;
14
import java.net.URI;
15
import java.net.URL;
16
import java.net.URLClassLoader;
17
import java.nio.charset.Charset;
18
import java.nio.file.FileSystem;
19
import java.nio.file.FileSystems;
20
import java.nio.file.Files;
21
import java.nio.file.Path;
22
import java.nio.file.Paths;
23
import java.util.ArrayList;
24
import java.util.Collections;
25
import java.util.Enumeration;
26
import java.util.HashMap;
27
import java.util.List;
28
import java.util.Map;
29
import java.util.Objects;
30
import java.util.Set;
31
import java.util.StringTokenizer;
32
import java.util.stream.Collectors;
33
import java.util.stream.Stream;
34

35
import org.apache.commons.lang3.StringUtils;
36
import org.checkerframework.checker.nullness.qual.Nullable;
37
import org.objectweb.asm.ClassReader;
38
import org.objectweb.asm.ClassVisitor;
39
import org.objectweb.asm.ModuleVisitor;
40
import org.objectweb.asm.Opcodes;
41
import org.slf4j.Logger;
42
import org.slf4j.LoggerFactory;
43

44
import net.sourceforge.pmd.util.AssertionUtil;
45

46
/**
47
 * Create a ClassLoader which loads classes using a CLASSPATH like String. If
48
 * the String looks like a URL to a file (e.g. starts with <code>file://</code>)
49
 * the file will be read with each line representing an path on the classpath.
50
 *
51
 * @author Edwin Chan
52
 */
53
public class ClasspathClassLoader extends URLClassLoader {
54

55
    private static final Logger LOG = LoggerFactory.getLogger(ClasspathClassLoader.class);
1✔
56

57
    String javaHome;
58

59
    private FileSystem fileSystem;
60
    private Map<String, Set<String>> packagesDirsToModules;
61

62
    static {
63
        registerAsParallelCapable();
1✔
64

65
        // Disable caching for jar files to prevent issues like #4899
66
        try {
67
            // Uses a pseudo URL to be able to call URLConnection#setDefaultUseCaches
68
            // with Java9+ there is a static method for that per protocol:
69
            // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/URLConnection.html#setDefaultUseCaches(java.lang.String,boolean)
70
            URI.create("jar:file:file.jar!/").toURL().openConnection().setDefaultUseCaches(false);
1✔
71
        } catch (IOException e) {
×
72
            throw new RuntimeException(e);
×
73
        }
1✔
74
    }
1✔
75

76
    public ClasspathClassLoader(List<File> files, ClassLoader parent) throws IOException {
77
        super(new URL[0], parent);
×
78
        for (URL url : fileToURL(files)) {
×
79
            addURL(url);
×
80
        }
×
81
    }
×
82

83
    public ClasspathClassLoader(String classpath, ClassLoader parent) throws IOException {
84
        super(new URL[0], parent);
1✔
85
        for (URL url : initURLs(classpath)) {
1✔
86
            addURL(url);
1✔
87
        }
1✔
88
    }
1✔
89

90
    private List<URL> fileToURL(List<File> files) throws IOException {
91
        List<URL> urlList = new ArrayList<>();
×
92
        for (File f : files) {
×
93
            urlList.add(createURLFromPath(f.getAbsolutePath()));
×
94
        }
×
95
        return urlList;
×
96
    }
97

98
    private List<URL> initURLs(String classpath) {
99
        AssertionUtil.requireParamNotNull("classpath", classpath);
1✔
100
        final List<URL> urls = new ArrayList<>();
1✔
101
        try {
102
            if (classpath.startsWith("file:")) {
1✔
103
                // Treat as file URL
104
                addFileURLs(urls, new URL(classpath));
1✔
105
            } else {
106
                // Treat as classpath
107
                addClasspathURLs(urls, classpath);
1✔
108
            }
109
        } catch (IOException e) {
×
110
            throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e);
×
111
        }
1✔
112
        return urls;
1✔
113
    }
114

115
    private void addClasspathURLs(final List<URL> urls, final String classpath) throws MalformedURLException {
116
        StringTokenizer toker = new StringTokenizer(classpath, File.pathSeparator);
1✔
117
        while (toker.hasMoreTokens()) {
1✔
118
            String token = toker.nextToken();
1✔
119
            LOG.debug("Adding classpath entry: <{}>", token);
1✔
120
            urls.add(createURLFromPath(token));
1✔
121
        }
1✔
122
    }
1✔
123

124
    private void addFileURLs(List<URL> urls, URL fileURL) throws IOException {
125
        try (BufferedReader in = new BufferedReader(new InputStreamReader(fileURL.openStream(), Charset.defaultCharset()))) {
1✔
126
            String line;
127
            while ((line = in.readLine()) != null) {
1✔
128
                LOG.debug("Read classpath entry line: <{}>", line);
1✔
129
                line = line.trim();
1✔
130
                if (line.length() > 0 && line.charAt(0) != '#') {
1✔
131
                    LOG.debug("Adding classpath entry: <{}>", line);
1✔
132
                    urls.add(createURLFromPath(line));
1✔
133
                }
134
            }
135
        }
136
    }
1✔
137

138
    private URL createURLFromPath(String path) throws MalformedURLException {
139
        Path filePath = Paths.get(path).toAbsolutePath();
1✔
140
        if (filePath.endsWith(Paths.get("lib", "jrt-fs.jar"))) {
1!
141
            initializeJrtFilesystem(filePath);
×
142
            // don't add jrt-fs.jar to the normal aux classpath
143
            return null;
×
144
        }
145

146
        return filePath.toUri().normalize().toURL();
1✔
147
    }
148

149
    /**
150
     * Initializes a Java Runtime Filesystem that will be used to load class files.
151
     * This allows end users to provide in the aux classpath another Java Runtime version
152
     * than the one used for executing PMD.
153
     *
154
     * @param filePath path to the file "lib/jrt-fs.jar" inside the java installation directory.
155
     * @see <a href="https://openjdk.org/jeps/220">JEP 220: Modular Run-Time Images</a>
156
     */
157
    private void initializeJrtFilesystem(Path filePath) {
158
        try {
159
            LOG.debug("Detected Java Runtime Filesystem Provider in {}", filePath);
×
160

161
            if (fileSystem != null) {
×
162
                throw new IllegalStateException("There is already a jrt filesystem. Do you have multiple jrt-fs.jar files on the classpath?");
×
163
            }
164

165
            if (filePath.getNameCount() < 2) {
×
166
                throw new IllegalArgumentException("Can't determine java home from " + filePath + " - please provide a complete path.");
×
167
            }
168

169
            try (URLClassLoader loader = new URLClassLoader(new URL[] { filePath.toUri().toURL() })) {
×
170
                Map<String, String> env = new HashMap<>();
×
171
                // note: providing java.home here is crucial, so that the correct runtime image is loaded.
172
                // the class loader is only used to provide an implementation of JrtFileSystemProvider, if the current
173
                // Java runtime doesn't provide one (e.g. if running in Java 8).
174
                javaHome = filePath.getParent().getParent().toString();
×
175
                env.put("java.home", javaHome);
×
176
                LOG.debug("Creating jrt-fs with env {}", env);
×
177
                fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env, loader);
×
178
            }
179

180
            packagesDirsToModules = new HashMap<>();
×
181
            Path packages = fileSystem.getPath("packages");
×
182
            try (Stream<Path> packagesStream = Files.list(packages)) {
×
183
                packagesStream.forEach(p -> {
×
184
                    String packageName = p.getFileName().toString().replace('.', '/');
×
185
                    try (Stream<Path> modulesStream = Files.list(p)) {
×
186
                        Set<String> modules = modulesStream
×
187
                                .map(Path::getFileName)
×
188
                                .map(Path::toString)
×
189
                                .collect(Collectors.toSet());
×
190
                        packagesDirsToModules.put(packageName, modules);
×
191
                    } catch (IOException e) {
×
192
                        throw new UncheckedIOException(e);
×
193
                    }
×
194
                });
×
195
            }
196
        } catch (IOException e) {
×
197
            throw new UncheckedIOException(e);
×
198
        }
×
199
    }
×
200

201
    @Override
202
    public String toString() {
203
        return getClass().getSimpleName()
×
204
            + "[["
205
            + StringUtils.join(getURLs(), ":")
×
206
            + "] jrt-fs: " + javaHome + " parent: " + getParent() + ']';
×
207
    }
208

209
    private static final String MODULE_INFO_SUFFIX = "module-info.class";
210
    private static final String MODULE_INFO_SUFFIX_SLASH = "/" + MODULE_INFO_SUFFIX;
211
    // this is lazily initialized on first query of a module-info.class
212
    private Map<String, URL> moduleNameToModuleInfoUrls;
213

214
    private static @Nullable String extractModuleName(String name) {
215
        if (!name.endsWith(MODULE_INFO_SUFFIX_SLASH)) {
1✔
216
            return null;
1✔
217
        }
218
        return name.substring(0, name.length() - MODULE_INFO_SUFFIX_SLASH.length());
1✔
219
    }
220

221
    @Override
222
    public InputStream getResourceAsStream(String name) {
223
        // always first search in jrt-fs, if available
224
        // note: we can't override just getResource(String) and return a jrt:/-URL, because the URL itself
225
        // won't be connected to the correct JrtFileSystem and would just load using the system classloader.
226
        if (fileSystem != null) {
1!
227
            String moduleName = extractModuleName(name);
×
228
            if (moduleName != null) {
×
229
                LOG.trace("Trying to load module-info.class for module {} in jrt-fs", moduleName);
×
230
                Path candidate = fileSystem.getPath("modules", moduleName, MODULE_INFO_SUFFIX);
×
231
                if (Files.exists(candidate)) {
×
232
                    return newInputStreamFromJrtFilesystem(candidate);
×
233
                }
234
            }
235

236
            int lastSlash = name.lastIndexOf('/');
×
237
            String packageName = name.substring(0, Math.max(lastSlash, 0));
×
238
            Set<String> moduleNames = packagesDirsToModules.get(packageName);
×
239
            if (moduleNames != null) {
×
240
                LOG.trace("Trying to find {} in jrt-fs with packageName={} and modules={}",
×
241
                        name, packageName, moduleNames);
242

243
                for (String moduleCandidate : moduleNames) {
×
244
                    Path candidate = fileSystem.getPath("modules", moduleCandidate, name);
×
245
                    if (Files.exists(candidate)) {
×
246
                        return newInputStreamFromJrtFilesystem(candidate);
×
247
                    }
248
                }
×
249
            }
250
        }
251

252
        // search in the other jars of the aux classpath.
253
        // this will call this.getResource, which will do a child-first search, see below.
254
        return super.getResourceAsStream(name);
1✔
255
    }
256

257
    private static InputStream newInputStreamFromJrtFilesystem(Path path) {
258
        LOG.trace("Found {}", path);
×
259
        try {
260
            // Note: The input streams from JrtFileSystem are ByteArrayInputStreams and do not
261
            // need to be closed - we don't need to track these. The filesystem itself needs to be closed at the end.
262
            // See https://github.com/openjdk/jdk/blob/970cd202049f592946f9c1004ea92dbd58abf6fb/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java#L334
263
            return Files.newInputStream(path);
×
264
        } catch (IOException e) {
×
265
            throw new UncheckedIOException(e);
×
266
        }
267
    }
268

269
    private static class ModuleNameExtractor extends ClassVisitor {
270
        private String moduleName;
271

272
        protected ModuleNameExtractor() {
273
            super(Opcodes.ASM9);
1✔
274
        }
1✔
275

276
        @Override
277
        public ModuleVisitor visitModule(String name, int access, String version) {
278
            moduleName = name;
1✔
279
            return null;
1✔
280
        }
281

282
        public String getModuleName() {
283
            return moduleName;
1✔
284
        }
285
    }
286

287
    private void collectAllModules() {
288
        if (moduleNameToModuleInfoUrls != null) {
1✔
289
            return;
1✔
290
        }
291

292
        Map<String, URL> allModules = new HashMap<>();
1✔
293
        try {
294
            Enumeration<URL> moduleInfoUrls = findResources(MODULE_INFO_SUFFIX);
1✔
295
            collectModules(allModules, moduleInfoUrls);
1✔
296

297
            // also search in parents
298
            moduleInfoUrls = getParent().getResources(MODULE_INFO_SUFFIX);
1✔
299
            collectModules(allModules, moduleInfoUrls);
1✔
300

301
            LOG.debug("Found {} modules on auxclasspath", allModules.size());
1✔
302

303
            moduleNameToModuleInfoUrls = Collections.unmodifiableMap(allModules);
1✔
304
        } catch (IOException e) {
×
305
            throw new RuntimeException(e);
×
306
        }
1✔
307
    }
1✔
308

309
    private void collectModules(Map<String, URL> allModules, Enumeration<URL> moduleInfoUrls) throws IOException {
310
        while (moduleInfoUrls.hasMoreElements()) {
1✔
311
            URL url = moduleInfoUrls.nextElement();
1✔
312

313
            ModuleNameExtractor finder = new ModuleNameExtractor();
1✔
314
            try (InputStream inputStream = url.openStream()) {
1✔
315
                ClassReader classReader = new ClassReader(inputStream);
1✔
316
                classReader.accept(finder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
1✔
317
            }
318
            allModules.putIfAbsent(finder.getModuleName(), url);
1✔
319
        }
1✔
320
    }
1✔
321

322
    @Override
323
    public URL getResource(String name) {
324
        // Override to make it child-first. This is the method used by
325
        // pmd-java's type resolution to fetch classes, instead of loadClass.
326
        Objects.requireNonNull(name);
1✔
327

328
        String moduleName = extractModuleName(name);
1✔
329
        if (moduleName != null) {
1✔
330
            collectAllModules();
1✔
331
            assert moduleNameToModuleInfoUrls != null : "Modules should have been detected by collectAllModules()";
1!
332
            return moduleNameToModuleInfoUrls.get(moduleName);
1✔
333
        }
334

335
        URL url = findResource(name);
1✔
336
        if (url == null) {
1✔
337
            // note this will actually call back into this.findResource, but
338
            // we can't avoid this as the super implementation uses JDK internal
339
            // stuff that we can't copy down here.
340
            return super.getResource(name);
1✔
341
        }
342
        return url;
1✔
343
    }
344

345
    @Override
346
    protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
347
        throw new IllegalStateException("This class loader shouldn't be used to load classes");
×
348
    }
349

350
    @Override
351
    public void close() throws IOException {
352
        if (fileSystem != null) {
1!
353
            fileSystem.close();
×
354
            // jrt created an own classloader to load the JrtFileSystemProvider class out of the
355
            // jrt-fs.jar. This needs to be closed manually.
356
            ClassLoader classLoader = fileSystem.getClass().getClassLoader();
×
357
            if (classLoader instanceof URLClassLoader) {
×
358
                ((URLClassLoader) classLoader).close();
×
359
            }
360
            packagesDirsToModules = null;
×
361
            fileSystem = null;
×
362
        }
363
        super.close();
1✔
364
    }
1✔
365
}
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