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 ;
1931
2032public 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