66import java .util .concurrent .ConcurrentHashMap ;
77
88import 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 ;
914import org .eclipse .core .resources .IWorkspaceRoot ;
1015import org .eclipse .core .resources .ResourcesPlugin ;
1116import org .eclipse .core .runtime .CoreException ;
1217import org .eclipse .core .runtime .IPath ;
1318import org .eclipse .core .runtime .IProgressMonitor ;
19+ import org .eclipse .jdt .core .ElementChangedEvent ;
1420import 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 ;
1524import org .eclipse .jdt .core .IJavaProject ;
1625import org .eclipse .jdt .core .JavaCore ;
1726import org .eclipse .jdt .core .JavaModelException ;
2130import com .microsoft .jdtls .ext .core .JdtlsExtActivator ;
2231
2332public 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