Skip to content

Commit 34691cf

Browse files
committed
Merge branch 'main' into wenyt/trackresult
2 parents cef0200 + b3fd5db commit 34691cf

2 files changed

Lines changed: 247 additions & 14 deletions

File tree

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -647,10 +647,9 @@ public static List<DependencyInfo> getProjectDependencies(List<Object> arguments
647647
/**
648648
* Get project dependencies information including JDK version.
649649
*
650-
* @param arguments List containing the project URI as the first element
651-
* @param monitor Progress monitor for cancellation support
652-
* @return List of DependencyInfo containing key-value pairs of project
653-
* information
650+
* @param arguments List containing the file URI as the first element
651+
* @param monitor Progress monitor for cancellation support
652+
* @return List of DependencyInfo containing key-value pairs of project information
654653
*/
655654
public static ProjectDependenciesResult getProjectDependenciesWithResult(List<Object> arguments,
656655
IProgressMonitor monitor) {

jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java

Lines changed: 244 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5+
import java.util.Map;
6+
import java.util.concurrent.ConcurrentHashMap;
57

68
import org.eclipse.core.resources.IProject;
9+
import org.eclipse.core.resources.IResource;
10+
import org.eclipse.core.resources.IResourceChangeEvent;
11+
import org.eclipse.core.resources.IResourceChangeListener;
12+
import org.eclipse.core.resources.IResourceDelta;
13+
import org.eclipse.core.resources.IResourceDeltaVisitor;
714
import org.eclipse.core.resources.IWorkspaceRoot;
815
import org.eclipse.core.resources.ResourcesPlugin;
16+
import org.eclipse.core.runtime.CoreException;
917
import org.eclipse.core.runtime.IPath;
1018
import org.eclipse.core.runtime.IProgressMonitor;
19+
import org.eclipse.jdt.core.ElementChangedEvent;
1120
import org.eclipse.jdt.core.IClasspathEntry;
21+
import org.eclipse.jdt.core.IElementChangedListener;
22+
import org.eclipse.jdt.core.IJavaElement;
23+
import org.eclipse.jdt.core.IJavaElementDelta;
1224
import org.eclipse.jdt.core.IJavaProject;
1325
import org.eclipse.jdt.core.JavaCore;
1426
import org.eclipse.jdt.core.JavaModelException;
@@ -18,6 +30,179 @@
1830
import com.microsoft.jdtls.ext.core.JdtlsExtActivator;
1931

2032
public class ProjectResolver {
33+
34+
// Cache for project dependency information
35+
private static final Map<String, CachedDependencyInfo> dependencyCache = new ConcurrentHashMap<>();
36+
37+
// Flag to track if listeners are registered
38+
private static volatile boolean listenersRegistered = false;
39+
40+
// Lock for listener registration
41+
private static final Object listenerLock = new Object();
42+
43+
/**
44+
* Cached dependency information with timestamp
45+
*/
46+
private static class CachedDependencyInfo {
47+
final List<DependencyInfo> dependencies;
48+
final long timestamp;
49+
final long classpathHash;
50+
51+
CachedDependencyInfo(List<DependencyInfo> dependencies, long classpathHash) {
52+
this.dependencies = new ArrayList<>(dependencies);
53+
this.timestamp = System.currentTimeMillis();
54+
this.classpathHash = classpathHash;
55+
}
56+
57+
boolean isValid() {
58+
// Cache is valid for 5 minutes
59+
return (System.currentTimeMillis() - timestamp) < 300000;
60+
}
61+
}
62+
63+
/**
64+
* Listener for Java element changes (classpath changes, project references, etc.)
65+
*/
66+
private static final IElementChangedListener javaElementListener = new IElementChangedListener() {
67+
@Override
68+
public void elementChanged(ElementChangedEvent event) {
69+
IJavaElementDelta delta = event.getDelta();
70+
processDelta(delta);
71+
}
72+
73+
private void processDelta(IJavaElementDelta delta) {
74+
IJavaElement element = delta.getElement();
75+
int flags = delta.getFlags();
76+
77+
// Check for classpath changes
78+
if ((flags & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0 ||
79+
(flags & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0) {
80+
81+
if (element instanceof IJavaProject) {
82+
IJavaProject project = (IJavaProject) element;
83+
invalidateCache(project.getProject());
84+
}
85+
}
86+
87+
// Recursively process children
88+
for (IJavaElementDelta child : delta.getAffectedChildren()) {
89+
processDelta(child);
90+
}
91+
}
92+
};
93+
94+
/**
95+
* Listener for resource changes (pom.xml, build.gradle, etc.)
96+
*/
97+
private static final IResourceChangeListener resourceListener = new IResourceChangeListener() {
98+
@Override
99+
public void resourceChanged(IResourceChangeEvent event) {
100+
if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
101+
return;
102+
}
103+
104+
IResourceDelta delta = event.getDelta();
105+
if (delta == null) {
106+
return;
107+
}
108+
109+
try {
110+
delta.accept(new IResourceDeltaVisitor() {
111+
@Override
112+
public boolean visit(IResourceDelta delta) throws CoreException {
113+
IResource resource = delta.getResource();
114+
115+
// Check for build file changes
116+
if (resource.getType() == IResource.FILE) {
117+
String fileName = resource.getName();
118+
if ("pom.xml".equals(fileName) ||
119+
"build.gradle".equals(fileName) ||
120+
"build.gradle.kts".equals(fileName) ||
121+
".classpath".equals(fileName) ||
122+
".project".equals(fileName)) {
123+
124+
IProject project = resource.getProject();
125+
if (project != null) {
126+
invalidateCache(project);
127+
}
128+
}
129+
}
130+
return true;
131+
}
132+
});
133+
} catch (CoreException e) {
134+
JdtlsExtActivator.logException("Error processing resource delta", e);
135+
}
136+
}
137+
};
138+
139+
/**
140+
* Initialize listeners for cache invalidation
141+
*/
142+
private static void ensureListenersRegistered() {
143+
if (!listenersRegistered) {
144+
synchronized (listenerLock) {
145+
if (!listenersRegistered) {
146+
try {
147+
// Register Java element change listener
148+
JavaCore.addElementChangedListener(javaElementListener,
149+
ElementChangedEvent.POST_CHANGE);
150+
151+
// Register resource change listener
152+
ResourcesPlugin.getWorkspace().addResourceChangeListener(
153+
resourceListener,
154+
IResourceChangeEvent.POST_CHANGE);
155+
156+
listenersRegistered = true;
157+
JdtlsExtActivator.logInfo("ProjectResolver cache listeners registered successfully");
158+
} catch (Exception e) {
159+
JdtlsExtActivator.logException("Failed to register ProjectResolver listeners", e);
160+
}
161+
}
162+
}
163+
}
164+
}
165+
166+
/**
167+
* Invalidate cache for a specific project
168+
*/
169+
private static void invalidateCache(IProject project) {
170+
if (project == null) {
171+
return;
172+
}
173+
174+
String projectPath = project.getLocation() != null ?
175+
project.getLocation().toOSString() : project.getName();
176+
177+
if (dependencyCache.remove(projectPath) != null) {
178+
JdtlsExtActivator.logInfo("Cache invalidated for project: " + project.getName());
179+
}
180+
}
181+
182+
/**
183+
* Clear all cached dependency information
184+
*/
185+
public static void clearCache() {
186+
dependencyCache.clear();
187+
JdtlsExtActivator.logInfo("ProjectResolver cache cleared");
188+
}
189+
190+
/**
191+
* Calculate a simple hash of classpath entries for cache validation
192+
*/
193+
private static long calculateClasspathHash(IJavaProject javaProject) {
194+
try {
195+
IClasspathEntry[] entries = javaProject.getResolvedClasspath(true);
196+
long hash = 0;
197+
for (IClasspathEntry entry : entries) {
198+
hash = hash * 31 + entry.getPath().toString().hashCode();
199+
hash = hash * 31 + entry.getEntryKind();
200+
}
201+
return hash;
202+
} catch (JavaModelException e) {
203+
return 0;
204+
}
205+
}
21206

22207
// Constants for dependency info keys
23208
private static final String KEY_BUILD_TOOL = "buildTool";
@@ -44,30 +229,48 @@ public DependencyInfo(String key, String value) {
44229

45230
/**
46231
* Resolve project dependencies information including JDK version.
232+
* Supports both single projects and multi-module aggregator projects.
47233
*
48-
* @param projectUri The project URI
234+
* @param fileUri The file URI
49235
* @param monitor Progress monitor for cancellation support
50236
* @return List of DependencyInfo containing key-value pairs of project information
51237
*/
52-
public static List<DependencyInfo> resolveProjectDependencies(String projectUri, IProgressMonitor monitor) {
238+
public static List<DependencyInfo> resolveProjectDependencies(String fileUri, IProgressMonitor monitor) {
239+
// Ensure listeners are registered for cache invalidation
240+
ensureListenersRegistered();
241+
53242
List<DependencyInfo> result = new ArrayList<>();
54243

55244
try {
56-
IPath projectPath = ResourceUtils.canonicalFilePathFromURI(projectUri);
245+
IPath fileIPath = ResourceUtils.canonicalFilePathFromURI(fileUri);
57246

58247
// Find the project
59248
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
60-
IProject project = findProjectByPath(root, projectPath);
249+
IProject project = findProjectByPath(root, fileIPath);
61250

62251
if (project == null || !project.isAccessible()) {
63252
return result;
64253
}
65254

66255
IJavaProject javaProject = JavaCore.create(project);
256+
// Check if this is a Java project
67257
if (javaProject == null || !javaProject.exists()) {
68258
return result;
69259
}
70260

261+
// Generate cache key based on project location
262+
String cacheKey = project.getLocation().toOSString();
263+
264+
// Calculate current classpath hash for validation
265+
long currentClasspathHash = calculateClasspathHash(javaProject);
266+
267+
// Try to get from cache
268+
CachedDependencyInfo cached = dependencyCache.get(cacheKey);
269+
if (cached != null && cached.isValid() && cached.classpathHash == currentClasspathHash) {
270+
JdtlsExtActivator.logInfo("Using cached dependencies for project: " + project.getName());
271+
return new ArrayList<>(cached.dependencies);
272+
}
273+
71274
// Add basic project information
72275
addBasicProjectInfo(result, project, javaProject);
73276

@@ -76,6 +279,9 @@ public static List<DependencyInfo> resolveProjectDependencies(String projectUri,
76279

77280
// Add build tool info by checking for build files
78281
detectBuildTool(result, project);
282+
283+
// Store in cache
284+
dependencyCache.put(cacheKey, new CachedDependencyInfo(result, currentClasspathHash));
79285

80286
} catch (Exception e) {
81287
JdtlsExtActivator.logException("Error in resolveProjectDependencies", e);
@@ -86,14 +292,31 @@ public static List<DependencyInfo> resolveProjectDependencies(String projectUri,
86292

87293
/**
88294
* Find project by path from all projects in workspace.
295+
* The path can be either a project root path or a file/folder path within a project.
296+
* This method will find the project that contains the given path.
297+
*
298+
* @param root The workspace root
299+
* @param filePath The path to search for (can be project root or file within project)
300+
* @return The project that contains the path, or null if not found
89301
*/
90-
private static IProject findProjectByPath(IWorkspaceRoot root, IPath projectPath) {
302+
private static IProject findProjectByPath(IWorkspaceRoot root, IPath filePath) {
91303
IProject[] allProjects = root.getProjects();
304+
305+
// First pass: check for exact project location match (most efficient)
92306
for (IProject p : allProjects) {
93-
if (p.getLocation() != null && p.getLocation().equals(projectPath)) {
307+
if (p.getLocation() != null && p.getLocation().equals(filePath)) {
94308
return p;
95309
}
96310
}
311+
312+
// Second pass: check if the file path is within any project directory
313+
// This handles cases where filePath points to a file or folder inside a project
314+
for (IProject p : allProjects) {
315+
if (p.getLocation() != null && p.getLocation().isPrefixOf(filePath)) {
316+
return p;
317+
}
318+
}
319+
97320
return null;
98321
}
99322

@@ -158,17 +381,19 @@ private static void processClasspathEntries(List<DependencyInfo> result, IJavaPr
158381

159382
/**
160383
* Process a library classpath entry.
384+
* Only returns the library file name without full path to reduce data size.
161385
*/
162386
private static void processLibraryEntry(List<DependencyInfo> result, IClasspathEntry entry, int libCount) {
163387
IPath libPath = entry.getPath();
164388
if (libPath != null) {
165-
result.add(new DependencyInfo("library_" + libCount,
166-
libPath.lastSegment() + " (" + libPath.toOSString() + ")"));
389+
// Only keep the file name, remove the full path
390+
result.add(new DependencyInfo("library_" + libCount, libPath.lastSegment()));
167391
}
168392
}
169393

170394
/**
171395
* Process a project reference classpath entry.
396+
* Simplified to only extract essential information.
172397
*/
173398
private static void processProjectEntry(List<DependencyInfo> result, IClasspathEntry entry, int projectRefCount) {
174399
IPath projectRefPath = entry.getPath();
@@ -185,12 +410,21 @@ private static void processContainerEntry(List<DependencyInfo> result, IClasspat
185410
String containerPath = entry.getPath().toString();
186411

187412
if (containerPath.contains("JRE_CONTAINER")) {
188-
result.add(new DependencyInfo(KEY_JRE_CONTAINER_PATH, containerPath));
413+
// Only extract the JRE version, not the full container path
189414
try {
190415
String vmInstallName = JavaRuntime.getVMInstallName(entry.getPath());
191416
addIfNotNull(result, KEY_JRE_CONTAINER, vmInstallName);
192417
} catch (Exception e) {
193-
// Ignore if unable to get VM install name
418+
// Fallback: try to extract version from path
419+
if (containerPath.contains("JavaSE-")) {
420+
int startIdx = containerPath.lastIndexOf("JavaSE-");
421+
String version = containerPath.substring(startIdx);
422+
// Clean up any trailing characters
423+
if (version.contains("/")) {
424+
version = version.substring(0, version.indexOf("/"));
425+
}
426+
result.add(new DependencyInfo(KEY_JRE_CONTAINER, version));
427+
}
194428
}
195429
} else if (containerPath.contains("MAVEN")) {
196430
result.add(new DependencyInfo(KEY_BUILD_TOOL, "Maven"));

0 commit comments

Comments
 (0)