Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ This is a minor release.

### API Changes

#### Deprecations

The following methods have been deprecated for removal:

* `net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties#getAuxClasspath()`
* `net.sourceforge.pmd.eclipse.runtime.properties.impl.ProjectPropertiesImpl#getAuxClasspath()`

Use the new method `getClasspath()` instead. It doesn't use a custom classloader anymore and just returns
the classpath as a single string (path elements separated by the os specific path separator).

The following class has been deprecated for removal:

* `net.sourceforge.pmd.eclipse.runtime.cmd.JavaProjectClassLoader`

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.eclipse.core.resources.IFile;
Expand Down Expand Up @@ -523,9 +526,11 @@ private void dumpRuleSet(final RuleSet ruleSet) {
* </ul>
*
* @throws Exception
* @deprecated Since 7.21.0. Tests the deprecated {@link IProjectProperties#getAuxClasspath()} method.
*/
@Test
public void testProjectClasspath() throws Exception {
@Deprecated
public void testProjectClasspathClassloader() throws Exception {
IProject otherProject = EclipseUtils.createJavaProject("OtherProject");
additionalProjects.add(otherProject);
IFile sampleLib1 = otherProject.getFile("sample-lib1.jar");
Expand Down Expand Up @@ -607,4 +612,95 @@ public void testProjectClasspath() throws Exception {
// no remaining urls
Assert.assertTrue(urls.isEmpty());
}

/**
* Project structure:
* <ul>
* <li>this.testProject "ProjectPropertiesModelTest": main project, with build path, contains lib/sample-lib3.jar</li>
* <li>otherProject "OtherProject": contains sample-lib1.jar, sample-lib2.jar</li>
* <li>otherProject2 "OtherProject2": ProjectPropertiesModelTest depends on this</li>
* <li>externalProject "ExternalProject": not stored within workspace, contains sample-lib4.jar</li>
* </ul>
*
* @throws Exception
*/
@Test
public void testProjectClasspath() throws Exception {
IProject otherProject = EclipseUtils.createJavaProject("OtherProject");
additionalProjects.add(otherProject);
IFile sampleLib1 = otherProject.getFile("sample-lib1.jar");
sampleLib1.create(IOUtils.toInputStream("", "UTF-8"), false, null);
File realSampleLib1 = sampleLib1.getLocation().toFile().getCanonicalFile();
IFile sampleLib2 = otherProject.getFile("sample-lib2.jar");
sampleLib2.create(IOUtils.toInputStream("", "UTF-8"), false, null);
File realSampleLib2 = sampleLib2.getLocation().toFile().getCanonicalFile();

IFolder libFolder = this.testProject.getFolder("lib");
libFolder.create(false, true, null);
IFile sampleLib3 = libFolder.getFile("sample-lib3.jar");
sampleLib3.create(IOUtils.toInputStream("", "UTF-8"), false, null);
File realSampleLib3 = sampleLib3.getLocation().toFile().getCanonicalFile();

IProject otherProject2 = EclipseUtils.createJavaProject("OtherProject2");
additionalProjects.add(otherProject2);
// build the project, so that the output folder "bin/" is created
otherProject2.build(IncrementalProjectBuilder.FULL_BUILD, null);

IProject externalProject = ResourcesPlugin.getWorkspace().getRoot().getProject("ExternalProject");
additionalProjects.add(externalProject);
Assert.assertFalse("Project must not exist yet", externalProject.exists());
java.nio.file.Path externalProjectDir = Files.createTempDirectory("pmd-eclipse-plugin");
IProjectDescription description = externalProject.getWorkspace().newProjectDescription("ExternalProject");
description.setLocation(Path.fromOSString(externalProjectDir.toString()));
externalProject.create(description, null);
externalProject.open(null);
IFile sampleLib4 = externalProject.getFile("sample-lib4.jar");
sampleLib4.create(IOUtils.toInputStream("", "UTF-8"), false, null);
File realSampleLib4 = sampleLib4.getLocation().toFile().getCanonicalFile();

// build the project, so that the output folder "bin/" is created
this.testProject.build(IncrementalProjectBuilder.FULL_BUILD, null);

IFile file = this.testProject.getFile(".classpath");
String newClasspathContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<classpath>\n"
+ " <classpathentry kind=\"src\" path=\"src\"/>\n"
+ " <!-- <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/> -->\n"
+ " <classpathentry combineaccessrules=\"false\" kind=\"src\" path=\"/OtherProject2\"/>\n"
+ " <classpathentry kind=\"lib\" path=\"/OtherProject/sample-lib1.jar\"/>\n"
+ " <classpathentry kind=\"lib\" path=\"" + realSampleLib2.getAbsolutePath() + "\"/>\n"
+ " <classpathentry kind=\"lib\" path=\"lib/sample-lib3.jar\"/>\n"
+ " <classpathentry kind=\"output\" path=\"bin\"/>\n"
+ " <classpathentry kind=\"lib\" path=\"/ExternalProject/sample-lib4.jar\"/>\n"
+ "</classpath>\n";
file.setContents(IOUtils.toInputStream(newClasspathContent, "UTF-8"), 0, null);
// refresh, so that changed .classpath file is considered
this.testProject.refreshLocal(IResource.DEPTH_INFINITE, null);
// rebuild again, so that changed classpath is configured on java project
this.testProject.build(IncrementalProjectBuilder.FULL_BUILD, null);

final IProjectPropertiesManager mgr = PMDPlugin.getDefault().getPropertiesManager();
IProjectProperties model = mgr.loadProjectProperties(this.testProject);
List<String> classpath = new ArrayList<>(Arrays.asList(model.getClasspath().split(Pattern.quote(File.pathSeparator))));

Assert.assertEquals("Found these paths: " + classpath, 6, classpath.size());

// own project's output folder
Assert.assertTrue(classpath.remove(
new File(this.testProject.getLocation().toFile().getAbsoluteFile(), "bin").toString()));
// output folder of other project 2 (project dependency)
Assert.assertTrue(classpath.remove(
new File(otherProject2.getLocation().toFile().getAbsoluteFile(), "bin").toString()));
// sample-lib1.jar stored in OtherProject
Assert.assertTrue(classpath.remove(realSampleLib1.toString()));
// sample-lib2.jar referenced with absolute path
Assert.assertTrue(classpath.remove(realSampleLib2.toString()));
// sample-lib3.jar stored in own project folder lib
Assert.assertTrue(classpath.remove(realSampleLib3.toString()));
// sample-lib4.jar stored in external project folder outside of workspace
Assert.assertTrue(classpath.remove(realSampleLib4.toString()));

// no remaining urls
Assert.assertTrue(classpath.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ protected final void reviewResource(IResource resource) {
LOG.debug("discovered language: {}", languageVersion);

if (PMDPlugin.getDefault().loadPreferences().isProjectBuildPathEnabled()) {
configuration().setClassLoader(projectProperties.getAuxClasspath());
configuration().prependAuxClasspath(projectProperties.getClasspath());
}

// Avoid warnings about not providing cache for incremental analysis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@
import org.slf4j.LoggerFactory;

import net.sourceforge.pmd.eclipse.core.internal.FileModificationUtil;
import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties;

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/


package net.sourceforge.pmd.eclipse.runtime.cmd.internal;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sourceforge.pmd.eclipse.core.internal.FileModificationUtil;

public class JavaProjectClasspath {
private static final Logger LOG = LoggerFactory.getLogger(JavaProjectClasspath.class);

private final IJavaProject javaProject;
private final long lastModTimestamp;
private final IWorkspace workspace;
private Set<IJavaProject> javaProjects = new HashSet<>();
private final List<String> classpath = new ArrayList<>();

public JavaProjectClasspath(IProject project) {
try {
if (!project.hasNature(JavaCore.NATURE_ID)) {
throw new IllegalArgumentException("The project " + project + " is not a java project");
}
} catch (CoreException e) {
throw new IllegalArgumentException("The project " + project + " is not a java project", e);
}

workspace = project.getWorkspace();
javaProject = JavaCore.create(project);
lastModTimestamp = getClasspathModificationTimestamp();
addPaths(javaProject, false);

// No longer need these things, drop references
javaProjects = null;
}

public boolean isModified() {
long newTimestamp = getClasspathModificationTimestamp();
return newTimestamp != lastModTimestamp;
}

public List<String> getClasspath() {
return classpath;
}

private long getClasspathModificationTimestamp() {
IFile classpathFile = javaProject.getProject().getFile(IJavaProject.CLASSPATH_FILE_NAME);
return FileModificationUtil.getFileModificationTimestamp(classpathFile.getLocation().toFile());
}

private IProject projectFor(IClasspathEntry classpathEntry) {
return workspace.getRoot().getProject(classpathEntry.getPath().toString());
}

private void addPaths(IJavaProject javaProject, boolean exportsOnly) {

if (javaProjects.contains(javaProject)) {
return;
}

javaProjects.add(javaProject);

try {
// Add default output location
IPath projectLocation = javaProject.getProject().getLocation();
addPath(projectLocation.append(javaProject.getOutputLocation().removeFirstSegments(1)));

// Add each classpath entry
IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true);
for (IClasspathEntry classpathEntry : classpathEntries) {
if (classpathEntry.isExported() || !exportsOnly) {
switch (classpathEntry.getEntryKind()) {

// Recurse on projects
case IClasspathEntry.CPE_PROJECT:
IProject project = projectFor(classpathEntry);
IJavaProject javaProj = JavaCore.create(project);
if (javaProj != null) {
addPaths(javaProj, true);
}
break;

// Library
case IClasspathEntry.CPE_LIBRARY:
addPath(classpathEntry);
break;

// Only Source entries with custom output location need to
// be added
case IClasspathEntry.CPE_SOURCE:
IPath outputLocation = classpathEntry.getOutputLocation();
if (outputLocation != null) {
addPath(projectLocation.append(outputLocation.removeFirstSegments(1)));
}
break;

// Variable and Container entries should not be happening,
// because we've asked for resolved entries.
case IClasspathEntry.CPE_VARIABLE:
case IClasspathEntry.CPE_CONTAINER:
default:
break;
}
}
}
} catch (JavaModelException e) {
LOG.warn("JavaModelException occurred: {}", e.getMessage(), e);
}
}

private void addPath(IClasspathEntry classpathEntry) {
addPath(classpathEntry.getPath());
}

private void addPath(IPath path) {
File absoluteFile = null;
IPath location = workspace.getRoot().getFile(path).getLocation();
if (location != null) {
// location is only present, if a project exists in the workspace
// in other words: only if path referenced something inside an existing project
absoluteFile = location.toFile().getAbsoluteFile();
}

if (absoluteFile == null) {
// if location couldn't be resolved, then it is already an absolute path
absoluteFile = path.toFile().getAbsoluteFile();
}

if (!absoluteFile.exists()) {
LOG.warn("auxclasspath: Resolved file {} does not exist", absoluteFile);
}
LOG.debug("auxclasspath: Adding {}", absoluteFile);
classpath.add(absoluteFile.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ public interface IProjectProperties {
* The classloader is not stored to the project properties file.
*
* @return the classpath or <code>null</code> if the project is not a java project
* @deprecated Since 7.21.0. Avoid using a classloader directly. Use {@link #getClasspath()} instead.
*/
@Deprecated
ClassLoader getAuxClasspath();

/**
* Determines the auxiliary classpath needed for type resolution.
* The classpath is cached and used for all PMD executions for the same project.
* The classpath is not stored to the project properties file.
*
* @return the classpath or <code>null</code> if the project is not a java project
* @since 7.21.0
*/
String getClasspath();
}
Loading