diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java index fdf17a3c4..ce2e33a6e 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java @@ -170,6 +170,7 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi FileSystems.getDefault().getPathMatcher("glob:**/*.json"), FileSystems.getDefault().getPathMatcher("glob:**/*.yml"), FileSystems.getDefault().getPathMatcher("glob:**/*.properties"), + FileSystems.getDefault().getPathMatcher("glob:**/*.class"), FileSystems.getDefault().getPathMatcher("glob:**/META-INF/spring/*.factories") ))); diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index b194f9fbe..f7bb73edf 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -113,9 +113,9 @@ 1.13 - 8.1.2 - 5.0.1 - 1.0.0 + 8.2.0-SNAPSHOT + 5.1.0-SNAPSHOT + 1.1.0-SNAPSHOT 7.6 6.1.1 diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java index eadc31952..5919c4e32 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathListenerManager.java @@ -10,30 +10,22 @@ *******************************************************************************/ package org.springframework.tooling.jdt.ls.commons.classpath; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Queue; -import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; -import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; 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.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; import org.springframework.tooling.jdt.ls.commons.Logger; /** @@ -87,8 +79,7 @@ public class ClasspathListenerManager { // Classpath unchanged but maven/gradle repo cache has JAR's removed or downloaded // See individual method comments for more details || isClasspathManifestFileChanged(jp, delta) - || areClasspathJarsChanged(delta) - || areOutputFoldersContentChanged(jp, delta)) { + || areClasspathJarsChanged(delta)) { listener.classpathChanged(jp); } break; @@ -116,53 +107,6 @@ public class ClasspathListenerManager { return false; } - private boolean areOutputFoldersContentChanged(IJavaProject jp, IJavaElementDelta delta) { - Collection outputFolders = getOutputFolders(jp); - if (delta.getResourceDeltas() != null && (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) { - for (IResourceDelta resourceDelta : delta.getResourceDeltas()) { - if (outputFolders.stream().anyMatch(of -> resourceDelta.getFullPath().isPrefixOf(of))) { - return areClassFilesChangedOrAdded(resourceDelta); - } - } - } - return false; - } - - private boolean areClassFilesChangedOrAdded(IResourceDelta resourceDelta) { - if (resourceDelta.getResource() instanceof IContainer) { - for (IResourceDelta rd : resourceDelta.getAffectedChildren()) { - if(areClassFilesChangedOrAdded(rd)) { - return true; - } - } - return false; - } else { - if ("class".equals(resourceDelta.getResource().getFileExtension())) { - switch (resourceDelta.getKind()) { - case IResourceDelta.ADDED: - return true; - case IResourceDelta.CHANGED: - return (resourceDelta.getFlags() & IResourceDelta.CONTENT) != 0; - } - } - return false; - } - } - - private Collection getOutputFolders(IJavaProject jp) { - try { - Set outputFolders = new HashSet<>();; - for (IClasspathEntry cpe : jp.getRawClasspath()) { - if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE && cpe.getOutputLocation() != null) { - outputFolders.add(cpe.getOutputLocation()); - } - } - return outputFolders; - } catch (JavaModelException e) { - return Collections.emptyList(); - } - } - /** * When Maven project update is completed .classpath file content changed is one * of the resource delta's expected. diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index f75255dca..31dd5dbed 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -29,6 +29,7 @@ import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ServerUtils; import org.springframework.ide.vscode.boot.java.utils.SymbolCache; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; @@ -80,7 +81,7 @@ public class BootLanguageServerInitializer implements InitializingBean { private VscodeCompletionEngineAdapter completionEngineAdapter; private static final Logger log = LoggerFactory.getLogger(BootLanguageServerInitializer.class); - + private ProjectObserver.Listener reconcileDocumentsForProjectChange(SimpleLanguageServer s, CompositeLanguageServerComponents c, JavaProjectFinder projectFinder) { return new ProjectObserver.Listener() { @@ -90,28 +91,28 @@ public class BootLanguageServerInitializer implements InitializingBean { @Override public void created(IJavaProject project) { - validateAll(project); + validateAll(c, s, project); } @Override public void changed(IJavaProject project) { - validateAll(project); + validateAll(c, s, project); } - private void validateAll(IJavaProject project) { - c.getReconcileEngine().ifPresent(reconciler -> { - log.debug("A project changed {}, triggering reconcile on all project's open documents", - project.getElementName()); - for (TextDocument doc : s.getTextDocumentService().getAll()) { - if (projectFinder.find(doc.getId()).orElse(null) == project) { - s.validateWith(doc.getId(), reconciler); - } - } - }); - } }; } + private void validateAll(CompositeLanguageServerComponents c, SimpleLanguageServer s, IJavaProject project) { + c.getReconcileEngine().ifPresent(reconciler -> { + log.debug("A project changed {}, triggering reconcile on all project's open documents", + project.getElementName()); + for (TextDocument doc : s.getTextDocumentService().getAll()) { + if (projectFinder.find(doc.getId()).orElse(null) == project) { + s.validateWith(doc.getId(), reconciler); + } + } + }); + } @Override public void afterPropertiesSet() throws Exception { //TODO: CompositeLanguageServerComponents object instance serves no purpose anymore. The constructor really just contains @@ -191,6 +192,8 @@ public class BootLanguageServerInitializer implements InitializingBean { TextDocument doc = params.getDocument(); server.validateWith(doc.getId(), reconcileEngine); }); + + ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, project -> validateAll(components, server, project)); }); config.addListener(evt -> reconcile()); params.projectObserver.addListener(reconcileDocumentsForProjectChange(server, components, params.projectFinder)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java index 611474ce2..503be019e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaProjectReconcilerScheduler.java @@ -16,6 +16,7 @@ import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.common.IJavaProjectReconcileEngine; import org.springframework.ide.vscode.boot.common.ProjectReconcileScheduler; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; +import org.springframework.ide.vscode.boot.java.utils.ServerUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; @@ -25,8 +26,6 @@ public class BootJavaProjectReconcilerScheduler extends ProjectReconcileSchedule private static final Logger log = LoggerFactory.getLogger(BootJavaProjectReconcilerScheduler.class); -// private static final List FILES_TO_WATCH_GLOB = List.of("**/*.java"); - private ProjectObserver projectObserver; private BootJavaConfig config; private RewriteRecipeRepository recipesRepo; @@ -80,29 +79,11 @@ public class BootJavaProjectReconcilerScheduler extends ProjectReconcileSchedule } }); -// getServer().getWorkspaceService().getFileObserver().onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles); -// getServer().getWorkspaceService().getFileObserver().onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles); + ServerUtils.listenToClassFileChanges(getServer().getWorkspaceService().getFileObserver(), getProjectFinder(), this::scheduleValidation); // TODO: index update even happens on every file save. Very expensive to blindly reconcile all projects. // Need to figure out a check if spring index has any changes // springIndexer.onUpdate(v -> reconcile()); } -// private void handleFiles(String[] files) { -// for (String f : files) { -// URI uri = URI.create(f); -// TextDocumentIdentifier docId = new TextDocumentIdentifier(uri.toASCIIString()); -// TextDocument doc = getServer().getTextDocumentService().getLatestSnapshot(docId.getUri()); -// if (doc == null) { -// getProjectFinder().find(docId).ifPresent(project -> { -// Path p = Paths.get(uri); -// if (IClasspathUtil.getSourceFolders(project.getClasspath()) -// .filter(folder -> p.startsWith(folder.toPath())).findFirst().isPresent()) { -// scheduleValidation(project); -// } -// }); -// } -// } -// } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java index 5b2ffcd74..8ea86f900 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java @@ -28,8 +28,8 @@ import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.openrewrite.Tree; import org.openrewrite.Parser.Input; +import org.openrewrite.Tree; import org.openrewrite.java.JavaParser; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.J.CompilationUnit; @@ -38,6 +38,7 @@ import org.openrewrite.java.tree.JavaType.FullyQualified; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.utils.DocumentContentProvider; +import org.springframework.ide.vscode.boot.java.utils.ServerUtils; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -90,7 +91,7 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis CompletableFuture future = notification.getValue(); if (future != null) { - if (!future.isCancelled()) { + if (!future.isDone() && !future.isCancelled()) { future.cancel(true); } Optional project = projectFinder.find(new TextDocumentIdentifier(uri.toASCIIString())); @@ -98,8 +99,19 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis JavaParser parser = javaParsers.getIfPresent(project.get().getLocationUri()); if (parser != null) { - parser.reset(List.of(uri)); -// parser.reset(); +// if (future.isDone()) { +// try { +// CompilationUnit cu = future.get(); +// if (cu != null) { +// parser.resetCUs(List.of(cu)); +// return; +// } +// } catch (Throwable t) { +// logger.error("", t); +// } +// } +// parser.reset(List.of(uri)); + parser.reset(); } } } @@ -113,6 +125,7 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis @Override public void onRemoval(RemovalNotification notification) { + logger.info("CU Cache: invalidate project {}", notification.getKey()); sourceSetClasspath.invalidate(notification.getKey()); } }) @@ -164,6 +177,10 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis this.projectObserver.addListener(this.projectListener); } + if (server != null) { + ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, this::invalidateProject); + } + } public void dispose() { @@ -187,8 +204,6 @@ public class RewriteCompilationUnitCache implements DocumentContentProvider, Dis } private void invalidateProject(IJavaProject project) { - logger.info("CU Cache: invalidate project <{}>", project.getElementName()); - Set docUris = projectToDocs.getIfPresent(project.getLocationUri()); if (docUris != null) { uriToCu.invalidateAll(docUris); 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 48dff22e9..413900c38 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 @@ -147,6 +147,10 @@ public final class CompilationUnitCache implements DocumentContentProvider { this.projectObserver.addListener(this.projectListener); } + if (server != null) { + ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, this::invalidateProject); + } + } public void dispose() { @@ -329,8 +333,6 @@ public final class CompilationUnitCache implements DocumentContentProvider { } private synchronized void invalidateProject(IJavaProject project) { - logger.info("Invalidate project <{}>", project.getElementName()); - Set docUris = projectToDocs.getIfPresent(project.getLocationUri()); if (docUris != null) { uriToCu.invalidateAll(docUris); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ServerUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ServerUtils.java new file mode 100644 index 000000000..f9688c529 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ServerUtils.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.commons.java.IClasspathUtil; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.util.FileObserver; + +public class ServerUtils { + + private static final Logger log = LoggerFactory.getLogger(ServerUtils.class); + + private static final List CLASS_FILES_TO_WATCH_GLOB = List.of("**/*.class"); + + public static void listenToClassFileChanges(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer callback) { + fileObserver.onFilesChanged(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback)); + fileObserver.onFilesCreated(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback)); + } + + private static void handleFiles(JavaProjectFinder projectFinder, String[] files, Consumer callback) { + for (String f : files) { + URI uri = URI.create(f); + TextDocumentIdentifier docId = new TextDocumentIdentifier(uri.toASCIIString()); + projectFinder.find(docId).ifPresent(project -> { + Path p = Paths.get(uri); + if (IClasspathUtil.getOutputFolders(project.getClasspath()).anyMatch(folder -> p.startsWith(folder.toPath()))) { + try { + callback.accept(project); + } catch (Throwable t) { + log.error("", t); + } + } + }); + } + } + + +}