22
33import java .util .ArrayList ;
44import java .util .List ;
5+ import java .util .Map ;
6+ import java .util .concurrent .ConcurrentHashMap ;
57
68import 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 ;
714import org .eclipse .core .resources .IWorkspaceRoot ;
815import org .eclipse .core .resources .ResourcesPlugin ;
16+ import org .eclipse .core .runtime .CoreException ;
917import org .eclipse .core .runtime .IPath ;
1018import org .eclipse .core .runtime .IProgressMonitor ;
19+ import org .eclipse .jdt .core .ElementChangedEvent ;
1120import 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 ;
1224import org .eclipse .jdt .core .IJavaProject ;
1325import org .eclipse .jdt .core .JavaCore ;
1426import org .eclipse .jdt .core .JavaModelException ;
1830import com .microsoft .jdtls .ext .core .JdtlsExtActivator ;
1931
2032public 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