Skip to content

Commit 00edbf4

Browse files
committed
Merge pull request #135 from adangel:project-classpath
Fix JavaProjectClassLoader (auxclasspath support) for workspace relative libraries #135
2 parents a790522 + 246a6ca commit 00edbf4

File tree

36 files changed

+470
-18
lines changed

36 files changed

+470
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ You should see 6 projects:
6464
### Debugging
6565
You can run eclipse with debugging enabled and connect to it via remote debugging:
6666

67-
eclipse -data workspace-directory -vmargs -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
67+
eclipse -data workspace-directory -vmargs -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000
6868

6969

7070
### Releasing and updating the official eclipse update site

ReleaseNotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ This is a minor release.
1717

1818
### Fixed Issues
1919

20+
* [#135](https://github.com/pmd/pmd-eclipse-plugin/pull/135): Fix JavaProjectClassLoader (auxclasspath support) for workspace relative libraries
21+
2022
### API Changes
2123

2224
### External Contributions

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@
4545
public class ReviewCmdTest {
4646
private IProject testProject;
4747

48-
/**
49-
* @see junit.framework.TestCase#setUp()
50-
*/
5148
@Before
5249
public void setUp() throws Exception {
5350

@@ -68,9 +65,6 @@ public void setUp() throws Exception {
6865
properties.setPmdEnabled(true);
6966
}
7067

71-
/**
72-
* @see junit.framework.TestCase#tearDown()
73-
*/
7468
@After
7569
public void tearDown() throws Exception {
7670
try {

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

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,30 @@
66

77
import java.io.ByteArrayInputStream;
88
import java.io.ByteArrayOutputStream;
9+
import java.io.File;
910
import java.io.InputStream;
1011
import java.io.PrintStream;
12+
import java.net.URI;
13+
import java.net.URL;
14+
import java.net.URLClassLoader;
1115
import java.nio.charset.StandardCharsets;
16+
import java.nio.file.Files;
1217
import java.util.Collection;
18+
import java.util.HashSet;
1319
import java.util.Iterator;
20+
import java.util.LinkedList;
21+
import java.util.List;
22+
import java.util.Set;
1423

24+
import org.apache.commons.io.IOUtils;
1525
import org.eclipse.core.resources.IFile;
26+
import org.eclipse.core.resources.IFolder;
1627
import org.eclipse.core.resources.IProject;
28+
import org.eclipse.core.resources.IProjectDescription;
29+
import org.eclipse.core.resources.IncrementalProjectBuilder;
30+
import org.eclipse.core.resources.ResourcesPlugin;
1731
import org.eclipse.core.runtime.CoreException;
32+
import org.eclipse.core.runtime.Path;
1833
import org.eclipse.ui.IWorkingSet;
1934
import org.junit.After;
2035
import org.junit.Assert;
@@ -43,6 +58,7 @@
4358
public class ProjectPropertiesModelTest {
4459
private IProject testProject;
4560
private RuleSet initialPluginRuleSet;
61+
private List<IProject> additionalProjects = new LinkedList<>();
4662

4763
@Before
4864
public void setUp() throws Exception {
@@ -88,6 +104,13 @@ public void tearDown() throws Exception {
88104

89105
// 2. Restore the plugin initial rule set
90106
PMDPlugin.getDefault().getPreferencesManager().setRuleSet(this.initialPluginRuleSet);
107+
108+
// 3. Delete additional projects
109+
for (IProject project : additionalProjects) {
110+
if (project.exists() && project.isAccessible()) {
111+
project.delete(true, true, null);
112+
}
113+
}
91114
}
92115

93116
public static void compareTwoRuleSets(RuleSet ruleSet1, RuleSet ruleSet2) {
@@ -503,4 +526,93 @@ private void dumpRuleSet(final RuleSet ruleSet) {
503526
System.out.println();
504527
}
505528

529+
/**
530+
* Project structure:
531+
* <ul>
532+
* <li>this.testProject "PMDTestProject": main project, with build path, contains lib/sample-lib3.jar</li>
533+
* <li>otherProject "OtherProject": contains sample-lib1.jar, sample-lib2.jar</li>
534+
* <li>otherProject2 "OtherProject2": PMDTestProject depends on this</li>
535+
* <li>externalProject "ExternalProject": not stored within workspace, contains sample-lib4.jar</li>
536+
* </ul>
537+
*
538+
* @throws Exception
539+
*/
540+
@Test
541+
public void testProjectClasspath() throws Exception {
542+
IProject otherProject = EclipseUtils.createJavaProject("OtherProject");
543+
additionalProjects.add(otherProject);
544+
IFile sampleLib1 = otherProject.getFile("sample-lib1.jar");
545+
sampleLib1.create(IOUtils.toInputStream("", "UTF-8"), false, null);
546+
File realSampleLib1 = sampleLib1.getLocation().toFile().getCanonicalFile();
547+
IFile sampleLib2 = otherProject.getFile("sample-lib2.jar");
548+
sampleLib2.create(IOUtils.toInputStream("", "UTF-8"), false, null);
549+
File realSampleLib2 = sampleLib2.getLocation().toFile().getCanonicalFile();
550+
551+
IFolder libFolder = this.testProject.getFolder("lib");
552+
libFolder.create(false, true, null);
553+
IFile sampleLib3 = libFolder.getFile("sample-lib3.jar");
554+
sampleLib3.create(IOUtils.toInputStream("", "UTF-8"), false, null);
555+
File realSampleLib3 = sampleLib3.getLocation().toFile().getCanonicalFile();
556+
557+
IProject otherProject2 = EclipseUtils.createJavaProject("OtherProject2");
558+
additionalProjects.add(otherProject2);
559+
// build the project, so that the output folder "bin/" is created
560+
otherProject2.build(IncrementalProjectBuilder.FULL_BUILD, null);
561+
562+
IProject externalProject = ResourcesPlugin.getWorkspace().getRoot().getProject("ExternalProject");
563+
additionalProjects.add(externalProject);
564+
Assert.assertFalse("Project must not exist yet", externalProject.exists());
565+
java.nio.file.Path externalProjectDir = Files.createTempDirectory("pmd-eclipse-plugin");
566+
IProjectDescription description = externalProject.getWorkspace().newProjectDescription("ExternalProject");
567+
description.setLocation(Path.fromOSString(externalProjectDir.toString()));
568+
externalProject.create(description, null);
569+
externalProject.open(null);
570+
IFile sampleLib4 = externalProject.getFile("sample-lib4.jar");
571+
sampleLib4.create(IOUtils.toInputStream("", "UTF-8"), false, null);
572+
File realSampleLib4 = sampleLib4.getLocation().toFile().getCanonicalFile();
573+
574+
// build the project, so that the output folder "bin/" is created
575+
this.testProject.build(IncrementalProjectBuilder.FULL_BUILD, null);
576+
577+
IFile file = this.testProject.getFile(".classpath");
578+
String newClasspathContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
579+
+ "<classpath>\n"
580+
+ " <classpathentry kind=\"src\" path=\"src\"/>\n"
581+
+ " <!-- <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/> -->\n"
582+
+ " <classpathentry combineaccessrules=\"false\" kind=\"src\" path=\"/OtherProject2\"/>\n"
583+
+ " <classpathentry kind=\"lib\" path=\"/OtherProject/sample-lib1.jar\"/>\n"
584+
+ " <classpathentry kind=\"lib\" path=\"" + realSampleLib2.getAbsolutePath() + "\"/>\n"
585+
+ " <classpathentry kind=\"lib\" path=\"lib/sample-lib3.jar\"/>\n"
586+
+ " <classpathentry kind=\"output\" path=\"bin\"/>\n"
587+
+ " <classpathentry kind=\"lib\" path=\"/ExternalProject/sample-lib4.jar\"/>\n"
588+
+ "</classpath>\n";
589+
file.setContents(IOUtils.toInputStream(newClasspathContent, "UTF-8"), 0, null);
590+
final IProjectPropertiesManager mgr = PMDPlugin.getDefault().getPropertiesManager();
591+
IProjectProperties model = mgr.loadProjectProperties(this.testProject);
592+
URLClassLoader auxClasspath = (URLClassLoader) model.getAuxClasspath();
593+
Set<URI> urls = new HashSet<>();
594+
for (URL url : auxClasspath.getURLs()) {
595+
urls.add(url.toURI());
596+
}
597+
598+
Assert.assertEquals(6, urls.size());
599+
600+
// own project's output folder
601+
Assert.assertTrue(urls.remove(
602+
URI.create(this.testProject.getLocation().toFile().getAbsoluteFile().toURI() + "bin/")));
603+
// output folder of other project 2 (project dependency)
604+
Assert.assertTrue(urls.remove(
605+
URI.create(otherProject2.getLocation().toFile().getAbsoluteFile().toURI() + "bin/")));
606+
// sample-lib1.jar stored in OtherProject
607+
Assert.assertTrue(urls.remove(realSampleLib1.toURI()));
608+
// sample-lib2.jar referenced with absolute path
609+
Assert.assertTrue(urls.remove(realSampleLib2.toURI()));
610+
// sample-lib3.jar stored in own project folder lib
611+
Assert.assertTrue(urls.remove(realSampleLib3.toURI()));
612+
// sample-lib4.jar stored in external project folder outside of workspace
613+
Assert.assertTrue(urls.remove(realSampleLib4.toURI()));
614+
615+
// no remaining urls
616+
Assert.assertTrue(urls.isEmpty());
617+
}
506618
}

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

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
package net.sourceforge.pmd.eclipse.runtime.cmd;
66

7+
import java.io.File;
78
import java.net.MalformedURLException;
89
import java.net.URL;
910
import java.net.URLClassLoader;
1011
import java.util.HashSet;
1112
import java.util.Set;
1213

14+
import org.eclipse.core.resources.IFile;
1315
import org.eclipse.core.resources.IProject;
16+
import org.eclipse.core.resources.IWorkspace;
1417
import org.eclipse.core.runtime.CoreException;
1518
import org.eclipse.core.runtime.IPath;
1619
import org.eclipse.jdt.core.IClasspathEntry;
@@ -20,14 +23,20 @@
2023
import org.slf4j.Logger;
2124
import org.slf4j.LoggerFactory;
2225

26+
import net.sourceforge.pmd.eclipse.core.internal.FileModificationUtil;
27+
2328
/**
2429
* This is a ClassLoader for the Build Path of an IJavaProject.
2530
*/
2631
public class JavaProjectClassLoader extends URLClassLoader {
2732
private static final Logger LOG = LoggerFactory.getLogger(JavaProjectClassLoader.class);
2833

34+
private final IJavaProject javaProject;
35+
private final long lastModTimestamp;
36+
private final IWorkspace workspace;
2937
private Set<IJavaProject> javaProjects = new HashSet<IJavaProject>();
3038

39+
3140
public JavaProjectClassLoader(ClassLoader parent, IProject project) {
3241
super(new URL[0], parent);
3342
try {
@@ -38,15 +47,27 @@ public JavaProjectClassLoader(ClassLoader parent, IProject project) {
3847
throw new IllegalArgumentException("The project " + project + " is not a java project", e);
3948
}
4049

41-
IJavaProject javaProject = JavaCore.create(project);
50+
workspace = project.getWorkspace();
51+
javaProject = JavaCore.create(project);
52+
lastModTimestamp = getClasspathModificationTimestamp();
4253
addURLs(javaProject, false);
4354

4455
// No longer need these things, drop references
4556
javaProjects = null;
4657
}
4758

48-
private static IProject projectFor(IJavaProject javaProject, IClasspathEntry classpathEntry) {
49-
return javaProject.getProject().getWorkspace().getRoot().getProject(classpathEntry.getPath().toString());
59+
public boolean isModified() {
60+
long newTimestamp = getClasspathModificationTimestamp();
61+
return newTimestamp != lastModTimestamp;
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());
5071
}
5172

5273
private void addURLs(IJavaProject javaProject, boolean exportsOnly) {
@@ -70,7 +91,7 @@ private void addURLs(IJavaProject javaProject, boolean exportsOnly) {
7091

7192
// Recurse on projects
7293
case IClasspathEntry.CPE_PROJECT:
73-
IProject project = projectFor(javaProject, classpathEntry);
94+
IProject project = projectFor(classpathEntry);
7495
IJavaProject javaProj = JavaCore.create(project);
7596
if (javaProj != null) {
7697
addURLs(javaProj, true);
@@ -101,7 +122,7 @@ private void addURLs(IJavaProject javaProject, boolean exportsOnly) {
101122
}
102123
}
103124
} catch (JavaModelException e) {
104-
LOG.debug("MalformedURLException occurred: " + e.getLocalizedMessage(), e);
125+
LOG.warn("JavaModelException occurred: {}", e.getMessage(), e);
105126
}
106127
}
107128

@@ -111,10 +132,27 @@ private void addURL(IClasspathEntry classpathEntry) {
111132

112133
private void addURL(IPath path) {
113134
try {
114-
URL url = path.toFile().getAbsoluteFile().toURI().toURL();
135+
File absoluteFile = null;
136+
IPath location = workspace.getRoot().getFile(path).getLocation();
137+
if (location != null) {
138+
// location is only present, if a project exists in the workspace
139+
// in other words: only if path referenced something inside an existing project
140+
absoluteFile = location.toFile().getAbsoluteFile();
141+
}
142+
143+
if (absoluteFile == null) {
144+
// if location couldn't be resolved, then it is already an absolute path
145+
absoluteFile = path.toFile().getAbsoluteFile();
146+
}
147+
148+
if (!absoluteFile.exists()) {
149+
LOG.warn("auxclasspath: Resolved file {} does not exist", absoluteFile);
150+
}
151+
URL url = absoluteFile.toURI().toURL();
152+
LOG.debug("auxclasspath: Adding url {}", url);
115153
addURL(url);
116154
} catch (MalformedURLException e) {
117-
LOG.debug("MalformedURLException occurred: " + e.getLocalizedMessage(), e);
155+
LOG.warn("MalformedURLException occurred: {}", e.getMessage(), e);
118156
}
119157
}
120158
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class ProjectPropertiesImpl implements IProjectProperties {
6666
private boolean fullBuildEnabled = true; // default in case didn't come from properties
6767
private Set<String> buildPathExcludePatterns = new HashSet<String>();
6868
private Set<String> buildPathIncludePatterns = new HashSet<String>();
69-
private ClassLoader auxclasspath;
69+
private JavaProjectClassLoader auxclasspath;
7070

7171
/**
7272
* The default constructor takes a project as an argument
@@ -495,6 +495,18 @@ public Set<String> getBuildPathIncludePatterns() {
495495
public ClassLoader getAuxClasspath() {
496496
try {
497497
if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
498+
String projectName = project.getName();
499+
if (auxclasspath != null && auxclasspath.isModified()) {
500+
PMDPlugin.getDefault().logInformation("Classpath of project " + projectName
501+
+ " changed - recreating it.");
502+
try {
503+
auxclasspath.close();
504+
} catch (IOException e) {
505+
// ignored
506+
}
507+
auxclasspath = null;
508+
}
509+
498510
if (auxclasspath == null) {
499511
PMDPlugin.getDefault()
500512
.logInformation("Creating new auxclasspath class loader for project " + project.getName());

test-projects/.gitignore

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
eclipse*/
22
ws/
3-
!project*/.project
4-
!project*/.pmd
5-
!project*/.classpath
3+
!project**/.project
4+
!project**/.pmd
5+
!project**/.classpath
6+
!project**/.ruleset
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="src" path="src"/>
4+
<classpathentry combineaccessrules="false" kind="src" path="/sample-lib2"/>
5+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
6+
<attributes>
7+
<attribute name="module" value="true"/>
8+
</attributes>
9+
</classpathentry>
10+
<classpathentry kind="lib" path="/sample-lib1/sample-lib1-v1.jar"/>
11+
<classpathentry kind="lib" path="/tmp/sample-lib3-v1.jar"/>
12+
<classpathentry kind="lib" path="lib/sample-lib4.jar"/>
13+
<classpathentry kind="output" path="bin"/>
14+
</classpath>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
lib/sample-lib4.jar
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<pmd>
3+
<useProjectRuleSet>true</useProjectRuleSet>
4+
<ruleSetFile>.ruleset</ruleSetFile>
5+
<includeDerivedFiles>false</includeDerivedFiles>
6+
<violationsAsErrors>true</violationsAsErrors>
7+
<fullBuildEnabled>true</fullBuildEnabled>
8+
</pmd>

0 commit comments

Comments
 (0)