Skip to content

Commit 226d7c8

Browse files
committed
feat: add cache to project resolver
1 parent 609dfd2 commit 226d7c8

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed

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

Lines changed: 205 additions & 0 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";
@@ -50,6 +235,9 @@ public DependencyInfo(String key, String value) {
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 {
@@ -64,10 +252,24 @@ public static List<DependencyInfo> resolveProjectDependencies(String projectUri,
64252
}
65253

66254
IJavaProject javaProject = JavaCore.create(project);
255+
// Check if this is a Java project
67256
if (javaProject == null || !javaProject.exists()) {
68257
return result;
69258
}
70259

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

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

77279
// Add build tool info by checking for build files
78280
detectBuildTool(result, project);
281+
282+
// Store in cache
283+
dependencyCache.put(cacheKey, new CachedDependencyInfo(result, currentClasspathHash));
79284

80285
} catch (Exception e) {
81286
JdtlsExtActivator.logException("Error in resolveProjectDependencies", e);

0 commit comments

Comments
 (0)