Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
Expand All @@ -21,6 +30,179 @@
import com.microsoft.jdtls.ext.core.JdtlsExtActivator;

public class ProjectResolver {

// Cache for project dependency information
private static final Map<String, CachedDependencyInfo> dependencyCache = new ConcurrentHashMap<>();

// Flag to track if listeners are registered
private static volatile boolean listenersRegistered = false;

// Lock for listener registration
private static final Object listenerLock = new Object();

/**
* Cached dependency information with timestamp
*/
private static class CachedDependencyInfo {
final List<DependencyInfo> dependencies;
final long timestamp;
final long classpathHash;

CachedDependencyInfo(List<DependencyInfo> dependencies, long classpathHash) {
this.dependencies = new ArrayList<>(dependencies);
this.timestamp = System.currentTimeMillis();
this.classpathHash = classpathHash;
}

boolean isValid() {
// Cache is valid for 5 minutes
return (System.currentTimeMillis() - timestamp) < 300000;
}
}

/**
* Listener for Java element changes (classpath changes, project references, etc.)
*/
private static final IElementChangedListener javaElementListener = new IElementChangedListener() {
@Override
public void elementChanged(ElementChangedEvent event) {
IJavaElementDelta delta = event.getDelta();
processDelta(delta);
}

private void processDelta(IJavaElementDelta delta) {
IJavaElement element = delta.getElement();
int flags = delta.getFlags();

// Check for classpath changes
if ((flags & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0 ||
(flags & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0) {

if (element instanceof IJavaProject) {
IJavaProject project = (IJavaProject) element;
invalidateCache(project.getProject());
}
}

// Recursively process children
for (IJavaElementDelta child : delta.getAffectedChildren()) {
processDelta(child);
}
}
};

/**
* Listener for resource changes (pom.xml, build.gradle, etc.)
*/
private static final IResourceChangeListener resourceListener = new IResourceChangeListener() {
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
return;
}

IResourceDelta delta = event.getDelta();
if (delta == null) {
return;
}

try {
delta.accept(new IResourceDeltaVisitor() {
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();

// Check for build file changes
if (resource.getType() == IResource.FILE) {
String fileName = resource.getName();
if ("pom.xml".equals(fileName) ||
"build.gradle".equals(fileName) ||
"build.gradle.kts".equals(fileName) ||
".classpath".equals(fileName) ||
".project".equals(fileName)) {

IProject project = resource.getProject();
if (project != null) {
invalidateCache(project);
}
}
}
return true;
}
});
} catch (CoreException e) {
JdtlsExtActivator.logException("Error processing resource delta", e);
}
}
};

/**
* Initialize listeners for cache invalidation
*/
private static void ensureListenersRegistered() {
if (!listenersRegistered) {
synchronized (listenerLock) {
if (!listenersRegistered) {
try {
// Register Java element change listener
JavaCore.addElementChangedListener(javaElementListener,
ElementChangedEvent.POST_CHANGE);

// Register resource change listener
ResourcesPlugin.getWorkspace().addResourceChangeListener(
resourceListener,
IResourceChangeEvent.POST_CHANGE);

listenersRegistered = true;
JdtlsExtActivator.logInfo("ProjectResolver cache listeners registered successfully");
} catch (Exception e) {
JdtlsExtActivator.logException("Failed to register ProjectResolver listeners", e);
}
}
}
}
}

/**
* Invalidate cache for a specific project
*/
private static void invalidateCache(IProject project) {
if (project == null) {
return;
}

String projectPath = project.getLocation() != null ?
project.getLocation().toOSString() : project.getName();

if (dependencyCache.remove(projectPath) != null) {
JdtlsExtActivator.logInfo("Cache invalidated for project: " + project.getName());
}
}

/**
* Clear all cached dependency information
*/
public static void clearCache() {
dependencyCache.clear();
JdtlsExtActivator.logInfo("ProjectResolver cache cleared");
}

/**
* Calculate a simple hash of classpath entries for cache validation
*/
private static long calculateClasspathHash(IJavaProject javaProject) {
try {
IClasspathEntry[] entries = javaProject.getResolvedClasspath(true);
long hash = 0;
for (IClasspathEntry entry : entries) {
hash = hash * 31 + entry.getPath().toString().hashCode();
hash = hash * 31 + entry.getEntryKind();
}
return hash;
} catch (JavaModelException e) {
return 0;
}
}

// Constants for dependency info keys
private static final String KEY_BUILD_TOOL = "buildTool";
Expand Down Expand Up @@ -54,6 +236,9 @@ public DependencyInfo(String key, String value) {
* @return List of DependencyInfo containing key-value pairs of project information
*/
public static List<DependencyInfo> resolveProjectDependencies(String fileUri, IProgressMonitor monitor) {
// Ensure listeners are registered for cache invalidation
ensureListenersRegistered();

List<DependencyInfo> result = new ArrayList<>();

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

// Generate cache key based on project location
String cacheKey = project.getLocation().toOSString();

// Calculate current classpath hash for validation
long currentClasspathHash = calculateClasspathHash(javaProject);

// Try to get from cache
CachedDependencyInfo cached = dependencyCache.get(cacheKey);
if (cached != null && cached.isValid() && cached.classpathHash == currentClasspathHash) {
JdtlsExtActivator.logInfo("Using cached dependencies for project: " + project.getName());
return new ArrayList<>(cached.dependencies);
}

// Add basic project information
addBasicProjectInfo(result, project, javaProject);

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

// Add build tool info by checking for build files
detectBuildTool(result, project);

// Store in cache
dependencyCache.put(cacheKey, new CachedDependencyInfo(result, currentClasspathHash));

} catch (Exception e) {
JdtlsExtActivator.logException("Error in resolveProjectDependencies", e);
Expand Down
Loading