diff --git a/headless-services/spring-boot-language-server/pom.xml b/headless-services/spring-boot-language-server/pom.xml
index ff13c574e..45c407e10 100644
--- a/headless-services/spring-boot-language-server/pom.xml
+++ b/headless-services/spring-boot-language-server/pom.xml
@@ -96,7 +96,7 @@
org.eclipse.jdt
org.eclipse.jdt.core
- 3.20.100-SNAPSHOT
+ 3.21.0-SNAPSHOT
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
index 4dea47313..73bdaa652 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
@@ -11,11 +11,14 @@
package org.springframework.ide.vscode.boot.java.utils;
import java.net.URI;
-import java.util.HashSet;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.core.JavaCore;
@@ -34,6 +37,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserve
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.AsyncRunner;
+import org.springframework.ide.vscode.commons.util.FileObserver;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.cache.Cache;
@@ -48,12 +52,12 @@ public final class CompilationUnitCache implements DocumentContentProvider {
private static final long CU_ACCESS_EXPIRATION = 1;
private JavaProjectFinder projectFinder;
private ProjectObserver projectObserver;
- private Cache uriToCu;
- private Cache> projectToDocs;
- private Cache lookupEnvCache;
+ private Cache lookupEnvCache;
+ private Map projectSubscriptions;
private ProjectObserver.Listener projectListener;
private SimpleTextDocumentService documents;
private AsyncRunner async;
+ private FileObserver fileObserver;
private Object lock = new Object();;
@@ -62,16 +66,10 @@ public final class CompilationUnitCache implements DocumentContentProvider {
this.projectObserver = projectObserver;
this.lookupEnvCache = CacheBuilder.newBuilder().build();
- // PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been
- // accessed after some time
- uriToCu = CacheBuilder.newBuilder()
- .expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES)
- .removalListener(removal -> invalidateCuForJavaFile(removal.getKey().toString()))
- .build();
- projectToDocs = CacheBuilder.newBuilder().build();
-
this.documents = server == null ? null : server.getTextDocumentService();
this.async = server == null ? new AsyncRunner(Schedulers.single()) : server.getAsync();
+ this.fileObserver = server == null ? null : server.getWorkspaceService().getFileObserver();
+ this.projectSubscriptions = new HashMap<>();
if (documents != null) {
documents.onDidChangeContent(doc -> {
@@ -79,11 +77,11 @@ public final class CompilationUnitCache implements DocumentContentProvider {
invalidateCuForJavaFile(doc.getDocument().getId().getUri());
}
});
- documents.onDidClose(doc -> {
- synchronized(lock) {
- invalidateCuForJavaFile(doc.getId().getUri());
- }
- });
+// documents.onDidClose(doc -> {
+// synchronized(lock) {
+// invalidateCuForJavaFile(doc.getId().getUri());
+// }
+// });
}
// async.execute(() -> {
@@ -104,7 +102,10 @@ public final class CompilationUnitCache implements DocumentContentProvider {
logger.info("CU Cache: deleted project {}", project.getElementName());
async.execute(() -> {
synchronized(lock) {
+ stopListenToProjectSourceFolderChanges(project);
invalidateProject(project);
+ projectData(project);
+ startListenToProjectSourceFolderChanges(project);
}
});
}
@@ -114,7 +115,10 @@ public final class CompilationUnitCache implements DocumentContentProvider {
logger.info("CU Cache: created project {}", project.getElementName());
async.execute(() -> {
synchronized(lock) {
+ stopListenToProjectSourceFolderChanges(project);
invalidateProject(project);
+ projectData(project);
+ startListenToProjectSourceFolderChanges(project);
}
});
}
@@ -135,6 +139,40 @@ public final class CompilationUnitCache implements DocumentContentProvider {
}
}
+
+ private void startListenToProjectSourceFolderChanges(IJavaProject project) {
+ List globs = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).map(file -> {
+ Path path = file.getAbsoluteFile().toPath();
+ return Stream.of(path.resolve("*").toString(), path.resolve("**/*").toString());
+ }).flatMap(glob -> glob).collect(Collectors.toList());
+ String[] subscriptions = new String[] {
+ fileObserver.onFileCreated(globs, (file) -> {
+ invalidateProject(project);
+ try {
+ projectData(project);
+ } catch (ExecutionException e) {
+ logger.error("", e);
+ }
+ }),
+ fileObserver.onFileDeleted(globs, (file) -> {
+ invalidateProject(project);
+ try {
+ projectData(project);
+ } catch (ExecutionException e) {
+ logger.error("", e);
+ }
+ }),
+ };
+ projectSubscriptions.put(project, subscriptions);
+ }
+
+ private void stopListenToProjectSourceFolderChanges(IJavaProject project) {
+ if (projectSubscriptions.containsKey(project)) {
+ for (String subscription : projectSubscriptions.remove(project)) {
+ CompilationUnitCache.this.fileObserver.unsubscribe(subscription);
+ }
+ }
+ }
public void dispose() {
if (projectObserver != null) {
@@ -164,20 +202,22 @@ public final class CompilationUnitCache implements DocumentContentProvider {
synchronized(lock ) {
try {
- cu = uriToCu.get(uri, () -> {
- BindingEnvironment lookupEnv =lookupEnvCache.get(project, () -> {
- return createClasspath(project);
- });
- String utiStr = uri.toString();
- String unitName = utiStr.substring(utiStr.lastIndexOf("/"));
- CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnv);
-
- projectToDocs.get(project, () -> new HashSet<>()).add(uri);
- return cUnit;
- });
- if (cu != null) {
- projectToDocs.get(project, () -> new HashSet<>()).add(uri);
- }
+ ProjectEnvironmentData data = projectData(project);
+ cu = data.compilationUnit(uri);
+// cu = uriToCu.get(uri, () -> {
+// BindingEnvironment lookupEnv =lookupEnvCache.get(project, () -> {
+// return createClasspath(project);
+// });
+// String utiStr = uri.toString();
+// String unitName = utiStr.substring(utiStr.lastIndexOf("/"));
+// CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnv);
+//
+// projectToDocs.get(project, () -> new HashSet<>()).add(uri);
+// return cUnit;
+// });
+// if (cu != null) {
+// projectToDocs.get(project, () -> new HashSet<>()).add(uri);
+// }
} catch (Exception e) {
logger.error("", e);
}
@@ -200,17 +240,27 @@ public final class CompilationUnitCache implements DocumentContentProvider {
private void invalidateCuForJavaFile(String uriStr) {
- URI uri = URI.create(uriStr);
- uriToCu.invalidate(uri);
+// URI uri = URI.create(uriStr);
+// uriToCu.invalidate(uri);
+// IJavaProject project = projectFinder.find(new TextDocumentIdentifier(uriStr)).orElse(null);
+// if (project != null) {
+// Set docUris = projectToDocs.getIfPresent(project);
+// if (docUris != null) {
+// docUris.remove(uri);
+// if (docUris.isEmpty()) {
+// invalidateProject(project);
+// } else {
+// BindingEnvironment environment = lookupEnvCache.getIfPresent(project);
+// if (environment != null) {
+// environment.reset();
+// }
+// }
+// }
+// }
IJavaProject project = projectFinder.find(new TextDocumentIdentifier(uriStr)).orElse(null);
if (project != null) {
- Set docUris = projectToDocs.getIfPresent(project);
- if (docUris != null) {
- docUris.remove(uri);
- if (docUris.isEmpty()) {
- invalidateProject(project);
- }
- }
+ ProjectEnvironmentData data = this.lookupEnvCache.getIfPresent(project);
+ data.reset();
}
}
@@ -341,7 +391,16 @@ public final class CompilationUnitCache implements DocumentContentProvider {
// }
// return CUResolver.getClasspath(parser);
}
-
+
+ private ProjectEnvironmentData createProjectEnvironment(IJavaProject project) {
+ IClasspath cp = project.getClasspath();
+ BindingEnvironment env = ASTParser.createBindingEnvironment(
+ IClasspathUtil.getAllBinaryRoots(cp).stream().map(f -> f.getAbsolutePath()).toArray(String[]::new),
+ new String[0], null, false, createCompilerOptions(), 0);
+ return new ProjectEnvironmentData(env);
+
+ }
+
// private static String[] getClasspathEntries(IJavaProject project) throws Exception {
// if (project == null) {
// return new String[0];
@@ -354,16 +413,26 @@ public final class CompilationUnitCache implements DocumentContentProvider {
// }
// }
- private void invalidateProject(IJavaProject project) {
- Set docUris = projectToDocs.getIfPresent(project);
- if (docUris != null) {
- uriToCu.invalidateAll(docUris);
- projectToDocs.invalidate(project);
- }
- BindingEnvironment env = lookupEnvCache.getIfPresent(project);
- if (env != null) {
- env.dispose();
+ private boolean invalidateProject(IJavaProject project) {
+// Set docUris = projectToDocs.getIfPresent(project);
+// if (docUris != null) {
+// uriToCu.invalidateAll(docUris);
+// projectToDocs.invalidate(project);
+// }
+// BindingEnvironment env = lookupEnvCache.getIfPresent(project);
+// if (env != null) {
+// env.dispose();
+// }
+ ProjectEnvironmentData data = lookupEnvCache.getIfPresent(project);
+ if (data != null) {
+ data.dispose();
}
+ lookupEnvCache.invalidate(project);
+ return data != null;
+ }
+
+ private ProjectEnvironmentData projectData(IJavaProject project) throws ExecutionException {
+ return lookupEnvCache.get(project, () -> createProjectEnvironment(project));
}
@Override
@@ -378,6 +447,35 @@ public final class CompilationUnitCache implements DocumentContentProvider {
}
+ private class ProjectEnvironmentData {
+
+ private BindingEnvironment bindingEnvironment;
+
+ private Cache compilationUnits;
+
+ ProjectEnvironmentData(BindingEnvironment bindingEnvironment) {
+ this.bindingEnvironment = bindingEnvironment;
+ this.compilationUnits = CacheBuilder.newBuilder().build();
+ }
+
+ CompilationUnit compilationUnit(URI uri) throws ExecutionException {
+ return this.compilationUnits.get(uri, () -> {
+ String utiStr = uri.toString();
+ String unitName = utiStr.substring(utiStr.lastIndexOf("/"));
+ return parse2(fetchContent(uri).toCharArray(), utiStr, unitName, bindingEnvironment);
+ });
+ }
+
+ void reset() {
+ this.bindingEnvironment.reset();
+ }
+
+ void dispose() {
+ this.compilationUnits.invalidateAll();
+ this.bindingEnvironment.dispose();
+ }
+ }
+
// private static class ClasspathLookupEnvironmentPool {
//
// private Map> classpathsMap = new HashMap<>();