Skip to content

Commit 118ef8d

Browse files
committed
feat: add cache for project info
1 parent 9204668 commit 118ef8d

1 file changed

Lines changed: 223 additions & 5 deletions

File tree

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

Lines changed: 223 additions & 5 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;
@@ -19,6 +31,179 @@
1931

2032
public class ProjectResolver {
2133

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+
}
206+
22207
// Constants for dependency info keys
23208
private static final String KEY_BUILD_TOOL = "buildTool";
24209
private static final String KEY_PROJECT_NAME = "projectName";
@@ -29,7 +214,6 @@ public class ProjectResolver {
29214
private static final String KEY_MODULE_NAME = "moduleName";
30215
private static final String KEY_TOTAL_LIBRARIES = "totalLibraries";
31216
private static final String KEY_TOTAL_PROJECT_REFS = "totalProjectReferences";
32-
private static final String KEY_JRE_CONTAINER_PATH = "jreContainerPath";
33217
private static final String KEY_JRE_CONTAINER = "jreContainer";
34218

35219
public static class DependencyInfo {
@@ -44,12 +228,16 @@ public DependencyInfo(String key, String value) {
44228

45229
/**
46230
* Resolve project dependencies information including JDK version.
231+
* Uses cache with automatic invalidation on project changes.
47232
*
48233
* @param projectUri The project URI
49234
* @param monitor Progress monitor for cancellation support
50235
* @return List of DependencyInfo containing key-value pairs of project information
51236
*/
52237
public static List<DependencyInfo> resolveProjectDependencies(String projectUri, IProgressMonitor monitor) {
238+
// Ensure listeners are registered for cache invalidation
239+
ensureListenersRegistered();
240+
53241
List<DependencyInfo> result = new ArrayList<>();
54242

55243
try {
@@ -68,6 +256,22 @@ public static List<DependencyInfo> resolveProjectDependencies(String projectUri,
68256
return result;
69257
}
70258

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

@@ -77,6 +281,9 @@ public static List<DependencyInfo> resolveProjectDependencies(String projectUri,
77281
// Add build tool info by checking for build files
78282
detectBuildTool(result, project);
79283

284+
// Store in cache
285+
dependencyCache.put(cacheKey, new CachedDependencyInfo(result, currentClasspathHash));
286+
80287
} catch (Exception e) {
81288
JdtlsExtActivator.logException("Error in resolveProjectDependencies", e);
82289
}
@@ -158,12 +365,13 @@ private static void processClasspathEntries(List<DependencyInfo> result, IJavaPr
158365

159366
/**
160367
* Process a library classpath entry.
368+
* Only returns the library file name without full path to reduce data size.
161369
*/
162370
private static void processLibraryEntry(List<DependencyInfo> result, IClasspathEntry entry, int libCount) {
163371
IPath libPath = entry.getPath();
164372
if (libPath != null) {
165-
result.add(new DependencyInfo("library_" + libCount,
166-
libPath.lastSegment() + " (" + libPath.toOSString() + ")"));
373+
// Only keep the file name, remove the full path
374+
result.add(new DependencyInfo("library_" + libCount, libPath.lastSegment()));
167375
}
168376
}
169377

@@ -180,17 +388,27 @@ private static void processProjectEntry(List<DependencyInfo> result, IClasspathE
180388

181389
/**
182390
* Process a container classpath entry (JRE, Maven, Gradle containers).
391+
* Simplified to only extract essential information.
183392
*/
184393
private static void processContainerEntry(List<DependencyInfo> result, IClasspathEntry entry) {
185394
String containerPath = entry.getPath().toString();
186395

187396
if (containerPath.contains("JRE_CONTAINER")) {
188-
result.add(new DependencyInfo(KEY_JRE_CONTAINER_PATH, containerPath));
397+
// Only extract the JRE version, not the full container path
189398
try {
190399
String vmInstallName = JavaRuntime.getVMInstallName(entry.getPath());
191400
addIfNotNull(result, KEY_JRE_CONTAINER, vmInstallName);
192401
} catch (Exception e) {
193-
// Ignore if unable to get VM install name
402+
// Fallback: try to extract version from path
403+
if (containerPath.contains("JavaSE-")) {
404+
int startIdx = containerPath.lastIndexOf("JavaSE-");
405+
String version = containerPath.substring(startIdx);
406+
// Clean up any trailing characters
407+
if (version.contains("/")) {
408+
version = version.substring(0, version.indexOf("/"));
409+
}
410+
result.add(new DependencyInfo(KEY_JRE_CONTAINER, version));
411+
}
194412
}
195413
} else if (containerPath.contains("MAVEN")) {
196414
result.add(new DependencyInfo(KEY_BUILD_TOOL, "Maven"));

0 commit comments

Comments
 (0)