Skip to content

Commit b3fd5db

Browse files
authored
feat: add cache to project resolver (#925)
* feat: add cache to project resolver * perf: update project location as key * fix: fix error
1 parent a9fa707 commit b3fd5db

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

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

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@
66
import java.util.concurrent.ConcurrentHashMap;
77

88
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;
914
import org.eclipse.core.resources.IWorkspaceRoot;
1015
import org.eclipse.core.resources.ResourcesPlugin;
1116
import org.eclipse.core.runtime.CoreException;
1217
import org.eclipse.core.runtime.IPath;
1318
import org.eclipse.core.runtime.IProgressMonitor;
19+
import org.eclipse.jdt.core.ElementChangedEvent;
1420
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;
1524
import org.eclipse.jdt.core.IJavaProject;
1625
import org.eclipse.jdt.core.JavaCore;
1726
import org.eclipse.jdt.core.JavaModelException;
@@ -21,6 +30,179 @@
2130
import com.microsoft.jdtls.ext.core.JdtlsExtActivator;
2231

2332
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+
}
24206

25207
// Constants for dependency info keys
26208
private static final String KEY_BUILD_TOOL = "buildTool";
@@ -54,6 +236,9 @@ public DependencyInfo(String key, String value) {
54236
* @return List of DependencyInfo containing key-value pairs of project information
55237
*/
56238
public static List<DependencyInfo> resolveProjectDependencies(String fileUri, IProgressMonitor monitor) {
239+
// Ensure listeners are registered for cache invalidation
240+
ensureListenersRegistered();
241+
57242
List<DependencyInfo> result = new ArrayList<>();
58243

59244
try {
@@ -73,6 +258,19 @@ public static List<DependencyInfo> resolveProjectDependencies(String fileUri, IP
73258
return result;
74259
}
75260

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+
76274
// Add basic project information
77275
addBasicProjectInfo(result, project, javaProject);
78276

@@ -81,6 +279,9 @@ public static List<DependencyInfo> resolveProjectDependencies(String fileUri, IP
81279

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

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

0 commit comments

Comments
 (0)