Skip to content

Commit a2209e2

Browse files
committed
chore: Avoid using classloader for auxclasspath settings (#353)
2 parents 8bb8ef7 + d57fe28 commit a2209e2

File tree

7 files changed

+312
-2
lines changed

7 files changed

+312
-2
lines changed

ReleaseNotes.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ This is a minor release.
2121

2222
### API Changes
2323

24+
#### Deprecations
25+
26+
The following methods have been deprecated for removal:
27+
28+
* `net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties#getAuxClasspath()`
29+
* `net.sourceforge.pmd.eclipse.runtime.properties.impl.ProjectPropertiesImpl#getAuxClasspath()`
30+
31+
Use the new method `getClasspath()` instead. It doesn't use a custom classloader anymore and just returns
32+
the classpath as a single string (path elements separated by the os specific path separator).
33+
34+
The following class has been deprecated for removal:
35+
36+
* `net.sourceforge.pmd.eclipse.runtime.cmd.JavaProjectClassLoader`
2437

2538
## 30-December-2025: 7.20.0.v20251230-1608-r
2639

net.sourceforge.pmd.eclipse.plugin.test/src/main/java/net/sourceforge/pmd/eclipse/runtime/properties/ProjectPropertiesModelTest.java

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
import java.net.URLClassLoader;
1616
import java.nio.charset.StandardCharsets;
1717
import java.nio.file.Files;
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
1820
import java.util.Collection;
1921
import java.util.HashSet;
2022
import java.util.LinkedList;
2123
import java.util.List;
2224
import java.util.Objects;
2325
import java.util.Set;
26+
import java.util.regex.Pattern;
2427

2528
import org.apache.commons.io.IOUtils;
2629
import org.eclipse.core.resources.IFile;
@@ -523,9 +526,11 @@ private void dumpRuleSet(final RuleSet ruleSet) {
523526
* </ul>
524527
*
525528
* @throws Exception
529+
* @deprecated Since 7.21.0. Tests the deprecated {@link IProjectProperties#getAuxClasspath()} method.
526530
*/
527531
@Test
528-
public void testProjectClasspath() throws Exception {
532+
@Deprecated
533+
public void testProjectClasspathClassloader() throws Exception {
529534
IProject otherProject = EclipseUtils.createJavaProject("OtherProject");
530535
additionalProjects.add(otherProject);
531536
IFile sampleLib1 = otherProject.getFile("sample-lib1.jar");
@@ -607,4 +612,95 @@ public void testProjectClasspath() throws Exception {
607612
// no remaining urls
608613
Assert.assertTrue(urls.isEmpty());
609614
}
615+
616+
/**
617+
* Project structure:
618+
* <ul>
619+
* <li>this.testProject "ProjectPropertiesModelTest": main project, with build path, contains lib/sample-lib3.jar</li>
620+
* <li>otherProject "OtherProject": contains sample-lib1.jar, sample-lib2.jar</li>
621+
* <li>otherProject2 "OtherProject2": ProjectPropertiesModelTest depends on this</li>
622+
* <li>externalProject "ExternalProject": not stored within workspace, contains sample-lib4.jar</li>
623+
* </ul>
624+
*
625+
* @throws Exception
626+
*/
627+
@Test
628+
public void testProjectClasspath() throws Exception {
629+
IProject otherProject = EclipseUtils.createJavaProject("OtherProject");
630+
additionalProjects.add(otherProject);
631+
IFile sampleLib1 = otherProject.getFile("sample-lib1.jar");
632+
sampleLib1.create(IOUtils.toInputStream("", "UTF-8"), false, null);
633+
File realSampleLib1 = sampleLib1.getLocation().toFile().getCanonicalFile();
634+
IFile sampleLib2 = otherProject.getFile("sample-lib2.jar");
635+
sampleLib2.create(IOUtils.toInputStream("", "UTF-8"), false, null);
636+
File realSampleLib2 = sampleLib2.getLocation().toFile().getCanonicalFile();
637+
638+
IFolder libFolder = this.testProject.getFolder("lib");
639+
libFolder.create(false, true, null);
640+
IFile sampleLib3 = libFolder.getFile("sample-lib3.jar");
641+
sampleLib3.create(IOUtils.toInputStream("", "UTF-8"), false, null);
642+
File realSampleLib3 = sampleLib3.getLocation().toFile().getCanonicalFile();
643+
644+
IProject otherProject2 = EclipseUtils.createJavaProject("OtherProject2");
645+
additionalProjects.add(otherProject2);
646+
// build the project, so that the output folder "bin/" is created
647+
otherProject2.build(IncrementalProjectBuilder.FULL_BUILD, null);
648+
649+
IProject externalProject = ResourcesPlugin.getWorkspace().getRoot().getProject("ExternalProject");
650+
additionalProjects.add(externalProject);
651+
Assert.assertFalse("Project must not exist yet", externalProject.exists());
652+
java.nio.file.Path externalProjectDir = Files.createTempDirectory("pmd-eclipse-plugin");
653+
IProjectDescription description = externalProject.getWorkspace().newProjectDescription("ExternalProject");
654+
description.setLocation(Path.fromOSString(externalProjectDir.toString()));
655+
externalProject.create(description, null);
656+
externalProject.open(null);
657+
IFile sampleLib4 = externalProject.getFile("sample-lib4.jar");
658+
sampleLib4.create(IOUtils.toInputStream("", "UTF-8"), false, null);
659+
File realSampleLib4 = sampleLib4.getLocation().toFile().getCanonicalFile();
660+
661+
// build the project, so that the output folder "bin/" is created
662+
this.testProject.build(IncrementalProjectBuilder.FULL_BUILD, null);
663+
664+
IFile file = this.testProject.getFile(".classpath");
665+
String newClasspathContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
666+
+ "<classpath>\n"
667+
+ " <classpathentry kind=\"src\" path=\"src\"/>\n"
668+
+ " <!-- <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/> -->\n"
669+
+ " <classpathentry combineaccessrules=\"false\" kind=\"src\" path=\"/OtherProject2\"/>\n"
670+
+ " <classpathentry kind=\"lib\" path=\"/OtherProject/sample-lib1.jar\"/>\n"
671+
+ " <classpathentry kind=\"lib\" path=\"" + realSampleLib2.getAbsolutePath() + "\"/>\n"
672+
+ " <classpathentry kind=\"lib\" path=\"lib/sample-lib3.jar\"/>\n"
673+
+ " <classpathentry kind=\"output\" path=\"bin\"/>\n"
674+
+ " <classpathentry kind=\"lib\" path=\"/ExternalProject/sample-lib4.jar\"/>\n"
675+
+ "</classpath>\n";
676+
file.setContents(IOUtils.toInputStream(newClasspathContent, "UTF-8"), 0, null);
677+
// refresh, so that changed .classpath file is considered
678+
this.testProject.refreshLocal(IResource.DEPTH_INFINITE, null);
679+
// rebuild again, so that changed classpath is configured on java project
680+
this.testProject.build(IncrementalProjectBuilder.FULL_BUILD, null);
681+
682+
final IProjectPropertiesManager mgr = PMDPlugin.getDefault().getPropertiesManager();
683+
IProjectProperties model = mgr.loadProjectProperties(this.testProject);
684+
List<String> classpath = new ArrayList<>(Arrays.asList(model.getClasspath().split(Pattern.quote(File.pathSeparator))));
685+
686+
Assert.assertEquals("Found these paths: " + classpath, 6, classpath.size());
687+
688+
// own project's output folder
689+
Assert.assertTrue(classpath.remove(
690+
new File(this.testProject.getLocation().toFile().getAbsoluteFile(), "bin").toString()));
691+
// output folder of other project 2 (project dependency)
692+
Assert.assertTrue(classpath.remove(
693+
new File(otherProject2.getLocation().toFile().getAbsoluteFile(), "bin").toString()));
694+
// sample-lib1.jar stored in OtherProject
695+
Assert.assertTrue(classpath.remove(realSampleLib1.toString()));
696+
// sample-lib2.jar referenced with absolute path
697+
Assert.assertTrue(classpath.remove(realSampleLib2.toString()));
698+
// sample-lib3.jar stored in own project folder lib
699+
Assert.assertTrue(classpath.remove(realSampleLib3.toString()));
700+
// sample-lib4.jar stored in external project folder outside of workspace
701+
Assert.assertTrue(classpath.remove(realSampleLib4.toString()));
702+
703+
// no remaining urls
704+
Assert.assertTrue(classpath.isEmpty());
705+
}
610706
}

net.sourceforge.pmd.eclipse.plugin/src/main/java/net/sourceforge/pmd/eclipse/runtime/cmd/BaseVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ protected final void reviewResource(IResource resource) {
239239
LOG.debug("discovered language: {}", languageVersion);
240240

241241
if (PMDPlugin.getDefault().loadPreferences().isProjectBuildPathEnabled()) {
242-
configuration().setClassLoader(projectProperties.getAuxClasspath());
242+
configuration().prependAuxClasspath(projectProperties.getClasspath());
243243
}
244244

245245
// Avoid warnings about not providing cache for incremental analysis

net.sourceforge.pmd.eclipse.plugin/src/main/java/net/sourceforge/pmd/eclipse/runtime/cmd/JavaProjectClassLoader.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@
2424
import org.slf4j.LoggerFactory;
2525

2626
import net.sourceforge.pmd.eclipse.core.internal.FileModificationUtil;
27+
import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties;
2728

2829
/**
2930
* This is a ClassLoader for the Build Path of an IJavaProject.
31+
*
32+
* @deprecated Since 7.21.0. This class is not needed anymore. See {@link IProjectProperties#getClasspath()}.
3033
*/
34+
@Deprecated
3135
public class JavaProjectClassLoader extends URLClassLoader {
3236
private static final Logger LOG = LoggerFactory.getLogger(JavaProjectClassLoader.class);
3337

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3+
*/
4+
5+
6+
package net.sourceforge.pmd.eclipse.runtime.cmd.internal;
7+
8+
import java.io.File;
9+
import java.util.ArrayList;
10+
import java.util.HashSet;
11+
import java.util.List;
12+
import java.util.Set;
13+
14+
import org.eclipse.core.resources.IFile;
15+
import org.eclipse.core.resources.IProject;
16+
import org.eclipse.core.resources.IWorkspace;
17+
import org.eclipse.core.runtime.CoreException;
18+
import org.eclipse.core.runtime.IPath;
19+
import org.eclipse.jdt.core.IClasspathEntry;
20+
import org.eclipse.jdt.core.IJavaProject;
21+
import org.eclipse.jdt.core.JavaCore;
22+
import org.eclipse.jdt.core.JavaModelException;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import net.sourceforge.pmd.eclipse.core.internal.FileModificationUtil;
27+
28+
public class JavaProjectClasspath {
29+
private static final Logger LOG = LoggerFactory.getLogger(JavaProjectClasspath.class);
30+
31+
private final IJavaProject javaProject;
32+
private final long lastModTimestamp;
33+
private final IWorkspace workspace;
34+
private Set<IJavaProject> javaProjects = new HashSet<>();
35+
private final List<String> classpath = new ArrayList<>();
36+
37+
public JavaProjectClasspath(IProject project) {
38+
try {
39+
if (!project.hasNature(JavaCore.NATURE_ID)) {
40+
throw new IllegalArgumentException("The project " + project + " is not a java project");
41+
}
42+
} catch (CoreException e) {
43+
throw new IllegalArgumentException("The project " + project + " is not a java project", e);
44+
}
45+
46+
workspace = project.getWorkspace();
47+
javaProject = JavaCore.create(project);
48+
lastModTimestamp = getClasspathModificationTimestamp();
49+
addPaths(javaProject, false);
50+
51+
// No longer need these things, drop references
52+
javaProjects = null;
53+
}
54+
55+
public boolean isModified() {
56+
long newTimestamp = getClasspathModificationTimestamp();
57+
return newTimestamp != lastModTimestamp;
58+
}
59+
60+
public List<String> getClasspath() {
61+
return classpath;
62+
}
63+
64+
private long getClasspathModificationTimestamp() {
65+
IFile classpathFile = javaProject.getProject().getFile(IJavaProject.CLASSPATH_FILE_NAME);
66+
return FileModificationUtil.getFileModificationTimestamp(classpathFile.getLocation().toFile());
67+
}
68+
69+
private IProject projectFor(IClasspathEntry classpathEntry) {
70+
return workspace.getRoot().getProject(classpathEntry.getPath().toString());
71+
}
72+
73+
private void addPaths(IJavaProject javaProject, boolean exportsOnly) {
74+
75+
if (javaProjects.contains(javaProject)) {
76+
return;
77+
}
78+
79+
javaProjects.add(javaProject);
80+
81+
try {
82+
// Add default output location
83+
IPath projectLocation = javaProject.getProject().getLocation();
84+
addPath(projectLocation.append(javaProject.getOutputLocation().removeFirstSegments(1)));
85+
86+
// Add each classpath entry
87+
IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true);
88+
for (IClasspathEntry classpathEntry : classpathEntries) {
89+
if (classpathEntry.isExported() || !exportsOnly) {
90+
switch (classpathEntry.getEntryKind()) {
91+
92+
// Recurse on projects
93+
case IClasspathEntry.CPE_PROJECT:
94+
IProject project = projectFor(classpathEntry);
95+
IJavaProject javaProj = JavaCore.create(project);
96+
if (javaProj != null) {
97+
addPaths(javaProj, true);
98+
}
99+
break;
100+
101+
// Library
102+
case IClasspathEntry.CPE_LIBRARY:
103+
addPath(classpathEntry);
104+
break;
105+
106+
// Only Source entries with custom output location need to
107+
// be added
108+
case IClasspathEntry.CPE_SOURCE:
109+
IPath outputLocation = classpathEntry.getOutputLocation();
110+
if (outputLocation != null) {
111+
addPath(projectLocation.append(outputLocation.removeFirstSegments(1)));
112+
}
113+
break;
114+
115+
// Variable and Container entries should not be happening,
116+
// because we've asked for resolved entries.
117+
case IClasspathEntry.CPE_VARIABLE:
118+
case IClasspathEntry.CPE_CONTAINER:
119+
default:
120+
break;
121+
}
122+
}
123+
}
124+
} catch (JavaModelException e) {
125+
LOG.warn("JavaModelException occurred: {}", e.getMessage(), e);
126+
}
127+
}
128+
129+
private void addPath(IClasspathEntry classpathEntry) {
130+
addPath(classpathEntry.getPath());
131+
}
132+
133+
private void addPath(IPath path) {
134+
File absoluteFile = null;
135+
IPath location = workspace.getRoot().getFile(path).getLocation();
136+
if (location != null) {
137+
// location is only present, if a project exists in the workspace
138+
// in other words: only if path referenced something inside an existing project
139+
absoluteFile = location.toFile().getAbsoluteFile();
140+
}
141+
142+
if (absoluteFile == null) {
143+
// if location couldn't be resolved, then it is already an absolute path
144+
absoluteFile = path.toFile().getAbsoluteFile();
145+
}
146+
147+
if (!absoluteFile.exists()) {
148+
LOG.warn("auxclasspath: Resolved file {} does not exist", absoluteFile);
149+
}
150+
LOG.debug("auxclasspath: Adding {}", absoluteFile);
151+
classpath.add(absoluteFile.toString());
152+
}
153+
}

net.sourceforge.pmd.eclipse.plugin/src/main/java/net/sourceforge/pmd/eclipse/runtime/properties/IProjectProperties.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ public interface IProjectProperties {
182182
* The classloader is not stored to the project properties file.
183183
*
184184
* @return the classpath or <code>null</code> if the project is not a java project
185+
* @deprecated Since 7.21.0. Avoid using a classloader directly. Use {@link #getClasspath()} instead.
185186
*/
187+
@Deprecated
186188
ClassLoader getAuxClasspath();
189+
190+
/**
191+
* Determines the auxiliary classpath needed for type resolution.
192+
* The classpath is cached and used for all PMD executions for the same project.
193+
* The classpath is not stored to the project properties file.
194+
*
195+
* @return the classpath or <code>null</code> if the project is not a java project
196+
* @since 7.21.0
197+
*/
198+
String getClasspath();
187199
}

0 commit comments

Comments
 (0)