Gracefully cleanup name env on CU cache. NPE in JPQL tokens in Java
This commit is contained in:
@@ -42,6 +42,8 @@ import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
@@ -63,7 +65,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
public class CompositeLanguageServerComponents implements LanguageServerComponents {
|
||||
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CompositeLanguageServerComponents.class);
|
||||
|
||||
public static class Builder {
|
||||
private Map<LanguageId, List<LanguageServerComponents>> componentsByLanguageId = new HashMap<>();
|
||||
|
||||
@@ -258,10 +262,15 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
|
||||
private Optional<SemanticTokensHandler> findHandler(TextDocumentIdentifier docId) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docId.getUri());
|
||||
LanguageId language = doc.getLanguageId();
|
||||
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
|
||||
if (subComponents != null) {
|
||||
return subComponents.stream().filter(sc -> sc.getSemanticTokensHandler().isPresent()).map(sc -> sc.getSemanticTokensHandler().get()).findFirst();
|
||||
// Only opened docs ideally should get requests for semantic token to highlight
|
||||
if (doc != null) {
|
||||
LanguageId language = doc.getLanguageId();
|
||||
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
|
||||
if (subComponents != null) {
|
||||
return subComponents.stream().filter(sc -> sc.getSemanticTokensHandler().isPresent()).map(sc -> sc.getSemanticTokensHandler().get()).findFirst();
|
||||
}
|
||||
} else {
|
||||
log.error("Received Semantic Tokens request for a non opened document: %s".formatted(docId.getUri()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ public class JdtSemanticTokensHandler implements SemanticTokensHandler {
|
||||
}
|
||||
|
||||
private SemanticTokens computeTokens(List<JdtSemanticTokensProvider> applicableTokenProviders, IJavaProject jp, CompilationUnit cu) {
|
||||
if (cu == null) {
|
||||
return new SemanticTokens();
|
||||
}
|
||||
List<SemanticTokenData> tokensData = applicableTokenProviders.stream().map(tp -> tp.computeTokens(jp, cu)).flatMap(t -> t.stream()).collect(Collectors.toList());
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(tokensData, legend, offset -> cu.getLineNumber(offset) - 1, cu::getColumnNumber));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2024 Pivotal, 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
|
||||
@@ -23,6 +23,9 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -74,6 +77,9 @@ public final class CompilationUnitCache implements DocumentContentProvider {
|
||||
private final Cache<URI, Set<URI>> projectToDocs;
|
||||
private final Cache<URI, Tuple2<List<Classpath>, INameEnvironmentWithProgress>> lookupEnvCache;
|
||||
|
||||
private final ReentrantReadWriteLock environmentCacheLock = new ReentrantReadWriteLock(true);
|
||||
private CompletableFuture<Void> debounceClassFileChanges = CompletableFuture.completedFuture(null);
|
||||
|
||||
private final Executor createCuExecutorThreadPool = Executors.newCachedThreadPool();
|
||||
|
||||
public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) {
|
||||
@@ -153,7 +159,12 @@ public final class CompilationUnitCache implements DocumentContentProvider {
|
||||
}
|
||||
|
||||
if (server != null) {
|
||||
ServerUtils.listenToClassFileChanges(server.getWorkspaceService().getFileObserver(), projectFinder, this::invalidateProject);
|
||||
ServerUtils.listenToClassFileCreateAndChange(server.getWorkspaceService().getFileObserver(), projectFinder, jp -> {
|
||||
if (!debounceClassFileChanges.isDone()) {
|
||||
debounceClassFileChanges.cancel(false);
|
||||
}
|
||||
debounceClassFileChanges = CompletableFuture.runAsync(() -> invalidateProject(jp), CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -200,45 +211,46 @@ public final class CompilationUnitCache implements DocumentContentProvider {
|
||||
logger.info("CU Cache: work item submitted for doc {}", uri.toASCIIString());
|
||||
|
||||
if (project != null) {
|
||||
|
||||
CompilationUnit cu = null;
|
||||
|
||||
ReadLock lock = environmentCacheLock.readLock();
|
||||
lock.lock();
|
||||
try {
|
||||
cu = requestCU(project, uri).get();
|
||||
} catch (UncheckedExecutionException e1) {
|
||||
// ignore errors from rewrite parser. There could be many parser exceptions due to
|
||||
// user incrementally typing code's text
|
||||
return null;
|
||||
} catch (InvalidCacheLoadException | CancellationException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
return requestor.apply(null);
|
||||
}
|
||||
|
||||
if (cu != null) {
|
||||
CompilationUnit cu = null;
|
||||
try {
|
||||
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
|
||||
|
||||
logger.debug("CU Cache: start work on AST for {}", uri.toString());
|
||||
return requestor.apply(cu);
|
||||
}
|
||||
catch (CancellationException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
cu = requestCU(project, uri).get();
|
||||
} catch (UncheckedExecutionException e1) {
|
||||
// ignore errors from rewrite parser. There could be many parser exceptions due to
|
||||
// user incrementally typing code's text
|
||||
return null;
|
||||
} catch (InvalidCacheLoadException | CancellationException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
return requestor.apply(null);
|
||||
}
|
||||
finally {
|
||||
logger.debug("CU Cache: end work on AST for {}", uri.toString());
|
||||
|
||||
if (cu != null) {
|
||||
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
|
||||
|
||||
logger.debug("CU Cache: start work on AST for {}", uri.toString());
|
||||
return requestor.apply(cu);
|
||||
}
|
||||
}
|
||||
catch (CancellationException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
finally {
|
||||
logger.debug("CU Cache: end work on AST for {}", uri.toString());
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
return requestor.apply(null);
|
||||
}
|
||||
|
||||
private synchronized CompletableFuture<CompilationUnit> requestCU(IJavaProject project, URI uri) throws ExecutionException {
|
||||
private synchronized CompletableFuture<CompilationUnit> requestCU(IJavaProject project, URI uri) {
|
||||
CompletableFuture<CompilationUnit> cuFuture = uriToCu.getIfPresent(uri);
|
||||
if (cuFuture == null) {
|
||||
cuFuture = CompletableFuture.supplyAsync(() -> {
|
||||
@@ -352,7 +364,13 @@ public final class CompilationUnitCache implements DocumentContentProvider {
|
||||
uriToCu.invalidateAll(docUris);
|
||||
projectToDocs.invalidate(project.getLocationUri());
|
||||
}
|
||||
lookupEnvCache.invalidate(project.getLocationUri());
|
||||
WriteLock lock = environmentCacheLock.writeLock();
|
||||
lock.lock();
|
||||
try {
|
||||
lookupEnvCache.invalidate(project.getLocationUri());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 VMware, Inc.
|
||||
* Copyright (c) 2023, 2024 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
|
||||
@@ -13,7 +13,9 @@ 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.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
@@ -30,25 +32,33 @@ public class ServerUtils {
|
||||
|
||||
private static final List<String> CLASS_FILES_TO_WATCH_GLOB = List.of("**/*.class");
|
||||
|
||||
public static void listenToClassFileChanges(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
|
||||
public static void listenToAnyClassFileChanges(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
|
||||
fileObserver.onAnyChange(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback));
|
||||
}
|
||||
|
||||
public static void listenToClassFileCreateAndChange(FileObserver fileObserver, JavaProjectFinder projectFinder, Consumer<IJavaProject> callback) {
|
||||
fileObserver.onCreatedOrChanged(CLASS_FILES_TO_WATCH_GLOB, files -> handleFiles(projectFinder, files, callback));
|
||||
}
|
||||
|
||||
private static void handleFiles(JavaProjectFinder projectFinder, String[] files, Consumer<IJavaProject> callback) {
|
||||
Set<IJavaProject> projects = new LinkedHashSet<>();
|
||||
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);
|
||||
}
|
||||
projects.add(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (IJavaProject project : projects) {
|
||||
try {
|
||||
callback.accept(project);
|
||||
} catch (Throwable t) {
|
||||
log.error("", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user